myetv-player 1.5.0 → 1.6.1

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.
@@ -27,7 +27,8 @@ class VideoPlayerI18n {
27
27
  'playlist_prev': 'Indietro',
28
28
  'settings_menu': 'Impostazioni',
29
29
  'loading': 'Caricamento...',
30
- 'encoding_in_progress': 'Encoding in corso'
30
+ 'encoding_in_progress': 'Encoding in corso',
31
+ 'restart_video': 'Torna all\'inizio'
31
32
  },
32
33
 
33
34
  // English
@@ -51,7 +52,8 @@ class VideoPlayerI18n {
51
52
  'playlist_prev': 'Previous',
52
53
  'settings_menu': 'Settings',
53
54
  'loading': 'Loading...',
54
- 'encoding_in_progress': 'Encoding in progress'
55
+ 'encoding_in_progress': 'Encoding in progress',
56
+ 'restart_video': 'Restart the video'
55
57
  },
56
58
 
57
59
  // Español
@@ -75,7 +77,8 @@ class VideoPlayerI18n {
75
77
  'playlist_prev': 'Anterior',
76
78
  'settings_menu': 'Configuración',
77
79
  'loading': 'Cargando...',
78
- 'encoding_in_progress': 'Codificación en curso'
80
+ 'encoding_in_progress': 'Codificación en curso',
81
+ 'restart_video': 'Reiniciar el vídeo'
79
82
  },
80
83
 
81
84
  // Français
@@ -99,7 +102,8 @@ class VideoPlayerI18n {
99
102
  'playlist_prev': 'Précédent',
100
103
  'settings_menu': 'Paramètres',
101
104
  'loading': 'Chargement...',
102
- 'encoding_in_progress': 'Encodage en cours'
105
+ 'encoding_in_progress': 'Encodage en cours',
106
+ 'restart_video': 'Redémarrer la vidéo'
103
107
  },
104
108
 
105
109
  // Deutsch
@@ -123,7 +127,8 @@ class VideoPlayerI18n {
123
127
  'playlist_prev': 'Zurück',
124
128
  'settings_menu': 'Einstellungen',
125
129
  'loading': 'Laden...',
126
- 'encoding_in_progress': 'Kodierung läuft'
130
+ 'encoding_in_progress': 'Kodierung läuft',
131
+ 'restart_video': 'Video neu starten'
127
132
  },
128
133
 
129
134
  // Português
@@ -147,7 +152,8 @@ class VideoPlayerI18n {
147
152
  'playlist_prev': 'Anterior',
148
153
  'settings_menu': 'Configurações',
149
154
  'loading': 'Carregando...',
150
- 'encoding_in_progress': 'Codificação em andamento'
155
+ 'encoding_in_progress': 'Codificação em andamento',
156
+ 'restart_video': 'Restart video'
151
157
  },
152
158
 
153
159
  // 中文
@@ -171,7 +177,8 @@ class VideoPlayerI18n {
171
177
  'playlist_prev': '上一个',
172
178
  'settings_menu': '设置',
173
179
  'loading': '加载中...',
174
- 'encoding_in_progress': '编码中'
180
+ 'encoding_in_progress': '编码中',
181
+ 'restart_video': '重新开始视频'
175
182
  },
176
183
 
177
184
  // 日本語
@@ -195,7 +202,8 @@ class VideoPlayerI18n {
195
202
  'playlist_prev': '前へ',
196
203
  'settings_menu': '設定',
197
204
  'loading': '読み込み中...',
198
- 'encoding_in_progress': 'エンコード中'
205
+ 'encoding_in_progress': 'エンコード中',
206
+ 'restart_video': 'ビデオを再開'
199
207
  },
200
208
 
201
209
  // Русский
@@ -219,7 +227,8 @@ class VideoPlayerI18n {
219
227
  'playlist_prev': 'Назад',
220
228
  'settings_menu': 'Настройки',
221
229
  'loading': 'Загрузка...',
222
- 'encoding_in_progress': 'Кодирование'
230
+ 'encoding_in_progress': 'Кодирование',
231
+ 'restart_video': 'Перезапустить видео'
223
232
  },
224
233
 
225
234
  // العربية
@@ -243,7 +252,8 @@ class VideoPlayerI18n {
243
252
  'playlist_prev': 'السابق',
244
253
  'settings_menu': 'الإعدادات',
245
254
  'loading': 'جاري التحميل...',
246
- 'encoding_in_progress': 'الترميز جارٍ'
255
+ 'encoding_in_progress': 'الترميز جارٍ',
256
+ 'restart_video': 'إعادة تشغيل الفيديو'
247
257
  },
248
258
 
249
259
  // 한국어 (Korean)
@@ -267,7 +277,8 @@ class VideoPlayerI18n {
267
277
  'playlist_prev': '이전',
268
278
  'settings_menu': '설정',
269
279
  'loading': '로딩 중...',
270
- 'encoding_in_progress': '인코딩 진행 중'
280
+ 'encoding_in_progress': '인코딩 진행 중',
281
+ 'restart_video': 'Restart video'
271
282
  },
272
283
 
273
284
  // Polski
@@ -291,7 +302,8 @@ class VideoPlayerI18n {
291
302
  'playlist_prev': 'Wstecz',
292
303
  'settings_menu': 'Ustawienia',
293
304
  'loading': 'Ładowanie...',
294
- 'encoding_in_progress': 'Kodowanie w toku'
305
+ 'encoding_in_progress': 'Kodowanie w toku',
306
+ 'restart_video': 'Restart video'
295
307
  },
296
308
 
297
309
  // Magyar
@@ -315,7 +327,8 @@ class VideoPlayerI18n {
315
327
  'playlist_prev': 'Előző',
316
328
  'settings_menu': 'Beállítások',
317
329
  'loading': 'Betöltés...',
318
- 'encoding_in_progress': 'Kódolás folyamatban'
330
+ 'encoding_in_progress': 'Kódolás folyamatban',
331
+ 'restart_video': 'Restart video'
319
332
  },
320
333
 
321
334
  // Türkçe
@@ -339,7 +352,8 @@ class VideoPlayerI18n {
339
352
  'playlist_prev': 'Önceki',
340
353
  'settings_menu': 'Ayarlar',
341
354
  'loading': 'Yükleniyor...',
342
- 'encoding_in_progress': 'Kodlama devam ediyor'
355
+ 'encoding_in_progress': 'Kodlama devam ediyor',
356
+ 'restart_video': 'Restart video'
343
357
  },
344
358
 
345
359
  // Nederlands
@@ -363,7 +377,8 @@ class VideoPlayerI18n {
363
377
  'playlist_prev': 'Vorige',
364
378
  'settings_menu': 'Instellingen',
365
379
  'loading': 'Laden...',
366
- 'encoding_in_progress': 'Codering bezig'
380
+ 'encoding_in_progress': 'Codering bezig',
381
+ 'restart_video': 'Restart video'
367
382
  },
368
383
 
369
384
  // हिन्दी (Hindi)
@@ -387,7 +402,8 @@ class VideoPlayerI18n {
387
402
  'playlist_prev': 'पिछला',
388
403
  'settings_menu': 'सेटिंग्स',
389
404
  'loading': 'लोड हो रहा है...',
390
- 'encoding_in_progress': 'एन्कोडिंग प्रगति में'
405
+ 'encoding_in_progress': 'एन्कोडिंग प्रगति में',
406
+ 'restart_video': 'Restart video'
391
407
  },
392
408
 
393
409
  // Svenska
@@ -411,7 +427,8 @@ class VideoPlayerI18n {
411
427
  'playlist_prev': 'Föregående',
412
428
  'settings_menu': 'Inställningar',
413
429
  'loading': 'Laddar...',
414
- 'encoding_in_progress': 'Kodning pågår'
430
+ 'encoding_in_progress': 'Kodning pågår',
431
+ 'restart_video': 'Restart video'
415
432
  },
416
433
 
417
434
  // Bahasa Indonesia
@@ -435,7 +452,8 @@ class VideoPlayerI18n {
435
452
  'playlist_prev': 'Sebelumnya',
436
453
  'settings_menu': 'Pengaturan',
437
454
  'loading': 'Memuat...',
438
- 'encoding_in_progress': 'Encoding sedang berlangsung'
455
+ 'encoding_in_progress': 'Encoding sedang berlangsung',
456
+ 'restart_video': 'Restart video'
439
457
  }
440
458
  };
441
459
 
@@ -565,7 +583,8 @@ try {
565
583
  'brand_logo': 'Brand logo',
566
584
  'settings_menu': 'Settings',
567
585
  'loading': 'Loading...',
568
- 'encoding_in_progress': 'Encoding in progress'
586
+ 'encoding_in_progress': 'Encoding in progress',
587
+ 'restart_video': 'Restart video'
569
588
  };
570
589
  return fallback[key] || key;
571
590
  },
@@ -629,6 +648,7 @@ constructor(videoElement, options = {}) {
629
648
  }
630
649
 
631
650
  this.options = {
651
+ playFromStartButton: false, // Enable play from start button (restart video)
632
652
  showQualitySelector: true, // Enable quality selector button
633
653
  showSpeedControl: true, // Enable speed control button
634
654
  showFullscreen: true, // Enable fullscreen button
@@ -737,6 +757,7 @@ constructor(videoElement, options = {}) {
737
757
  'played': [], // Fired when video starts playing
738
758
  'paused': [], // Fired when video is paused
739
759
  'ended': [], // Fired when video playback ends
760
+ 'restarted': [], // Fired when video is restarted from beginning
740
761
 
741
762
  // Playback state events
742
763
  'playing': [], // Fired when video is actually playing (after buffering)
@@ -1454,6 +1475,7 @@ initializeElements() {
1454
1475
  this.progressHandle = this.controls?.querySelector('.progress-handle');
1455
1476
  this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
1456
1477
 
1478
+ this.playFromStartBtn = this.controls?.querySelector('.play-from-start-btn');
1457
1479
  this.playPauseBtn = this.controls?.querySelector('.play-pause-btn');
1458
1480
  this.muteBtn = this.controls?.querySelector('.mute-btn');
1459
1481
  this.fullscreenBtn = this.controls?.querySelector('.fullscreen-btn');
@@ -1559,6 +1581,51 @@ setupMenuToggles() {
1559
1581
  }
1560
1582
  }
1561
1583
 
1584
+ /**
1585
+ * Restart video from beginning - Works with HTML5 video and ALL plugins
1586
+ * @returns {this} Returns this for method chaining
1587
+ */
1588
+ restartVideo() {
1589
+ if (!this.video) return this;
1590
+
1591
+ const previousTime = this.getCurrentTime();
1592
+ const wasPaused = this.isPaused();
1593
+
1594
+ // Set video to beginning (0 seconds)
1595
+ // This works for both HTML5 video and plugins
1596
+ this.video.currentTime = 0;
1597
+
1598
+ // Alternative: use seek method if available (for plugins)
1599
+ if (typeof this.seek === 'function') {
1600
+ this.seek(0);
1601
+ }
1602
+
1603
+ // Auto-play after restart if video was playing
1604
+ if (!wasPaused) {
1605
+ // Use player's play method (works for HTML5 and plugins)
1606
+ if (typeof this.play === 'function') {
1607
+ this.play();
1608
+ } else {
1609
+ this.video.play().catch(error => {
1610
+ if (this.options.debug) console.warn('⚠️ Restart play failed:', error);
1611
+ });
1612
+ }
1613
+ }
1614
+
1615
+ if (this.options.debug) {
1616
+ console.log(`🔄 Video restarted from ${this.formatTime(previousTime)} to 0:00`);
1617
+ }
1618
+
1619
+ // Trigger custom event
1620
+ this.triggerEvent('restarted', {
1621
+ previousTime: previousTime,
1622
+ duration: this.getDuration(),
1623
+ autoPlayed: !wasPaused
1624
+ });
1625
+
1626
+ return this;
1627
+ }
1628
+
1562
1629
  updateVolumeSliderVisual() {
1563
1630
  if (!this.video || !this.container) return;
1564
1631
 
@@ -2762,6 +2829,14 @@ addEventListener(eventType, callback) {
2762
2829
  bindEvents() {
2763
2830
  if (this.video) {
2764
2831
 
2832
+ // Play from start button
2833
+ if (this.playFromStartBtn) {
2834
+ this.playFromStartBtn.addEventListener('click', (e) => {
2835
+ e.stopPropagation();
2836
+ this.restartVideo();
2837
+ });
2838
+ }
2839
+
2765
2840
  // Playback events
2766
2841
  this.video.addEventListener('playing', () => {
2767
2842
  this.hideLoading();
@@ -3587,26 +3662,36 @@ createControls() {
3587
3662
 
3588
3663
  <div class="controls-main">
3589
3664
  <div class="controls-left">
3590
- <button class="control-btn play-pause-btn" data-tooltip="play_pause">
3591
- <span class="icon play-icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></span>
3592
- <span class="icon pause-icon hidden"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg></span>
3593
- </button>
3665
+ ${this.options.playFromStartButton ? `
3666
+ <button class="control-btn play-from-start-btn" data-tooltip="restart_video">
3667
+ <span class="icon restart-icon">
3668
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
3669
+ <path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
3670
+ </svg>
3671
+ </span>
3672
+ </button>
3673
+ ` : ''}
3594
3674
 
3595
- <button class="control-btn mute-btn" data-tooltip="mute_unmute">
3596
- <span class="icon volume-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303z"/><path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89z"/><path d="M10.025 8a4.486 4.486 0 0 1-1.318 3.182L8 10.475A3.489 3.489 0 0 0 9.025 8c0-.966-.392-1.841-1.025-2.475l.707-.707A4.486 4.486 0 0 1 10.025 8M7 4a.5.5 0 0 0-.812-.39L3.825 5.5H1.5A.5.5 0 0 0 1 6v4a.5.5 0 0 0 .5.5h2.325l2.363 1.89A.5.5 0 0 0 7 12z"/></svg></span>
3597
- <span class="icon mute-icon hidden"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06m7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0"/></svg></span>
3598
- </button>
3675
+ <button class="control-btn play-pause-btn" data-tooltip="play_pause">
3676
+ <span class="icon play-icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></span>
3677
+ <span class="icon pause-icon hidden"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg></span>
3678
+ </button>
3599
3679
 
3600
- <div class="volume-container" data-mobile-slider="${this.options.volumeSlider}">
3601
- <input type="range" class="volume-slider" min="0" max="100" value="100" data-tooltip="volume">
3602
- </div>
3680
+ <button class="control-btn mute-btn" data-tooltip="mute_unmute">
3681
+ <span class="icon volume-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303z"/><path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89z"/><path d="M10.025 8a4.486 4.486 0 0 1-1.318 3.182L8 10.475A3.489 3.489 0 0 0 9.025 8c0-.966-.392-1.841-1.025-2.475l.707-.707A4.486 4.486 0 0 1 10.025 8M7 4a.5.5 0 0 0-.812-.39L3.825 5.5H1.5A.5.5 0 0 0 1 6v4a.5.5 0 0 0 .5.5h2.325l2.363 1.89A.5.5 0 0 0 7 12z"/></svg></span>
3682
+ <span class="icon mute-icon hidden"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06m7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0"/></svg></span>
3683
+ </button>
3603
3684
 
3604
- <div class="time-display">
3605
- <span class="current-time">0:00</span>
3606
- <span>/</span>
3607
- <span class="duration">0:00</span>
3608
- </div>
3609
- </div>
3685
+ <div class="volume-container" data-mobile-slider="${this.options.volumeSlider}">
3686
+ <input type="range" class="volume-slider" min="0" max="100" value="100" data-tooltip="volume">
3687
+ </div>
3688
+
3689
+ <div class="time-display">
3690
+ <span class="current-time">0:00</span>
3691
+ <span>/</span>
3692
+ <span class="duration">0:00</span>
3693
+ </div>
3694
+ </div>
3610
3695
 
3611
3696
  <div class="controls-right">
3612
3697
  <button class="control-btn playlist-prev-btn" data-tooltip="prevvideo" style="display: none;">
@@ -3623,7 +3708,7 @@ createControls() {
3623
3708
  <div class="settings-menu"></div>
3624
3709
  </div>
3625
3710
 
3626
- ${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
3711
+ ${(this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1) || this.options.adaptiveQualityControl ? `
3627
3712
  <div class="quality-control">
3628
3713
  <button class="control-btn quality-btn" data-tooltip="video_quality">
3629
3714
  <div class="quality-btn-text">
@@ -4494,6 +4579,11 @@ updateQualityDisplay() {
4494
4579
  }
4495
4580
 
4496
4581
  updateQualityButton() {
4582
+ if (this.isAdaptiveStream) {
4583
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality button managed by streaming.js');
4584
+ return;
4585
+ }
4586
+
4497
4587
  const qualityBtn = this.controls?.querySelector('.quality-btn');
4498
4588
  if (!qualityBtn) return;
4499
4589
 
@@ -4545,6 +4635,11 @@ updateQualityMenu() {
4545
4635
  const qualityMenu = this.controls?.querySelector('.quality-menu');
4546
4636
  if (!qualityMenu) return;
4547
4637
 
4638
+ if (this.isAdaptiveStream) {
4639
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality menu managed by streaming.js');
4640
+ return;
4641
+ }
4642
+
4548
4643
  let menuHTML = '';
4549
4644
 
4550
4645
  // Check if adaptive streaming is active (HLS/DASH)
@@ -7220,6 +7315,16 @@ async loadAdaptiveLibraries() {
7220
7315
  }
7221
7316
 
7222
7317
  try {
7318
+ // Initialize quality selection to Auto BEFORE creating player
7319
+
7320
+ // FORCE Auto mode - always reset at initialization
7321
+ this.selectedQuality = 'auto';
7322
+ this.qualityEventsInitialized = false;
7323
+
7324
+ if (this.options.debug) {
7325
+ console.log('🔍 initializeDash - FORCED selectedQuality to:', this.selectedQuality);
7326
+ }
7327
+
7223
7328
  // Destroy existing DASH player
7224
7329
  if (this.dashPlayer) {
7225
7330
  this.dashPlayer.destroy();
@@ -7384,35 +7489,464 @@ disableDashTextTracks() {
7384
7489
  }
7385
7490
  }
7386
7491
 
7387
- updateAdaptiveQualities() {
7388
- this.adaptiveQualities = [];
7492
+ updateAdaptiveQualities() {
7493
+ this.adaptiveQualities = [];
7494
+
7495
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7496
+ try {
7497
+ // dash.js 5.x - Get ALL video tracks
7498
+ const videoTracks = this.dashPlayer.getTracksFor('video');
7499
+
7500
+ if (this.options.debug) {
7501
+ console.log('✅ DASH getTracksFor result:', videoTracks);
7502
+ }
7503
+
7504
+ if (videoTracks && videoTracks.length > 0) {
7505
+ // Collect qualities from ALL video tracks
7506
+ const allQualities = [];
7389
7507
 
7508
+ videoTracks.forEach((track, trackIndex) => {
7509
+ const bitrateList = track.bitrateList || [];
7510
+
7511
+ if (this.options.debug) {
7512
+ console.log(`✅ Track ${trackIndex} (${track.codec}):`, bitrateList);
7513
+ }
7514
+
7515
+ bitrateList.forEach((bitrate, index) => {
7516
+ allQualities.push({
7517
+ trackIndex: trackIndex,
7518
+ bitrateIndex: index,
7519
+ label: `${bitrate.height}p`,
7520
+ height: bitrate.height,
7521
+ width: bitrate.width,
7522
+ bandwidth: bitrate.bandwidth,
7523
+ codec: track.codec
7524
+ });
7525
+ });
7526
+ });
7527
+
7528
+ // Sort by height (descending) and remove duplicates
7529
+ const uniqueHeights = [...new Set(allQualities.map(q => q.height))];
7530
+ uniqueHeights.sort((a, b) => b - a);
7531
+
7532
+ this.adaptiveQualities = uniqueHeights.map((height, index) => {
7533
+ const quality = allQualities.find(q => q.height === height);
7534
+ return {
7535
+ index: index,
7536
+ label: `${height}p`,
7537
+ height: height,
7538
+ trackIndex: quality.trackIndex,
7539
+ bitrateIndex: quality.bitrateIndex,
7540
+ bandwidth: quality.bandwidth,
7541
+ codec: quality.codec
7542
+ };
7543
+ });
7544
+
7545
+ if (this.options.debug) {
7546
+ console.log('✅ All DASH qualities merged:', this.adaptiveQualities);
7547
+ }
7548
+ }
7549
+ } catch (error) {
7550
+ if (this.options.debug) {
7551
+ console.error('❌ Error getting DASH qualities:', error);
7552
+ }
7553
+ }
7554
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7555
+ const levels = this.hlsPlayer.levels;
7556
+ this.adaptiveQualities = levels.map((level, index) => ({
7557
+ index: index,
7558
+ label: this.getQualityLabel(level.height, level.width),
7559
+ height: level.height,
7560
+ bandwidth: level.bitrate
7561
+ }));
7562
+ }
7563
+
7564
+ if (this.options.adaptiveQualityControl) {
7565
+ this.updateAdaptiveQualityMenu();
7566
+ }
7567
+
7568
+ if (this.options.debug) {
7569
+ console.log('📡 Adaptive qualities available:', this.adaptiveQualities);
7570
+ console.log('📡 Selected quality mode:', this.selectedQuality);
7571
+ }
7572
+ }
7573
+
7574
+ updateAdaptiveQualityMenu() {
7575
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7576
+ if (!qualityMenu) {
7577
+ if (this.options.debug) console.log('❌ Quality menu not found in DOM');
7578
+ return;
7579
+ }
7580
+
7581
+ if (this.adaptiveQualities.length === 0) {
7582
+ if (this.options.debug) console.log('❌ No adaptive qualities to display');
7583
+ return;
7584
+ }
7585
+
7586
+ // Generate menu HTML with "Auto" option
7587
+ const isAutoActive = this.selectedQuality === 'auto';
7588
+ let menuHTML = `<div class="quality-option ${isAutoActive ? 'active' : ''}" data-quality="auto">Auto</div>`;
7589
+
7590
+ // Add all quality options
7591
+ this.adaptiveQualities.forEach((quality) => {
7592
+ const isActive = this.selectedQuality === quality.height;
7593
+
7594
+ if (this.options.debug) {
7595
+ console.log('🔍 Quality item:', quality.label, 'height:', quality.height, 'active:', isActive);
7596
+ }
7597
+
7598
+ menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-quality="${quality.height}">
7599
+ ${quality.label}
7600
+ <span class="quality-playing" style="display: none; color: #4CAF50; margin-left: 8px; font-size: 0.85em;">● Playing</span>
7601
+ </div>`;
7602
+ });
7603
+
7604
+ qualityMenu.innerHTML = menuHTML;
7605
+
7606
+ if (this.options.debug) {
7607
+ console.log('✅ Quality menu populated with', this.adaptiveQualities.length, 'options');
7608
+ }
7609
+
7610
+ // Bind events ONCE
7611
+ if (!this.qualityEventsInitialized) {
7612
+ this.bindAdaptiveQualityEvents();
7613
+ this.qualityEventsInitialized = true;
7614
+ }
7615
+
7616
+ // Update display
7617
+ this.updateAdaptiveQualityDisplay();
7618
+ }
7619
+
7620
+ updateAdaptiveQualityDisplay() {
7621
+ if (!this.dashPlayer && !this.hlsPlayer) return;
7622
+
7623
+ let currentHeight = null;
7624
+
7625
+ try {
7390
7626
  if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7391
- const bitrates = this.dashPlayer.getBitrateInfoListFor('video');
7392
- this.adaptiveQualities = bitrates.map((bitrate, index) => ({
7393
- index: index,
7394
- label: this.getQualityLabel(bitrate.height, bitrate.width),
7395
- height: bitrate.height,
7396
- bandwidth: bitrate.bandwidth
7397
- }));
7627
+ // Get video element to check actual resolution
7628
+ if (this.video && this.video.videoHeight) {
7629
+ currentHeight = this.video.videoHeight;
7630
+ }
7631
+
7632
+ if (this.options.debug) {
7633
+ console.log('📊 Current video height:', currentHeight, 'Selected mode:', this.selectedQuality);
7634
+ }
7398
7635
  } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7399
- const levels = this.hlsPlayer.levels;
7400
- this.adaptiveQualities = levels.map((level, index) => ({
7401
- index: index,
7402
- label: this.getQualityLabel(level.height, level.width),
7403
- height: level.height,
7404
- bandwidth: level.bitrate
7405
- }));
7636
+ const currentLevel = this.hlsPlayer.currentLevel;
7637
+ if (currentLevel >= 0 && this.hlsPlayer.levels[currentLevel]) {
7638
+ currentHeight = this.hlsPlayer.levels[currentLevel].height;
7639
+ }
7640
+ }
7641
+
7642
+ // Update button text (top text)
7643
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7644
+ if (qualityBtnText) {
7645
+ if (this.selectedQuality === 'auto') {
7646
+ qualityBtnText.textContent = 'Auto';
7647
+ } else {
7648
+ qualityBtnText.textContent = `${this.selectedQuality}p`;
7649
+ }
7650
+ }
7651
+
7652
+ // Update current quality display (bottom text) - ONLY in Auto mode
7653
+ const currentQualityText = this.controls?.querySelector('.quality-btn .current-quality');
7654
+ if (currentQualityText) {
7655
+ if (this.selectedQuality === 'auto' && currentHeight) {
7656
+ currentQualityText.textContent = `${currentHeight}p`;
7657
+ currentQualityText.style.display = 'block';
7658
+ } else {
7659
+ currentQualityText.textContent = '';
7660
+ currentQualityText.style.display = 'none';
7661
+ }
7662
+ }
7663
+
7664
+ // Update menu active states
7665
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7666
+ if (qualityMenu) {
7667
+ // Remove all active states
7668
+ qualityMenu.querySelectorAll('.quality-option').forEach(opt => {
7669
+ opt.classList.remove('active');
7670
+ });
7671
+
7672
+ // Set active based on selection
7673
+ if (this.selectedQuality === 'auto') {
7674
+ const autoOption = qualityMenu.querySelector('[data-quality="auto"]');
7675
+ if (autoOption) autoOption.classList.add('active');
7676
+ } else {
7677
+ const selectedOption = qualityMenu.querySelector(`[data-quality="${this.selectedQuality}"]`);
7678
+ if (selectedOption) selectedOption.classList.add('active');
7679
+ }
7680
+
7681
+ // Hide all playing indicators
7682
+ qualityMenu.querySelectorAll('.quality-playing').forEach(el => {
7683
+ el.style.display = 'none';
7684
+ });
7685
+
7686
+ // Show playing indicator only in Auto mode
7687
+ if (this.selectedQuality === 'auto' && currentHeight) {
7688
+ const playingOption = qualityMenu.querySelector(`[data-quality="${currentHeight}"] .quality-playing`);
7689
+ if (playingOption) {
7690
+ playingOption.style.display = 'inline';
7691
+ }
7692
+ }
7693
+ }
7694
+
7695
+ } catch (error) {
7696
+ if (this.options.debug) console.error('❌ Error updating quality display:', error);
7697
+ }
7698
+ }
7699
+
7700
+ updateQualityButtonText() {
7701
+ const qualityBtn = this.controls?.querySelector('.quality-btn .selected-quality');
7702
+ if (!qualityBtn) return;
7703
+
7704
+ if (this.selectedQuality === 'auto' || !this.selectedQuality) {
7705
+ qualityBtn.textContent = this.t('auto');
7706
+ } else {
7707
+ const quality = this.adaptiveQualities.find(q => q.index === parseInt(this.selectedQuality));
7708
+ qualityBtn.textContent = quality ? quality.label : 'Auto';
7709
+ }
7710
+ }
7711
+
7712
+ bindAdaptiveQualityEvents() {
7713
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7714
+ const qualityBtn = this.controls?.querySelector('.quality-btn');
7715
+
7716
+ if (!qualityMenu || !qualityBtn) return;
7717
+
7718
+ // Toggle menu
7719
+ qualityBtn.addEventListener('click', (e) => {
7720
+ e.stopPropagation();
7721
+ qualityMenu.classList.toggle('active');
7722
+
7723
+ // Update display when opening
7724
+ if (qualityMenu.classList.contains('active')) {
7725
+ this.updateAdaptiveQualityDisplay();
7406
7726
  }
7727
+ });
7407
7728
 
7408
- if (this.options.adaptiveQualityControl) {
7409
- this.updateAdaptiveQualityMenu();
7729
+ // Close menu on outside click
7730
+ const closeMenuHandler = (e) => {
7731
+ if (!qualityBtn.contains(e.target) && !qualityMenu.contains(e.target)) {
7732
+ qualityMenu.classList.remove('active');
7410
7733
  }
7734
+ };
7735
+ document.addEventListener('click', closeMenuHandler);
7736
+
7737
+ // Handle quality selection
7738
+ qualityMenu.addEventListener('click', (e) => {
7739
+ const option = e.target.closest('.quality-option');
7740
+ if (!option) return;
7741
+
7742
+ e.stopPropagation();
7743
+
7744
+ const qualityData = option.getAttribute('data-quality');
7411
7745
 
7412
7746
  if (this.options.debug) {
7413
- console.log('📡 Adaptive qualities available:', this.adaptiveQualities);
7747
+ console.log('🎬 Quality clicked - raw data:', qualityData, 'type:', typeof qualityData);
7414
7748
  }
7749
+
7750
+ if (qualityData === 'auto') {
7751
+ // Enable auto mode
7752
+ this.selectedQuality = 'auto';
7753
+
7754
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7755
+ this.dashPlayer.updateSettings({
7756
+ streaming: {
7757
+ abr: {
7758
+ autoSwitchBitrate: { video: true }
7759
+ }
7760
+ }
7761
+ });
7762
+ if (this.options.debug) console.log('✅ Auto quality enabled');
7763
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7764
+ this.hlsPlayer.currentLevel = -1;
7765
+ }
7766
+
7767
+ } else {
7768
+ // Manual quality selection
7769
+ const selectedHeight = parseInt(qualityData, 10);
7770
+
7771
+ if (isNaN(selectedHeight)) {
7772
+ if (this.options.debug) console.error('❌ Invalid quality data:', qualityData);
7773
+ return;
7774
+ }
7775
+
7776
+ if (this.options.debug) {
7777
+ console.log('🎬 Setting manual quality to height:', selectedHeight);
7778
+ }
7779
+
7780
+ this.selectedQuality = selectedHeight;
7781
+
7782
+ if (this.adaptiveStreamingType === 'dash') {
7783
+ this.setDashQualityByHeight(selectedHeight);
7784
+ } else if (this.adaptiveStreamingType === 'hls') {
7785
+ const levelIndex = this.hlsPlayer.levels.findIndex(l => l.height === selectedHeight);
7786
+ if (levelIndex >= 0) {
7787
+ this.hlsPlayer.currentLevel = levelIndex;
7788
+ }
7789
+ }
7790
+ }
7791
+
7792
+ // Update display immediately
7793
+ this.updateAdaptiveQualityDisplay();
7794
+
7795
+ // Close menu
7796
+ qualityMenu.classList.remove('active');
7797
+ });
7798
+
7799
+ if (this.options.debug) {
7800
+ console.log('✅ Quality events bound');
7415
7801
  }
7802
+ }
7803
+
7804
+ setDashQualityByHeight(targetHeight) {
7805
+ if (!this.dashPlayer) return;
7806
+
7807
+ try {
7808
+ const targetQuality = this.adaptiveQualities.find(q => q.height === targetHeight);
7809
+ if (!targetQuality) {
7810
+ if (this.options.debug) console.error('❌ Quality not found for height:', targetHeight);
7811
+ return;
7812
+ }
7813
+
7814
+ if (this.options.debug) {
7815
+ console.log('🎬 Setting quality:', targetQuality);
7816
+ }
7817
+
7818
+ // Disable auto quality
7819
+ this.dashPlayer.updateSettings({
7820
+ streaming: {
7821
+ abr: {
7822
+ autoSwitchBitrate: { video: false }
7823
+ }
7824
+ }
7825
+ });
7826
+
7827
+ // Get current video track
7828
+ const currentTrack = this.dashPlayer.getCurrentTrackFor('video');
7829
+
7830
+ if (!currentTrack) {
7831
+ if (this.options.debug) console.error('❌ No current video track');
7832
+ return;
7833
+ }
7834
+
7835
+ // Find the correct track for this quality
7836
+ const allTracks = this.dashPlayer.getTracksFor('video');
7837
+ let targetTrack = null;
7838
+
7839
+ for (const track of allTracks) {
7840
+ if (track.bitrateList && track.bitrateList[targetQuality.bitrateIndex]) {
7841
+ const bitrate = track.bitrateList[targetQuality.bitrateIndex];
7842
+ if (bitrate.height === targetHeight) {
7843
+ targetTrack = track;
7844
+ break;
7845
+ }
7846
+ }
7847
+ }
7848
+
7849
+ if (!targetTrack) {
7850
+ if (this.options.debug) console.error('❌ Target track not found');
7851
+ return;
7852
+ }
7853
+
7854
+ // Switch track if different
7855
+ if (currentTrack.index !== targetTrack.index) {
7856
+ this.dashPlayer.setCurrentTrack(targetTrack);
7857
+ if (this.options.debug) {
7858
+ console.log('✅ Switched to track:', targetTrack.index);
7859
+ }
7860
+ }
7861
+
7862
+ // Force quality on current track
7863
+ setTimeout(() => {
7864
+ try {
7865
+ // Use the MediaPlayer API to set quality
7866
+ this.dashPlayer.updateSettings({
7867
+ streaming: {
7868
+ abr: {
7869
+ initialBitrate: { video: targetQuality.bandwidth / 1000 },
7870
+ maxBitrate: { video: targetQuality.bandwidth / 1000 },
7871
+ minBitrate: { video: targetQuality.bandwidth / 1000 }
7872
+ }
7873
+ }
7874
+ });
7875
+
7876
+ if (this.options.debug) {
7877
+ console.log('✅ Quality locked to:', targetHeight + 'p', 'bandwidth:', targetQuality.bandwidth);
7878
+ }
7879
+
7880
+ // Update button text immediately
7881
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7882
+ if (qualityBtnText) {
7883
+ qualityBtnText.textContent = `${targetHeight}p`;
7884
+ }
7885
+
7886
+ // Force reload of segments at new quality
7887
+ const currentTime = this.video.currentTime;
7888
+ this.dashPlayer.seek(currentTime + 0.1);
7889
+ setTimeout(() => {
7890
+ this.dashPlayer.seek(currentTime);
7891
+ }, 100);
7892
+
7893
+ } catch (innerError) {
7894
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
7895
+ }
7896
+ }, 100);
7897
+
7898
+ } catch (error) {
7899
+ if (this.options.debug) console.error('❌ Error in setDashQualityByHeight:', error);
7900
+ }
7901
+ }
7902
+
7903
+ setDashQuality(qualityIndex) {
7904
+ if (!this.dashPlayer) return;
7905
+
7906
+ try {
7907
+ const selectedQuality = this.adaptiveQualities[qualityIndex];
7908
+ if (!selectedQuality) {
7909
+ if (this.options.debug) console.error('❌ Quality not found at index:', qualityIndex);
7910
+ return;
7911
+ }
7912
+
7913
+ if (this.options.debug) {
7914
+ console.log('🎬 Setting DASH quality:', selectedQuality);
7915
+ }
7916
+
7917
+ // Disable auto quality
7918
+ this.dashPlayer.updateSettings({
7919
+ streaming: {
7920
+ abr: {
7921
+ autoSwitchBitrate: { video: false }
7922
+ }
7923
+ }
7924
+ });
7925
+
7926
+ // Set the specific quality using bitrateIndex
7927
+ setTimeout(() => {
7928
+ try {
7929
+ this.dashPlayer.setQualityFor('video', selectedQuality.bitrateIndex);
7930
+
7931
+ if (this.options.debug) {
7932
+ console.log('✅ DASH quality set to bitrateIndex:', selectedQuality.bitrateIndex, 'height:', selectedQuality.height);
7933
+ }
7934
+
7935
+ // Update button text immediately
7936
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7937
+ if (qualityBtnText) {
7938
+ qualityBtnText.textContent = selectedQuality.label;
7939
+ }
7940
+
7941
+ } catch (innerError) {
7942
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
7943
+ }
7944
+ }, 100);
7945
+
7946
+ } catch (error) {
7947
+ if (this.options.debug) console.error('❌ Error in setDashQuality:', error);
7948
+ }
7949
+ }
7416
7950
 
7417
7951
  handleAdaptiveError(data) {
7418
7952
  if (this.options.debug) console.error('📡 Fatal adaptive streaming error:', data);