myetv-player 1.4.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.
@@ -25,7 +25,9 @@ class VideoPlayerI18n {
25
25
  'prev_video': 'Video precedente (P)',
26
26
  'playlist_next': 'Avanti',
27
27
  'playlist_prev': 'Indietro',
28
- 'settings_menu': 'Impostazioni'
28
+ 'settings_menu': 'Impostazioni',
29
+ 'loading': 'Caricamento...',
30
+ 'encoding_in_progress': 'Encoding in corso'
29
31
  },
30
32
 
31
33
  // English
@@ -47,7 +49,9 @@ class VideoPlayerI18n {
47
49
  'prev_video': 'Previous video (P)',
48
50
  'playlist_next': 'Next',
49
51
  'playlist_prev': 'Previous',
50
- 'settings_menu': 'Settings'
52
+ 'settings_menu': 'Settings',
53
+ 'loading': 'Loading...',
54
+ 'encoding_in_progress': 'Encoding in progress'
51
55
  },
52
56
 
53
57
  // Español
@@ -69,7 +73,9 @@ class VideoPlayerI18n {
69
73
  'prev_video': 'Vídeo anterior (P)',
70
74
  'playlist_next': 'Siguiente',
71
75
  'playlist_prev': 'Anterior',
72
- 'settings_menu': 'Configuración'
76
+ 'settings_menu': 'Configuración',
77
+ 'loading': 'Cargando...',
78
+ 'encoding_in_progress': 'Codificación en curso'
73
79
  },
74
80
 
75
81
  // Français
@@ -83,7 +89,7 @@ class VideoPlayerI18n {
83
89
  'volume': 'Volume',
84
90
  'playback_speed': 'Vitesse de lecture',
85
91
  'video_quality': 'Qualité vidéo',
86
- 'picture_in_picture': 'Image dans limage (P)',
92
+ 'picture_in_picture': 'Image dans l\'image(P)',
87
93
  'fullscreen': 'Plein écran (F)',
88
94
  'auto': 'Auto',
89
95
  'brand_logo': 'Logo de marque',
@@ -91,7 +97,9 @@ class VideoPlayerI18n {
91
97
  'prev_video': 'Vidéo précédente (P)',
92
98
  'playlist_next': 'Suivant',
93
99
  'playlist_prev': 'Précédent',
94
- 'settings_menu': 'Paramètres'
100
+ 'settings_menu': 'Paramètres',
101
+ 'loading': 'Chargement...',
102
+ 'encoding_in_progress': 'Encodage en cours'
95
103
  },
96
104
 
97
105
  // Deutsch
@@ -113,7 +121,9 @@ class VideoPlayerI18n {
113
121
  'prev_video': 'Vorheriges Video (P)',
114
122
  'playlist_next': 'Weiter',
115
123
  'playlist_prev': 'Zurück',
116
- 'settings_menu': 'Einstellungen'
124
+ 'settings_menu': 'Einstellungen',
125
+ 'loading': 'Laden...',
126
+ 'encoding_in_progress': 'Kodierung läuft'
117
127
  },
118
128
 
119
129
  // Português
@@ -135,7 +145,9 @@ class VideoPlayerI18n {
135
145
  'prev_video': 'Vídeo anterior (P)',
136
146
  'playlist_next': 'Próximo',
137
147
  'playlist_prev': 'Anterior',
138
- 'settings_menu': 'Configurações'
148
+ 'settings_menu': 'Configurações',
149
+ 'loading': 'Carregando...',
150
+ 'encoding_in_progress': 'Codificação em andamento'
139
151
  },
140
152
 
141
153
  // 中文
@@ -157,7 +169,9 @@ class VideoPlayerI18n {
157
169
  'prev_video': '上一个视频 (P)',
158
170
  'playlist_next': '下一个',
159
171
  'playlist_prev': '上一个',
160
- 'settings_menu': '设置'
172
+ 'settings_menu': '设置',
173
+ 'loading': '加载中...',
174
+ 'encoding_in_progress': '编码中'
161
175
  },
162
176
 
163
177
  // 日本語
@@ -179,7 +193,9 @@ class VideoPlayerI18n {
179
193
  'prev_video': '前の動画 (P)',
180
194
  'playlist_next': '次へ',
181
195
  'playlist_prev': '前へ',
182
- 'settings_menu': '設定'
196
+ 'settings_menu': '設定',
197
+ 'loading': '読み込み中...',
198
+ 'encoding_in_progress': 'エンコード中'
183
199
  },
184
200
 
185
201
  // Русский
@@ -201,7 +217,9 @@ class VideoPlayerI18n {
201
217
  'prev_video': 'Предыдущее видео (P)',
202
218
  'playlist_next': 'Далее',
203
219
  'playlist_prev': 'Назад',
204
- 'settings_menu': 'Настройки'
220
+ 'settings_menu': 'Настройки',
221
+ 'loading': 'Загрузка...',
222
+ 'encoding_in_progress': 'Кодирование'
205
223
  },
206
224
 
207
225
  // العربية
@@ -223,7 +241,9 @@ class VideoPlayerI18n {
223
241
  'prev_video': 'الفيديو السابق (P)',
224
242
  'playlist_next': 'التالي',
225
243
  'playlist_prev': 'السابق',
226
- 'settings_menu': 'الإعدادات'
244
+ 'settings_menu': 'الإعدادات',
245
+ 'loading': 'جاري التحميل...',
246
+ 'encoding_in_progress': 'الترميز جارٍ'
227
247
  },
228
248
 
229
249
  // 한국어 (Korean)
@@ -245,7 +265,9 @@ class VideoPlayerI18n {
245
265
  'prev_video': '이전 비디오 (P)',
246
266
  'playlist_next': '다음',
247
267
  'playlist_prev': '이전',
248
- 'settings_menu': '설정'
268
+ 'settings_menu': '설정',
269
+ 'loading': '로딩 중...',
270
+ 'encoding_in_progress': '인코딩 진행 중'
249
271
  },
250
272
 
251
273
  // Polski
@@ -267,7 +289,9 @@ class VideoPlayerI18n {
267
289
  'prev_video': 'Poprzednie wideo (P)',
268
290
  'playlist_next': 'Dalej',
269
291
  'playlist_prev': 'Wstecz',
270
- 'settings_menu': 'Ustawienia'
292
+ 'settings_menu': 'Ustawienia',
293
+ 'loading': 'Ładowanie...',
294
+ 'encoding_in_progress': 'Kodowanie w toku'
271
295
  },
272
296
 
273
297
  // Magyar
@@ -289,7 +313,9 @@ class VideoPlayerI18n {
289
313
  'prev_video': 'Előző videó (P)',
290
314
  'playlist_next': 'Következő',
291
315
  'playlist_prev': 'Előző',
292
- 'settings_menu': 'Beállítások'
316
+ 'settings_menu': 'Beállítások',
317
+ 'loading': 'Betöltés...',
318
+ 'encoding_in_progress': 'Kódolás folyamatban'
293
319
  },
294
320
 
295
321
  // Türkçe
@@ -311,7 +337,9 @@ class VideoPlayerI18n {
311
337
  'prev_video': 'Önceki video (P)',
312
338
  'playlist_next': 'Sonraki',
313
339
  'playlist_prev': 'Önceki',
314
- 'settings_menu': 'Ayarlar'
340
+ 'settings_menu': 'Ayarlar',
341
+ 'loading': 'Yükleniyor...',
342
+ 'encoding_in_progress': 'Kodlama devam ediyor'
315
343
  },
316
344
 
317
345
  // Nederlands
@@ -333,7 +361,9 @@ class VideoPlayerI18n {
333
361
  'prev_video': 'Vorige video (P)',
334
362
  'playlist_next': 'Volgende',
335
363
  'playlist_prev': 'Vorige',
336
- 'settings_menu': 'Instellingen'
364
+ 'settings_menu': 'Instellingen',
365
+ 'loading': 'Laden...',
366
+ 'encoding_in_progress': 'Codering bezig'
337
367
  },
338
368
 
339
369
  // हिन्दी (Hindi)
@@ -355,7 +385,9 @@ class VideoPlayerI18n {
355
385
  'prev_video': 'पिछला वीडियो (P)',
356
386
  'playlist_next': 'अगला',
357
387
  'playlist_prev': 'पिछला',
358
- 'settings_menu': 'सेटिंग्स'
388
+ 'settings_menu': 'सेटिंग्स',
389
+ 'loading': 'लोड हो रहा है...',
390
+ 'encoding_in_progress': 'एन्कोडिंग प्रगति में'
359
391
  },
360
392
 
361
393
  // Svenska
@@ -377,7 +409,9 @@ class VideoPlayerI18n {
377
409
  'prev_video': 'Föregående video (P)',
378
410
  'playlist_next': 'Nästa',
379
411
  'playlist_prev': 'Föregående',
380
- 'settings_menu': 'Inställningar'
412
+ 'settings_menu': 'Inställningar',
413
+ 'loading': 'Laddar...',
414
+ 'encoding_in_progress': 'Kodning pågår'
381
415
  },
382
416
 
383
417
  // Bahasa Indonesia
@@ -399,7 +433,9 @@ class VideoPlayerI18n {
399
433
  'prev_video': 'Video sebelumnya (P)',
400
434
  'playlist_next': 'Berikutnya',
401
435
  'playlist_prev': 'Sebelumnya',
402
- 'settings_menu': 'Pengaturan'
436
+ 'settings_menu': 'Pengaturan',
437
+ 'loading': 'Memuat...',
438
+ 'encoding_in_progress': 'Encoding sedang berlangsung'
403
439
  }
404
440
  };
405
441
 
@@ -527,7 +563,9 @@ try {
527
563
  'fullscreen': 'Fullscreen (F)',
528
564
  'auto': 'Auto',
529
565
  'brand_logo': 'Brand logo',
530
- 'settings_menu': 'Settings'
566
+ 'settings_menu': 'Settings',
567
+ 'loading': 'Loading...',
568
+ 'encoding_in_progress': 'Encoding in progress'
531
569
  };
532
570
  return fallback[key] || key;
533
571
  },
@@ -865,6 +903,59 @@ constructor(videoElement, options = {}) {
865
903
  }
866
904
  }
867
905
 
906
+ /**
907
+ * Decode HTML entities to normal characters
908
+ * @param {string} text - Text with HTML entities
909
+ * @returns {string} Decoded text
910
+ */
911
+ decodeHTMLEntities(text) {
912
+ if (!text) return '';
913
+ const textarea = document.createElement('textarea');
914
+ textarea.innerHTML = text;
915
+ return textarea.value;
916
+ }
917
+
918
+ // check if the device is Fire TV
919
+ isFireTV() {
920
+ const ua = navigator.userAgent.toLowerCase();
921
+ return ua.includes('aftm') ||
922
+ ua.includes('aftb') ||
923
+ ua.includes('afts') ||
924
+ ua.includes('aftmm') ||
925
+ ua.includes('aftt');
926
+ }
927
+
928
+ // apply Fire TV specific optimizations
929
+ optimizeVideoForFireTV() {
930
+ if (!this.isFireTV() || !this.video) return;
931
+
932
+ if (this.options.debug) {
933
+ console.log('Fire TV detected - applying optimizations');
934
+ }
935
+
936
+ // set playsinline attributes
937
+ this.video.setAttribute('playsinline', '');
938
+ this.video.setAttribute('webkit-playsinline', '');
939
+
940
+ // CSS optimizations
941
+ this.video.style.transform = 'translateZ(0)';
942
+ this.video.style.webkitTransform = 'translateZ(0)';
943
+ this.video.style.backfaceVisibility = 'hidden';
944
+ this.video.style.webkitBackfaceVisibility = 'hidden';
945
+ this.video.style.willChange = 'transform';
946
+
947
+ // force repaint on loadeddata
948
+ this.video.addEventListener('loadeddata', () => {
949
+ if (this.options.debug) {
950
+ console.log('Fire TV: Video loaded, forcing repaint');
951
+ }
952
+ this.video.style.display = 'none';
953
+ setTimeout(() => {
954
+ this.video.style.display = 'block';
955
+ }, 10);
956
+ }, { once: true });
957
+ }
958
+
868
959
  getPlayerState() {
869
960
  return {
870
961
  isPlaying: !this.isPaused(),
@@ -1164,6 +1255,7 @@ createPlayerStructure() {
1164
1255
 
1165
1256
  this.container = wrapper;
1166
1257
 
1258
+ this.optimizeVideoForFireTV();
1167
1259
  this.createInitialLoading();
1168
1260
  this.createLoadingOverlay();
1169
1261
  this.collectVideoQualities();
@@ -1204,14 +1296,14 @@ createTitleOverlay() {
1204
1296
 
1205
1297
  const titleText = document.createElement('h2');
1206
1298
  titleText.className = 'title-text';
1207
- titleText.textContent = this.options.videoTitle || '';
1299
+ titleText.textContent = this.decodeHTMLEntities(this.options.videoTitle) || '';
1208
1300
  overlay.appendChild(titleText);
1209
1301
 
1210
1302
  // add subtitles
1211
1303
  if (this.options.videoSubtitle) {
1212
1304
  const subtitleText = document.createElement('p');
1213
1305
  subtitleText.className = 'subtitle-text';
1214
- subtitleText.textContent = this.options.videoSubtitle;
1306
+ subtitleText.textContent = this.decodeHTMLEntities(this.options.videoSubtitle);
1215
1307
  overlay.appendChild(subtitleText);
1216
1308
  }
1217
1309
 
@@ -1266,7 +1358,7 @@ setVideoTitle(title) {
1266
1358
  if (this.titleOverlay) {
1267
1359
  const titleElement = this.titleOverlay.querySelector('.title-text');
1268
1360
  if (titleElement) {
1269
- titleElement.textContent = this.options.videoTitle;
1361
+ titleElement.textContent = this.decodeHTMLEntities(this.options.videoTitle);
1270
1362
  }
1271
1363
 
1272
1364
  if (title) {
@@ -1748,9 +1840,10 @@ updateProgress() {
1748
1840
  this.progressHandle.style.left = progress + '%';
1749
1841
  }
1750
1842
 
1843
+ // Always call updateTimeDisplay, regardless of duration validity
1751
1844
  this.updateTimeDisplay();
1752
1845
 
1753
- // Trigger timeupdate event (with throttling to avoid too many events)
1846
+ // Trigger timeupdate event with throttling
1754
1847
  if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
1755
1848
  this.triggerEvent('timeupdate', {
1756
1849
  currentTime: this.getCurrentTime(),
@@ -1829,6 +1922,8 @@ updateDuration() {
1829
1922
  if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
1830
1923
  this.durationEl.textContent = this.formatTime(this.video.duration);
1831
1924
  }
1925
+ // Call updateTimeDisplay to handle all states (loading, encoding, normal)
1926
+ this.updateTimeDisplay();
1832
1927
  }
1833
1928
 
1834
1929
  changeSpeed(e) {
@@ -2121,7 +2216,7 @@ switchToVideo(newVideoElement, shouldPlay = false) {
2121
2216
  if (newTitle && this.options.showTitleOverlay) {
2122
2217
  this.options.videoTitle = newTitle;
2123
2218
  if (this.titleText) {
2124
- this.titleText.textContent = newTitle;
2219
+ this.titleText.textContent = this.decodeHTMLEntities(newTitle);
2125
2220
  }
2126
2221
  }
2127
2222
 
@@ -2715,6 +2810,10 @@ addEventListener(eventType, callback) {
2715
2810
  if (!this.isChangingQuality) {
2716
2811
  this.showLoading();
2717
2812
  }
2813
+
2814
+ // Update time display to show "Loading..." during initial buffering
2815
+ this.updateTimeDisplay();
2816
+
2718
2817
  // Trigger loadstart event - browser started loading media
2719
2818
  this.triggerEvent('loadstart');
2720
2819
  });
@@ -2722,6 +2821,9 @@ addEventListener(eventType, callback) {
2722
2821
  this.video.addEventListener('loadedmetadata', () => {
2723
2822
  this.updateDuration();
2724
2823
 
2824
+ // Update time display when metadata is loaded
2825
+ this.updateTimeDisplay();
2826
+
2725
2827
  // Trigger loadedmetadata event - video metadata loaded
2726
2828
  this.triggerEvent('loadedmetadata', {
2727
2829
  duration: this.getDuration(),
@@ -2739,6 +2841,10 @@ addEventListener(eventType, callback) {
2739
2841
  if (!this.isChangingQuality) {
2740
2842
  this.hideLoading();
2741
2843
  }
2844
+
2845
+ // Update time display when data is loaded
2846
+ this.updateTimeDisplay();
2847
+
2742
2848
  // Trigger loadeddata event - current frame data loaded
2743
2849
  this.triggerEvent('loadeddata', {
2744
2850
  currentTime: this.getCurrentTime()
@@ -2749,6 +2855,10 @@ addEventListener(eventType, callback) {
2749
2855
  if (!this.isChangingQuality) {
2750
2856
  this.hideLoading();
2751
2857
  }
2858
+
2859
+ // Update time display when video can play
2860
+ this.updateTimeDisplay();
2861
+
2752
2862
  // Trigger canplay event - video can start playing
2753
2863
  this.triggerEvent('canplay', {
2754
2864
  currentTime: this.getCurrentTime(),
@@ -2756,6 +2866,21 @@ addEventListener(eventType, callback) {
2756
2866
  });
2757
2867
  });
2758
2868
 
2869
+ // Also add to waiting event
2870
+ this.video.addEventListener('waiting', () => {
2871
+ if (!this.isChangingQuality) {
2872
+ this.showLoading();
2873
+
2874
+ // Update time display during buffering
2875
+ this.updateTimeDisplay();
2876
+
2877
+ // Trigger waiting event - video is buffering
2878
+ this.triggerEvent('waiting', {
2879
+ currentTime: this.getCurrentTime()
2880
+ });
2881
+ }
2882
+ });
2883
+
2759
2884
  this.video.addEventListener('progress', () => {
2760
2885
  this.updateBuffer();
2761
2886
  // Trigger progress event - browser is downloading media
@@ -7721,63 +7846,79 @@ removePluginControlButton(buttonId) {
7721
7846
  }
7722
7847
 
7723
7848
  getBufferedTime() {
7724
- if (!this.video || !this.video.buffered || this.video.buffered.length === 0) return 0;
7725
- try {
7726
- return this.video.buffered.end(this.video.buffered.length - 1);
7727
- } catch (error) {
7728
- return 0;
7729
- }
7849
+ if (!this.video || !this.video.buffered || this.video.buffered.length === 0) return 0;
7850
+ try {
7851
+ return this.video.buffered.end(this.video.buffered.length - 1);
7852
+ } catch (error) {
7853
+ return 0;
7730
7854
  }
7855
+ }
7731
7856
 
7732
- clearTitleTimeout() {
7733
- if (this.titleTimeout) {
7734
- clearTimeout(this.titleTimeout);
7735
- this.titleTimeout = null;
7736
- }
7857
+ clearTitleTimeout() {
7858
+ if (this.titleTimeout) {
7859
+ clearTimeout(this.titleTimeout);
7860
+ this.titleTimeout = null;
7737
7861
  }
7862
+ }
7738
7863
 
7739
- skipTime(seconds) {
7740
- if (!this.video || !this.video.duration || this.isChangingQuality) return;
7864
+ skipTime(seconds) {
7865
+ if (!this.video || !this.video.duration || this.isChangingQuality) return;
7741
7866
 
7742
- this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
7743
- }
7867
+ this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
7868
+ }
7744
7869
 
7745
7870
  updateTimeDisplay() {
7746
- // update current time
7871
+ // Update current time
7747
7872
  if (this.currentTimeEl && this.video) {
7748
7873
  this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
7749
7874
  }
7750
7875
 
7751
- // update duration or show badge if encoding
7876
+ // Update duration or show appropriate message
7752
7877
  if (this.durationEl && this.video) {
7753
7878
  const duration = this.video.duration;
7754
-
7755
- // check if duration is valid
7756
- if (!duration || isNaN(duration) || !isFinite(duration)) {
7757
- // Video in encoding - show badge instead of duration
7758
- this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
7879
+ const readyState = this.video.readyState;
7880
+ const currentTime = this.video.currentTime;
7881
+ const networkState = this.video.networkState;
7882
+
7883
+ // Check for initial buffering state
7884
+ // readyState < 2 means not enough data to play (HAVE_NOTHING or HAVE_METADATA)
7885
+ // currentTime === 0 and duration === 0 indicates initial loading
7886
+ const isInitialBuffering = (readyState < 2 && currentTime === 0) ||
7887
+ (currentTime === 0 && (!duration || duration === 0) && networkState === 2);
7888
+
7889
+ // Check if duration is invalid (NaN or Infinity)
7890
+ const isDurationInvalid = !duration || isNaN(duration) || !isFinite(duration);
7891
+
7892
+ if (isInitialBuffering) {
7893
+ // Initial buffering - show loading message
7894
+ this.durationEl.textContent = t('loading');
7895
+ this.durationEl.classList.remove('encoding-state');
7896
+ this.durationEl.classList.add('loading-state');
7897
+ } else if (isDurationInvalid) {
7898
+ // Video is encoding (FFmpeg still processing) - show encoding badge
7899
+ this.durationEl.textContent = t('encoding_in_progress');
7900
+ this.durationEl.classList.remove('loading-state');
7759
7901
  this.durationEl.classList.add('encoding-state');
7760
7902
  } else {
7761
- // valid duration - show normal
7903
+ // Valid duration - show normal time
7762
7904
  this.durationEl.textContent = this.formatTime(duration);
7763
- this.durationEl.classList.remove('encoding-state');
7905
+ this.durationEl.classList.remove('encoding-state', 'loading-state');
7764
7906
  }
7765
7907
  }
7766
7908
  }
7767
7909
 
7910
+ formatTime(seconds) {
7911
+ if (isNaN(seconds) || seconds < 0) return '0:00';
7768
7912
 
7769
- formatTime(seconds) {
7770
- if (isNaN(seconds) || seconds < 0) return '0:00';
7913
+ const hours = Math.floor(seconds / 3600);
7914
+ const minutes = Math.floor((seconds % 3600) / 60);
7915
+ const secs = Math.floor(seconds % 60);
7771
7916
 
7772
- const hours = Math.floor(seconds / 3600);
7773
- const minutes = Math.floor((seconds % 3600) / 60);
7774
- const secs = Math.floor(seconds % 60);
7775
-
7776
- if (hours > 0) {
7777
- return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
7778
- }
7779
- return `${minutes}:${secs.toString().padStart(2, '0')}`;
7917
+ if (hours > 0) {
7918
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
7780
7919
  }
7920
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
7921
+ }
7781
7922
 
7782
7923
  }
7783
7924