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.
- package/README.md +1 -1
- package/css/myetv-player.css +118 -170
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +291 -160
- package/dist/myetv-player.min.js +214 -111
- package/package.json +3 -1
- package/plugins/soundcloud/myetv-player-soundcloud-plugin.js +927 -0
- package/plugins/soundcloud/readme.md +89 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +83 -125
package/dist/myetv-player.js
CHANGED
|
@@ -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 l
|
|
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(),
|
|
@@ -1108,6 +1199,11 @@ markPlayerReady() {
|
|
|
1108
1199
|
this.video.style.pointerEvents = '';
|
|
1109
1200
|
}
|
|
1110
1201
|
|
|
1202
|
+
// UPDATE SETTINGS MENU VISIBILITY IF APPLICABLE
|
|
1203
|
+
if (typeof this.updateSettingsMenuVisibility === 'function') {
|
|
1204
|
+
this.updateSettingsMenuVisibility();
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1111
1207
|
// INITIALIZE AUTO-HIDE AFTER EVERYTHING IS READY
|
|
1112
1208
|
setTimeout(() => {
|
|
1113
1209
|
if (this.options.autoHide && !this.autoHideInitialized) {
|
|
@@ -1159,6 +1255,7 @@ createPlayerStructure() {
|
|
|
1159
1255
|
|
|
1160
1256
|
this.container = wrapper;
|
|
1161
1257
|
|
|
1258
|
+
this.optimizeVideoForFireTV();
|
|
1162
1259
|
this.createInitialLoading();
|
|
1163
1260
|
this.createLoadingOverlay();
|
|
1164
1261
|
this.collectVideoQualities();
|
|
@@ -1199,14 +1296,14 @@ createTitleOverlay() {
|
|
|
1199
1296
|
|
|
1200
1297
|
const titleText = document.createElement('h2');
|
|
1201
1298
|
titleText.className = 'title-text';
|
|
1202
|
-
titleText.textContent = this.options.videoTitle || '';
|
|
1299
|
+
titleText.textContent = this.decodeHTMLEntities(this.options.videoTitle) || '';
|
|
1203
1300
|
overlay.appendChild(titleText);
|
|
1204
1301
|
|
|
1205
1302
|
// add subtitles
|
|
1206
1303
|
if (this.options.videoSubtitle) {
|
|
1207
1304
|
const subtitleText = document.createElement('p');
|
|
1208
1305
|
subtitleText.className = 'subtitle-text';
|
|
1209
|
-
subtitleText.textContent = this.options.videoSubtitle;
|
|
1306
|
+
subtitleText.textContent = this.decodeHTMLEntities(this.options.videoSubtitle);
|
|
1210
1307
|
overlay.appendChild(subtitleText);
|
|
1211
1308
|
}
|
|
1212
1309
|
|
|
@@ -1261,7 +1358,7 @@ setVideoTitle(title) {
|
|
|
1261
1358
|
if (this.titleOverlay) {
|
|
1262
1359
|
const titleElement = this.titleOverlay.querySelector('.title-text');
|
|
1263
1360
|
if (titleElement) {
|
|
1264
|
-
titleElement.textContent = this.options.videoTitle;
|
|
1361
|
+
titleElement.textContent = this.decodeHTMLEntities(this.options.videoTitle);
|
|
1265
1362
|
}
|
|
1266
1363
|
|
|
1267
1364
|
if (title) {
|
|
@@ -1743,9 +1840,10 @@ updateProgress() {
|
|
|
1743
1840
|
this.progressHandle.style.left = progress + '%';
|
|
1744
1841
|
}
|
|
1745
1842
|
|
|
1843
|
+
// Always call updateTimeDisplay, regardless of duration validity
|
|
1746
1844
|
this.updateTimeDisplay();
|
|
1747
1845
|
|
|
1748
|
-
// Trigger timeupdate event
|
|
1846
|
+
// Trigger timeupdate event with throttling
|
|
1749
1847
|
if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
|
|
1750
1848
|
this.triggerEvent('timeupdate', {
|
|
1751
1849
|
currentTime: this.getCurrentTime(),
|
|
@@ -1824,6 +1922,8 @@ updateDuration() {
|
|
|
1824
1922
|
if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
|
|
1825
1923
|
this.durationEl.textContent = this.formatTime(this.video.duration);
|
|
1826
1924
|
}
|
|
1925
|
+
// Call updateTimeDisplay to handle all states (loading, encoding, normal)
|
|
1926
|
+
this.updateTimeDisplay();
|
|
1827
1927
|
}
|
|
1828
1928
|
|
|
1829
1929
|
changeSpeed(e) {
|
|
@@ -2116,7 +2216,7 @@ switchToVideo(newVideoElement, shouldPlay = false) {
|
|
|
2116
2216
|
if (newTitle && this.options.showTitleOverlay) {
|
|
2117
2217
|
this.options.videoTitle = newTitle;
|
|
2118
2218
|
if (this.titleText) {
|
|
2119
|
-
this.titleText.textContent = newTitle;
|
|
2219
|
+
this.titleText.textContent = this.decodeHTMLEntities(newTitle);
|
|
2120
2220
|
}
|
|
2121
2221
|
}
|
|
2122
2222
|
|
|
@@ -2710,6 +2810,10 @@ addEventListener(eventType, callback) {
|
|
|
2710
2810
|
if (!this.isChangingQuality) {
|
|
2711
2811
|
this.showLoading();
|
|
2712
2812
|
}
|
|
2813
|
+
|
|
2814
|
+
// Update time display to show "Loading..." during initial buffering
|
|
2815
|
+
this.updateTimeDisplay();
|
|
2816
|
+
|
|
2713
2817
|
// Trigger loadstart event - browser started loading media
|
|
2714
2818
|
this.triggerEvent('loadstart');
|
|
2715
2819
|
});
|
|
@@ -2717,6 +2821,9 @@ addEventListener(eventType, callback) {
|
|
|
2717
2821
|
this.video.addEventListener('loadedmetadata', () => {
|
|
2718
2822
|
this.updateDuration();
|
|
2719
2823
|
|
|
2824
|
+
// Update time display when metadata is loaded
|
|
2825
|
+
this.updateTimeDisplay();
|
|
2826
|
+
|
|
2720
2827
|
// Trigger loadedmetadata event - video metadata loaded
|
|
2721
2828
|
this.triggerEvent('loadedmetadata', {
|
|
2722
2829
|
duration: this.getDuration(),
|
|
@@ -2734,6 +2841,10 @@ addEventListener(eventType, callback) {
|
|
|
2734
2841
|
if (!this.isChangingQuality) {
|
|
2735
2842
|
this.hideLoading();
|
|
2736
2843
|
}
|
|
2844
|
+
|
|
2845
|
+
// Update time display when data is loaded
|
|
2846
|
+
this.updateTimeDisplay();
|
|
2847
|
+
|
|
2737
2848
|
// Trigger loadeddata event - current frame data loaded
|
|
2738
2849
|
this.triggerEvent('loadeddata', {
|
|
2739
2850
|
currentTime: this.getCurrentTime()
|
|
@@ -2744,6 +2855,10 @@ addEventListener(eventType, callback) {
|
|
|
2744
2855
|
if (!this.isChangingQuality) {
|
|
2745
2856
|
this.hideLoading();
|
|
2746
2857
|
}
|
|
2858
|
+
|
|
2859
|
+
// Update time display when video can play
|
|
2860
|
+
this.updateTimeDisplay();
|
|
2861
|
+
|
|
2747
2862
|
// Trigger canplay event - video can start playing
|
|
2748
2863
|
this.triggerEvent('canplay', {
|
|
2749
2864
|
currentTime: this.getCurrentTime(),
|
|
@@ -2751,6 +2866,21 @@ addEventListener(eventType, callback) {
|
|
|
2751
2866
|
});
|
|
2752
2867
|
});
|
|
2753
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
|
+
|
|
2754
2884
|
this.video.addEventListener('progress', () => {
|
|
2755
2885
|
this.updateBuffer();
|
|
2756
2886
|
// Trigger progress event - browser is downloading media
|
|
@@ -3486,30 +3616,12 @@ createControls() {
|
|
|
3486
3616
|
<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>
|
|
3487
3617
|
</button>
|
|
3488
3618
|
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
<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>
|
|
3619
|
+
<div class="settings-control">
|
|
3620
|
+
<button class="control-btn settings-btn" data-tooltip="settings_menu">
|
|
3621
|
+
<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>
|
|
3493
3622
|
</button>
|
|
3494
|
-
<div class="
|
|
3495
|
-
<div class="subtitles-option active" data-track="off">Off</div>
|
|
3496
|
-
</div>
|
|
3497
|
-
</div>
|
|
3498
|
-
` : ''}
|
|
3499
|
-
|
|
3500
|
-
${this.options.showSpeedControl ? `
|
|
3501
|
-
<div class="speed-control">
|
|
3502
|
-
<button class="control-btn speed-btn" data-tooltip="playback_speed">1x</button>
|
|
3503
|
-
<div class="speed-menu">
|
|
3504
|
-
<div class="speed-option" data-speed="0.5">0.5x</div>
|
|
3505
|
-
<div class="speed-option" data-speed="0.75">0.75x</div>
|
|
3506
|
-
<div class="speed-option active" data-speed="1">1x</div>
|
|
3507
|
-
<div class="speed-option" data-speed="1.25">1.25x</div>
|
|
3508
|
-
<div class="speed-option" data-speed="1.5">1.5x</div>
|
|
3509
|
-
<div class="speed-option" data-speed="2">2x</div>
|
|
3510
|
-
</div>
|
|
3623
|
+
<div class="settings-menu"></div>
|
|
3511
3624
|
</div>
|
|
3512
|
-
` : ''}
|
|
3513
3625
|
|
|
3514
3626
|
${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
|
|
3515
3627
|
<div class="quality-control">
|
|
@@ -3522,8 +3634,8 @@ createControls() {
|
|
|
3522
3634
|
<div class="quality-menu">
|
|
3523
3635
|
<div class="quality-option selected" data-quality="auto">${this.t('auto')}</div>
|
|
3524
3636
|
${this.originalSources.map(s =>
|
|
3525
|
-
|
|
3526
|
-
|
|
3637
|
+
`<div class="quality-option" data-quality="${s.quality}">${s.quality}</div>`
|
|
3638
|
+
).join('')}
|
|
3527
3639
|
</div>
|
|
3528
3640
|
</div>
|
|
3529
3641
|
` : ''}
|
|
@@ -3535,13 +3647,6 @@ createControls() {
|
|
|
3535
3647
|
</button>
|
|
3536
3648
|
` : ''}
|
|
3537
3649
|
|
|
3538
|
-
<div class="settings-control">
|
|
3539
|
-
<button class="control-btn settings-btn" data-tooltip="settings_menu">
|
|
3540
|
-
<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>
|
|
3541
|
-
</button>
|
|
3542
|
-
<div class="settings-menu"></div>
|
|
3543
|
-
</div>
|
|
3544
|
-
|
|
3545
3650
|
${this.options.showFullscreen ? `
|
|
3546
3651
|
<button class="control-btn fullscreen-btn" data-tooltip="fullscreen">
|
|
3547
3652
|
<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>
|
|
@@ -3638,44 +3743,22 @@ checkScreenSize() {
|
|
|
3638
3743
|
}
|
|
3639
3744
|
}
|
|
3640
3745
|
|
|
3641
|
-
/* Update settings menu visibility
|
|
3746
|
+
/* Update settings menu visibility */
|
|
3642
3747
|
updateSettingsMenuVisibility() {
|
|
3643
3748
|
const settingsControl = this.controls?.querySelector('.settings-control');
|
|
3644
3749
|
if (!settingsControl) return;
|
|
3645
3750
|
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
settingsControl.style.display = 'block';
|
|
3649
|
-
|
|
3650
|
-
// Hide controls that will be moved to settings menu
|
|
3651
|
-
const pipBtn = this.controls.querySelector('.pip-btn');
|
|
3652
|
-
const speedControl = this.controls.querySelector('.speed-control');
|
|
3653
|
-
const subtitlesControl = this.controls.querySelector('.subtitles-control');
|
|
3654
|
-
|
|
3655
|
-
if (pipBtn) pipBtn.style.display = 'none';
|
|
3656
|
-
if (speedControl) speedControl.style.display = 'none';
|
|
3657
|
-
if (subtitlesControl) subtitlesControl.style.display = 'none';
|
|
3658
|
-
|
|
3659
|
-
this.populateSettingsMenu();
|
|
3660
|
-
} else {
|
|
3661
|
-
// Hide settings menu and show individual controls
|
|
3662
|
-
settingsControl.style.display = 'none';
|
|
3751
|
+
// always show settings
|
|
3752
|
+
settingsControl.style.display = 'block';
|
|
3663
3753
|
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
const speedControl = this.controls.querySelector('.speed-control');
|
|
3667
|
-
const subtitlesControl = this.controls.querySelector('.subtitles-control');
|
|
3754
|
+
// Populate settings menu
|
|
3755
|
+
this.populateSettingsMenu();
|
|
3668
3756
|
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
}
|
|
3675
|
-
if (subtitlesControl && this.options.showSubtitles && this.textTracks.length > 0) {
|
|
3676
|
-
subtitlesControl.style.display = 'block';
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3757
|
+
// hide speed and subtitles controls
|
|
3758
|
+
const speedControl = this.controls.querySelector('.speed-control');
|
|
3759
|
+
const subtitlesControl = this.controls.querySelector('.subtitles-control');
|
|
3760
|
+
if (speedControl) speedControl.style.display = 'none';
|
|
3761
|
+
if (subtitlesControl) subtitlesControl.style.display = 'none';
|
|
3679
3762
|
}
|
|
3680
3763
|
|
|
3681
3764
|
/**
|
|
@@ -3687,54 +3770,40 @@ populateSettingsMenu() {
|
|
|
3687
3770
|
|
|
3688
3771
|
let menuHTML = '';
|
|
3689
3772
|
|
|
3690
|
-
//
|
|
3691
|
-
if (this.options.showPictureInPicture && this.isPiPSupported) {
|
|
3692
|
-
const pipLabel = this.t('picture_in_picture') || 'Picture-in-Picture';
|
|
3693
|
-
menuHTML += `<div class="settings-option" data-action="pip">
|
|
3694
|
-
<span class="settings-option-label">${pipLabel}</span>
|
|
3695
|
-
</div>`;
|
|
3696
|
-
}
|
|
3697
|
-
|
|
3698
|
-
// Speed Control - expandable
|
|
3773
|
+
// SPEED - always included
|
|
3699
3774
|
if (this.options.showSpeedControl) {
|
|
3700
3775
|
const speedLabel = this.t('playback_speed') || 'Playback Speed';
|
|
3701
3776
|
const currentSpeed = this.video ? this.video.playbackRate : 1;
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
<
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
</div>
|
|
3709
|
-
<div class="settings-expandable-content" style="display: none;">`;
|
|
3777
|
+
menuHTML += `<div class="settings-expandable-wrapper">
|
|
3778
|
+
<div class="settings-option expandable-trigger" data-action="speed-expand">
|
|
3779
|
+
<span class="settings-option-label">${speedLabel} <strong>${currentSpeed}x</strong></span>
|
|
3780
|
+
<span class="expand-arrow">▶</span>
|
|
3781
|
+
</div>
|
|
3782
|
+
<div class="settings-expandable-content" style="display: none;">`;
|
|
3710
3783
|
|
|
3711
3784
|
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
3712
3785
|
speeds.forEach(speed => {
|
|
3713
3786
|
const isActive = Math.abs(speed - currentSpeed) < 0.01;
|
|
3714
3787
|
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
|
|
3715
3788
|
});
|
|
3716
|
-
|
|
3717
3789
|
menuHTML += `</div></div>`;
|
|
3718
3790
|
}
|
|
3719
3791
|
|
|
3720
|
-
//
|
|
3792
|
+
// SUBTITLES - always included
|
|
3721
3793
|
if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
|
|
3722
3794
|
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
3723
3795
|
const currentTrack = this.currentSubtitleTrack;
|
|
3724
|
-
const currentLabel = this.subtitlesEnabled
|
|
3796
|
+
const currentLabel = this.subtitlesEnabled ? (currentTrack ? currentTrack.label : 'Unknown') : (this.t('subtitlesoff') || 'Off');
|
|
3725
3797
|
|
|
3726
|
-
menuHTML +=
|
|
3727
|
-
<div class="settings-expandable-
|
|
3728
|
-
<
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
<div class="settings-expandable-content" style="display: none;">`;
|
|
3798
|
+
menuHTML += `<div class="settings-expandable-wrapper">
|
|
3799
|
+
<div class="settings-option expandable-trigger" data-action="subtitles-expand">
|
|
3800
|
+
<span class="settings-option-label">${subtitlesLabel} <strong>${currentLabel}</strong></span>
|
|
3801
|
+
<span class="expand-arrow">▶</span>
|
|
3802
|
+
</div>
|
|
3803
|
+
<div class="settings-expandable-content" style="display: none;">`;
|
|
3733
3804
|
|
|
3734
|
-
// Off option
|
|
3735
3805
|
menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
|
|
3736
3806
|
|
|
3737
|
-
// Subtitle tracks
|
|
3738
3807
|
this.textTracks.forEach((trackData, index) => {
|
|
3739
3808
|
const isActive = this.currentSubtitleTrack === trackData.track;
|
|
3740
3809
|
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
|
|
@@ -3744,9 +3813,6 @@ populateSettingsMenu() {
|
|
|
3744
3813
|
}
|
|
3745
3814
|
|
|
3746
3815
|
settingsMenu.innerHTML = menuHTML;
|
|
3747
|
-
|
|
3748
|
-
// Add scrollbar if needed
|
|
3749
|
-
this.addSettingsMenuScrollbar();
|
|
3750
3816
|
}
|
|
3751
3817
|
|
|
3752
3818
|
/**
|
|
@@ -3756,12 +3822,27 @@ addSettingsMenuScrollbar() {
|
|
|
3756
3822
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
3757
3823
|
if (!settingsMenu) return;
|
|
3758
3824
|
|
|
3759
|
-
const
|
|
3760
|
-
|
|
3825
|
+
const settingsBtn = document.querySelector('.settings-btn');
|
|
3826
|
+
if (!settingsBtn) return;
|
|
3761
3827
|
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3828
|
+
// helper to update menu height
|
|
3829
|
+
const updateMenuHeight = () => {
|
|
3830
|
+
if (settingsMenu.classList.contains('active')) {
|
|
3831
|
+
const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
|
|
3832
|
+
const btnRect = settingsBtn.getBoundingClientRect();
|
|
3833
|
+
const spaceBelow = containerRect.bottom - btnRect.bottom;
|
|
3834
|
+
const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
|
|
3835
|
+
settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
|
|
3836
|
+
settingsMenu.style.overflowY = 'auto';
|
|
3837
|
+
settingsMenu.style.overflowX = 'hidden';
|
|
3838
|
+
}
|
|
3839
|
+
};
|
|
3840
|
+
|
|
3841
|
+
// run initially
|
|
3842
|
+
updateMenuHeight();
|
|
3843
|
+
|
|
3844
|
+
// recalculate on window resize
|
|
3845
|
+
window.addEventListener('resize', updateMenuHeight);
|
|
3765
3846
|
|
|
3766
3847
|
// Add scrollbar styling
|
|
3767
3848
|
if (!document.getElementById('player-settings-scrollbar-style')) {
|
|
@@ -3794,9 +3875,43 @@ addSettingsMenuScrollbar() {
|
|
|
3794
3875
|
* Bind settings menu events
|
|
3795
3876
|
*/
|
|
3796
3877
|
bindSettingsMenuEvents() {
|
|
3878
|
+
const settingsBtn = this.controls?.querySelector('.settings-btn');
|
|
3797
3879
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
3798
|
-
if (!settingsMenu) return;
|
|
3880
|
+
if (!settingsMenu || !settingsBtn) return;
|
|
3799
3881
|
|
|
3882
|
+
// toggle menu on button click
|
|
3883
|
+
settingsBtn.addEventListener('click', (e) => {
|
|
3884
|
+
e.stopPropagation();
|
|
3885
|
+
settingsMenu.classList.toggle('active');
|
|
3886
|
+
|
|
3887
|
+
// when menu is opened, set max height and overflow
|
|
3888
|
+
if (settingsMenu.classList.contains('active')) {
|
|
3889
|
+
const settingsBtn = document.querySelector('.settings-btn');
|
|
3890
|
+
const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
|
|
3891
|
+
const btnRect = settingsBtn.getBoundingClientRect();
|
|
3892
|
+
const spaceBelow = containerRect.bottom - btnRect.bottom;
|
|
3893
|
+
const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
|
|
3894
|
+
|
|
3895
|
+
settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
|
|
3896
|
+
settingsMenu.style.overflowY = 'auto';
|
|
3897
|
+
settingsMenu.style.overflowX = 'hidden';
|
|
3898
|
+
} else {
|
|
3899
|
+
settingsMenu.style.maxHeight = 'none';
|
|
3900
|
+
settingsMenu.style.overflowY = 'visible';
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
});
|
|
3904
|
+
|
|
3905
|
+
// close menu when clicking outside
|
|
3906
|
+
document.addEventListener('click', (e) => {
|
|
3907
|
+
if (!settingsBtn?.contains(e.target) && !settingsMenu?.contains(e.target)) {
|
|
3908
|
+
settingsMenu?.classList.remove('active');
|
|
3909
|
+
settingsMenu.style.maxHeight = 'none';
|
|
3910
|
+
settingsMenu.style.overflowY = 'visible';
|
|
3911
|
+
}
|
|
3912
|
+
});
|
|
3913
|
+
|
|
3914
|
+
// manage clicks inside the menu
|
|
3800
3915
|
settingsMenu.addEventListener('click', (e) => {
|
|
3801
3916
|
e.stopPropagation();
|
|
3802
3917
|
|
|
@@ -7731,63 +7846,79 @@ removePluginControlButton(buttonId) {
|
|
|
7731
7846
|
}
|
|
7732
7847
|
|
|
7733
7848
|
getBufferedTime() {
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
}
|
|
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;
|
|
7740
7854
|
}
|
|
7855
|
+
}
|
|
7741
7856
|
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
}
|
|
7857
|
+
clearTitleTimeout() {
|
|
7858
|
+
if (this.titleTimeout) {
|
|
7859
|
+
clearTimeout(this.titleTimeout);
|
|
7860
|
+
this.titleTimeout = null;
|
|
7747
7861
|
}
|
|
7862
|
+
}
|
|
7748
7863
|
|
|
7749
|
-
|
|
7750
|
-
|
|
7864
|
+
skipTime(seconds) {
|
|
7865
|
+
if (!this.video || !this.video.duration || this.isChangingQuality) return;
|
|
7751
7866
|
|
|
7752
|
-
|
|
7753
|
-
|
|
7867
|
+
this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
|
|
7868
|
+
}
|
|
7754
7869
|
|
|
7755
7870
|
updateTimeDisplay() {
|
|
7756
|
-
//
|
|
7871
|
+
// Update current time
|
|
7757
7872
|
if (this.currentTimeEl && this.video) {
|
|
7758
7873
|
this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
|
|
7759
7874
|
}
|
|
7760
7875
|
|
|
7761
|
-
//
|
|
7876
|
+
// Update duration or show appropriate message
|
|
7762
7877
|
if (this.durationEl && this.video) {
|
|
7763
7878
|
const duration = this.video.duration;
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
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');
|
|
7769
7901
|
this.durationEl.classList.add('encoding-state');
|
|
7770
7902
|
} else {
|
|
7771
|
-
//
|
|
7903
|
+
// Valid duration - show normal time
|
|
7772
7904
|
this.durationEl.textContent = this.formatTime(duration);
|
|
7773
|
-
this.durationEl.classList.remove('encoding-state');
|
|
7905
|
+
this.durationEl.classList.remove('encoding-state', 'loading-state');
|
|
7774
7906
|
}
|
|
7775
7907
|
}
|
|
7776
7908
|
}
|
|
7777
7909
|
|
|
7910
|
+
formatTime(seconds) {
|
|
7911
|
+
if (isNaN(seconds) || seconds < 0) return '0:00';
|
|
7778
7912
|
|
|
7779
|
-
|
|
7780
|
-
|
|
7913
|
+
const hours = Math.floor(seconds / 3600);
|
|
7914
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
7915
|
+
const secs = Math.floor(seconds % 60);
|
|
7781
7916
|
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
const secs = Math.floor(seconds % 60);
|
|
7785
|
-
|
|
7786
|
-
if (hours > 0) {
|
|
7787
|
-
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
7788
|
-
}
|
|
7789
|
-
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
7917
|
+
if (hours > 0) {
|
|
7918
|
+
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
7790
7919
|
}
|
|
7920
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
7921
|
+
}
|
|
7791
7922
|
|
|
7792
7923
|
}
|
|
7793
7924
|
|