myetv-player 1.3.0 → 1.5.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(),
@@ -1039,6 +1120,10 @@ markPlayerReady() {
1039
1120
  this.video.style.pointerEvents = '';
1040
1121
  }
1041
1122
 
1123
+ if (typeof this.updateSettingsMenuVisibility === 'function') {
1124
+ this.updateSettingsMenuVisibility();
1125
+ }
1126
+
1042
1127
  setTimeout(() => {
1043
1128
  if (this.options.autoHide && !this.autoHideInitialized) {
1044
1129
  this.initAutoHide();
@@ -1087,6 +1172,7 @@ createPlayerStructure() {
1087
1172
 
1088
1173
  this.container = wrapper;
1089
1174
 
1175
+ this.optimizeVideoForFireTV();
1090
1176
  this.createInitialLoading();
1091
1177
  this.createLoadingOverlay();
1092
1178
  this.collectVideoQualities();
@@ -1127,13 +1213,13 @@ createTitleOverlay() {
1127
1213
 
1128
1214
  const titleText = document.createElement('h2');
1129
1215
  titleText.className = 'title-text';
1130
- titleText.textContent = this.options.videoTitle || '';
1216
+ titleText.textContent = this.decodeHTMLEntities(this.options.videoTitle) || '';
1131
1217
  overlay.appendChild(titleText);
1132
1218
 
1133
1219
  if (this.options.videoSubtitle) {
1134
1220
  const subtitleText = document.createElement('p');
1135
1221
  subtitleText.className = 'subtitle-text';
1136
- subtitleText.textContent = this.options.videoSubtitle;
1222
+ subtitleText.textContent = this.decodeHTMLEntities(this.options.videoSubtitle);
1137
1223
  overlay.appendChild(subtitleText);
1138
1224
  }
1139
1225
 
@@ -1188,7 +1274,7 @@ setVideoTitle(title) {
1188
1274
  if (this.titleOverlay) {
1189
1275
  const titleElement = this.titleOverlay.querySelector('.title-text');
1190
1276
  if (titleElement) {
1191
- titleElement.textContent = this.options.videoTitle;
1277
+ titleElement.textContent = this.decodeHTMLEntities(this.options.videoTitle);
1192
1278
  }
1193
1279
 
1194
1280
  if (title) {
@@ -1723,6 +1809,8 @@ updateDuration() {
1723
1809
  if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
1724
1810
  this.durationEl.textContent = this.formatTime(this.video.duration);
1725
1811
  }
1812
+
1813
+ this.updateTimeDisplay();
1726
1814
  }
1727
1815
 
1728
1816
  changeSpeed(e) {
@@ -1988,7 +2076,7 @@ switchToVideo(newVideoElement, shouldPlay = false) {
1988
2076
  if (newTitle && this.options.showTitleOverlay) {
1989
2077
  this.options.videoTitle = newTitle;
1990
2078
  if (this.titleText) {
1991
- this.titleText.textContent = newTitle;
2079
+ this.titleText.textContent = this.decodeHTMLEntities(newTitle);
1992
2080
  }
1993
2081
  }
1994
2082
 
@@ -2478,12 +2566,16 @@ addEventListener(eventType, callback) {
2478
2566
  this.showLoading();
2479
2567
  }
2480
2568
 
2569
+ this.updateTimeDisplay();
2570
+
2481
2571
  this.triggerEvent('loadstart');
2482
2572
  });
2483
2573
 
2484
2574
  this.video.addEventListener('loadedmetadata', () => {
2485
2575
  this.updateDuration();
2486
2576
 
2577
+ this.updateTimeDisplay();
2578
+
2487
2579
  this.triggerEvent('loadedmetadata', {
2488
2580
  duration: this.getDuration(),
2489
2581
  videoWidth: this.video.videoWidth,
@@ -2500,6 +2592,8 @@ addEventListener(eventType, callback) {
2500
2592
  this.hideLoading();
2501
2593
  }
2502
2594
 
2595
+ this.updateTimeDisplay();
2596
+
2503
2597
  this.triggerEvent('loadeddata', {
2504
2598
  currentTime: this.getCurrentTime()
2505
2599
  });
@@ -2510,12 +2604,26 @@ addEventListener(eventType, callback) {
2510
2604
  this.hideLoading();
2511
2605
  }
2512
2606
 
2607
+ this.updateTimeDisplay();
2608
+
2513
2609
  this.triggerEvent('canplay', {
2514
2610
  currentTime: this.getCurrentTime(),
2515
2611
  duration: this.getDuration()
2516
2612
  });
2517
2613
  });
2518
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
+
2519
2627
  this.video.addEventListener('progress', () => {
2520
2628
  this.updateBuffer();
2521
2629
 
@@ -3208,30 +3316,12 @@ createControls() {
3208
3316
  <span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M12.5 4v8l-7-4zm-8 0v8l7-4z"/></svg></span>
3209
3317
  </button>
3210
3318
 
3211
- ${this.options.showSubtitles ? `
3212
- <div class="subtitles-control" style="display: none;">
3213
- <button class="control-btn subtitles-btn" data-tooltip="subtitles">
3214
- <span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1z"/><path d="M6.096 4.972c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152m4.915 0c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152M6.096 9.972c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152m4.915 0c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152"/></svg></span>
3319
+ <div class="settings-control">
3320
+ <button class="control-btn settings-btn" data-tooltip="settings_menu">
3321
+ <span class="icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/></svg></span>
3215
3322
  </button>
3216
- <div class="subtitles-menu">
3217
- <div class="subtitles-option active" data-track="off">Off</div>
3218
- </div>
3219
- </div>
3220
- ` : ''}
3221
-
3222
- ${this.options.showSpeedControl ? `
3223
- <div class="speed-control">
3224
- <button class="control-btn speed-btn" data-tooltip="playback_speed">1x</button>
3225
- <div class="speed-menu">
3226
- <div class="speed-option" data-speed="0.5">0.5x</div>
3227
- <div class="speed-option" data-speed="0.75">0.75x</div>
3228
- <div class="speed-option active" data-speed="1">1x</div>
3229
- <div class="speed-option" data-speed="1.25">1.25x</div>
3230
- <div class="speed-option" data-speed="1.5">1.5x</div>
3231
- <div class="speed-option" data-speed="2">2x</div>
3232
- </div>
3323
+ <div class="settings-menu"></div>
3233
3324
  </div>
3234
- ` : ''}
3235
3325
 
3236
3326
  ${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
3237
3327
  <div class="quality-control">
@@ -3257,13 +3347,6 @@ createControls() {
3257
3347
  </button>
3258
3348
  ` : ''}
3259
3349
 
3260
- <div class="settings-control">
3261
- <button class="control-btn settings-btn" data-tooltip="settings_menu">
3262
- <span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52z"/></svg></span>
3263
- </button>
3264
- <div class="settings-menu"></div>
3265
- </div>
3266
-
3267
3350
  ${this.options.showFullscreen ? `
3268
3351
  <button class="control-btn fullscreen-btn" data-tooltip="fullscreen">
3269
3352
  <span class="icon fullscreen-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/></svg></span>
@@ -3353,37 +3436,14 @@ updateSettingsMenuVisibility() {
3353
3436
  const settingsControl = this.controls?.querySelector('.settings-control');
3354
3437
  if (!settingsControl) return;
3355
3438
 
3356
- if (this.isSmallScreen) {
3357
-
3358
3439
  settingsControl.style.display = 'block';
3359
3440
 
3360
- const pipBtn = this.controls.querySelector('.pip-btn');
3361
- const speedControl = this.controls.querySelector('.speed-control');
3362
- const subtitlesControl = this.controls.querySelector('.subtitles-control');
3363
-
3364
- if (pipBtn) pipBtn.style.display = 'none';
3365
- if (speedControl) speedControl.style.display = 'none';
3366
- if (subtitlesControl) subtitlesControl.style.display = 'none';
3367
-
3368
3441
  this.populateSettingsMenu();
3369
- } else {
3370
3442
 
3371
- settingsControl.style.display = 'none';
3372
-
3373
- const pipBtn = this.controls.querySelector('.pip-btn');
3374
3443
  const speedControl = this.controls.querySelector('.speed-control');
3375
3444
  const subtitlesControl = this.controls.querySelector('.subtitles-control');
3376
-
3377
- if (pipBtn && this.options.showPictureInPicture && this.isPiPSupported) {
3378
- pipBtn.style.display = 'flex';
3379
- }
3380
- if (speedControl && this.options.showSpeedControl) {
3381
- speedControl.style.display = 'block';
3382
- }
3383
- if (subtitlesControl && this.options.showSubtitles && this.textTracks.length > 0) {
3384
- subtitlesControl.style.display = 'block';
3385
- }
3386
- }
3445
+ if (speedControl) speedControl.style.display = 'none';
3446
+ if (subtitlesControl) subtitlesControl.style.display = 'none';
3387
3447
  }
3388
3448
 
3389
3449
  populateSettingsMenu() {
@@ -3392,22 +3452,13 @@ populateSettingsMenu() {
3392
3452
 
3393
3453
  let menuHTML = '';
3394
3454
 
3395
- if (this.options.showPictureInPicture && this.isPiPSupported) {
3396
- const pipLabel = this.t('picture_in_picture') || 'Picture-in-Picture';
3397
- menuHTML += `<div class="settings-option" data-action="pip">
3398
- <span class="settings-option-label">${pipLabel}</span>
3399
- </div>`;
3400
- }
3401
-
3402
3455
  if (this.options.showSpeedControl) {
3403
3456
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
3404
3457
  const currentSpeed = this.video ? this.video.playbackRate : 1;
3405
-
3406
- menuHTML += `
3407
- <div class="settings-expandable-wrapper">
3458
+ menuHTML += `<div class="settings-expandable-wrapper">
3408
3459
  <div class="settings-option expandable-trigger" data-action="speed-expand">
3409
- <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
3410
- <span class="expand-arrow">▼</span>
3460
+ <span class="settings-option-label">${speedLabel} <strong>${currentSpeed}x</strong></span>
3461
+ <span class="expand-arrow">▶</span>
3411
3462
  </div>
3412
3463
  <div class="settings-expandable-content" style="display: none;">`;
3413
3464
 
@@ -3416,20 +3467,18 @@ populateSettingsMenu() {
3416
3467
  const isActive = Math.abs(speed - currentSpeed) < 0.01;
3417
3468
  menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
3418
3469
  });
3419
-
3420
3470
  menuHTML += `</div></div>`;
3421
3471
  }
3422
3472
 
3423
3473
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
3424
3474
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3425
3475
  const currentTrack = this.currentSubtitleTrack;
3426
- const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
3476
+ const currentLabel = this.subtitlesEnabled ? (currentTrack ? currentTrack.label : 'Unknown') : (this.t('subtitlesoff') || 'Off');
3427
3477
 
3428
- menuHTML += `
3429
- <div class="settings-expandable-wrapper">
3478
+ menuHTML += `<div class="settings-expandable-wrapper">
3430
3479
  <div class="settings-option expandable-trigger" data-action="subtitles-expand">
3431
- <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
3432
- <span class="expand-arrow">▼</span>
3480
+ <span class="settings-option-label">${subtitlesLabel} <strong>${currentLabel}</strong></span>
3481
+ <span class="expand-arrow">▶</span>
3433
3482
  </div>
3434
3483
  <div class="settings-expandable-content" style="display: none;">`;
3435
3484
 
@@ -3444,20 +3493,30 @@ populateSettingsMenu() {
3444
3493
  }
3445
3494
 
3446
3495
  settingsMenu.innerHTML = menuHTML;
3447
-
3448
- this.addSettingsMenuScrollbar();
3449
3496
  }
3450
3497
 
3451
3498
  addSettingsMenuScrollbar() {
3452
3499
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3453
3500
  if (!settingsMenu) return;
3454
3501
 
3455
- const playerHeight = this.container.offsetHeight;
3456
- const maxMenuHeight = playerHeight - 100;
3502
+ const settingsBtn = document.querySelector('.settings-btn');
3503
+ if (!settingsBtn) return;
3457
3504
 
3505
+ const updateMenuHeight = () => {
3506
+ if (settingsMenu.classList.contains('active')) {
3507
+ const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
3508
+ const btnRect = settingsBtn.getBoundingClientRect();
3509
+ const spaceBelow = containerRect.bottom - btnRect.bottom;
3510
+ const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
3458
3511
  settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3459
3512
  settingsMenu.style.overflowY = 'auto';
3460
3513
  settingsMenu.style.overflowX = 'hidden';
3514
+ }
3515
+ };
3516
+
3517
+ updateMenuHeight();
3518
+
3519
+ window.addEventListener('resize', updateMenuHeight);
3461
3520
 
3462
3521
  if (!document.getElementById('player-settings-scrollbar-style')) {
3463
3522
  const scrollbarStyle = document.createElement('style');
@@ -3486,8 +3545,38 @@ addSettingsMenuScrollbar() {
3486
3545
  }
3487
3546
 
3488
3547
  bindSettingsMenuEvents() {
3548
+ const settingsBtn = this.controls?.querySelector('.settings-btn');
3489
3549
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3490
- if (!settingsMenu) return;
3550
+ if (!settingsMenu || !settingsBtn) return;
3551
+
3552
+ settingsBtn.addEventListener('click', (e) => {
3553
+ e.stopPropagation();
3554
+ settingsMenu.classList.toggle('active');
3555
+
3556
+ if (settingsMenu.classList.contains('active')) {
3557
+ const settingsBtn = document.querySelector('.settings-btn');
3558
+ const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
3559
+ const btnRect = settingsBtn.getBoundingClientRect();
3560
+ const spaceBelow = containerRect.bottom - btnRect.bottom;
3561
+ const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
3562
+
3563
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3564
+ settingsMenu.style.overflowY = 'auto';
3565
+ settingsMenu.style.overflowX = 'hidden';
3566
+ } else {
3567
+ settingsMenu.style.maxHeight = 'none';
3568
+ settingsMenu.style.overflowY = 'visible';
3569
+ }
3570
+
3571
+ });
3572
+
3573
+ document.addEventListener('click', (e) => {
3574
+ if (!settingsBtn?.contains(e.target) && !settingsMenu?.contains(e.target)) {
3575
+ settingsMenu?.classList.remove('active');
3576
+ settingsMenu.style.maxHeight = 'none';
3577
+ settingsMenu.style.overflowY = 'visible';
3578
+ }
3579
+ });
3491
3580
 
3492
3581
  settingsMenu.addEventListener('click', (e) => {
3493
3582
  e.stopPropagation();
@@ -6916,20 +7005,20 @@ getBufferedTime() {
6916
7005
  } catch (error) {
6917
7006
  return 0;
6918
7007
  }
6919
- }
7008
+ }
6920
7009
 
6921
- clearTitleTimeout() {
7010
+ clearTitleTimeout() {
6922
7011
  if (this.titleTimeout) {
6923
7012
  clearTimeout(this.titleTimeout);
6924
7013
  this.titleTimeout = null;
6925
7014
  }
6926
- }
7015
+ }
6927
7016
 
6928
- skipTime(seconds) {
7017
+ skipTime(seconds) {
6929
7018
  if (!this.video || !this.video.duration || this.isChangingQuality) return;
6930
7019
 
6931
7020
  this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
6932
- }
7021
+ }
6933
7022
 
6934
7023
  updateTimeDisplay() {
6935
7024
 
@@ -6939,20 +7028,34 @@ updateTimeDisplay() {
6939
7028
 
6940
7029
  if (this.durationEl && this.video) {
6941
7030
  const duration = this.video.duration;
7031
+ const readyState = this.video.readyState;
7032
+ const currentTime = this.video.currentTime;
7033
+ const networkState = this.video.networkState;
6942
7034
 
6943
- if (!duration || isNaN(duration) || !isFinite(duration)) {
7035
+ const isInitialBuffering = (readyState < 2 && currentTime === 0) ||
7036
+ (currentTime === 0 && (!duration || duration === 0) && networkState === 2);
6944
7037
 
6945
- this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
7038
+ const isDurationInvalid = !duration || isNaN(duration) || !isFinite(duration);
7039
+
7040
+ if (isInitialBuffering) {
7041
+
7042
+ this.durationEl.textContent = t('loading');
7043
+ this.durationEl.classList.remove('encoding-state');
7044
+ this.durationEl.classList.add('loading-state');
7045
+ } else if (isDurationInvalid) {
7046
+
7047
+ this.durationEl.textContent = t('encoding_in_progress');
7048
+ this.durationEl.classList.remove('loading-state');
6946
7049
  this.durationEl.classList.add('encoding-state');
6947
7050
  } else {
6948
7051
 
6949
7052
  this.durationEl.textContent = this.formatTime(duration);
6950
- this.durationEl.classList.remove('encoding-state');
7053
+ this.durationEl.classList.remove('encoding-state', 'loading-state');
6951
7054
  }
6952
7055
  }
6953
7056
  }
6954
7057
 
6955
- formatTime(seconds) {
7058
+ formatTime(seconds) {
6956
7059
  if (isNaN(seconds) || seconds < 0) return '0:00';
6957
7060
 
6958
7061
  const hours = Math.floor(seconds / 3600);
@@ -6963,7 +7066,7 @@ updateTimeDisplay() {
6963
7066
  return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
6964
7067
  }
6965
7068
  return `${minutes}:${secs.toString().padStart(2, '0')}`;
6966
- }
7069
+ }
6967
7070
 
6968
7071
  }
6969
7072
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myetv-player",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "MYETV Video Player - Modular HTML5 video player with plugin support for YouTube, Vimeo, Twitch, Facebook, and streaming protocols (HLS/DASH)",
5
5
  "main": "dist/myetv-player.js",
6
6
  "files": [
@@ -58,3 +58,5 @@
58
58
  "homepage": "https://oskarcosimo.com/myetv-video-player/myetv-player-demo.html"
59
59
  }
60
60
 
61
+
62
+