myetv-player 1.2.0 → 1.4.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.
Files changed (47) hide show
  1. package/css/myetv-player.css +242 -168
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +638 -203
  4. package/dist/myetv-player.min.js +548 -170
  5. package/package.json +35 -16
  6. package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
  7. package/plugins/vimeo/myetv-player-vimeo.js +80 -49
  8. package/plugins/youtube/README.md +5 -2
  9. package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
  10. package/.github/workflows/codeql.yml +0 -100
  11. package/.github/workflows/npm-publish.yml +0 -30
  12. package/SECURITY.md +0 -50
  13. package/build.js +0 -195
  14. package/scss/README.md +0 -161
  15. package/scss/_audio-player.scss +0 -21
  16. package/scss/_base.scss +0 -116
  17. package/scss/_controls.scss +0 -204
  18. package/scss/_loading.scss +0 -111
  19. package/scss/_menus.scss +0 -432
  20. package/scss/_mixins.scss +0 -112
  21. package/scss/_poster.scss +0 -8
  22. package/scss/_progress-bar.scss +0 -319
  23. package/scss/_resolution.scss +0 -68
  24. package/scss/_responsive.scss +0 -1368
  25. package/scss/_themes.scss +0 -30
  26. package/scss/_title-overlay.scss +0 -60
  27. package/scss/_tooltips.scss +0 -7
  28. package/scss/_variables.scss +0 -49
  29. package/scss/_video.scss +0 -221
  30. package/scss/_volume.scss +0 -122
  31. package/scss/_watermark.scss +0 -128
  32. package/scss/myetv-player.scss +0 -51
  33. package/scss/package.json +0 -16
  34. package/src/README.md +0 -560
  35. package/src/chapters.js +0 -521
  36. package/src/controls.js +0 -1242
  37. package/src/core.js +0 -1922
  38. package/src/events.js +0 -537
  39. package/src/fullscreen.js +0 -82
  40. package/src/i18n.js +0 -374
  41. package/src/playlist.js +0 -177
  42. package/src/plugins.js +0 -384
  43. package/src/quality.js +0 -963
  44. package/src/streaming.js +0 -346
  45. package/src/subtitles.js +0 -524
  46. package/src/utils.js +0 -65
  47. package/src/watermark.js +0 -246
@@ -9,14 +9,15 @@ class VideoPlayerI18n {
9
9
  // Italiano
10
10
  'it': {
11
11
  'subtitles': 'Sottotitoli (C)',
12
- 'subtitlesdisable': 'Disabilita Sottotitoli',
13
- 'subtitlesenable': 'Abilita Sottotitoli',
12
+ 'subtitlesoff': 'Disattivati',
13
+ 'subtitlesdisable': 'Disabilita sottotitoli',
14
+ 'subtitlesenable': 'Abilita sottotitoli',
14
15
  'play_pause': 'Play/Pausa (Spazio)',
15
16
  'mute_unmute': 'Muta/Smuta (M)',
16
17
  'volume': 'Volume',
17
18
  'playback_speed': 'Velocità riproduzione',
18
19
  'video_quality': 'Qualità video',
19
- 'picture_in_picture': 'Picture-in-Picture (P)',
20
+ 'picture_in_picture': 'Finestra sovrapposta (P)',
20
21
  'fullscreen': 'Schermo intero (F)',
21
22
  'auto': 'Auto',
22
23
  'brand_logo': 'Logo',
@@ -30,6 +31,7 @@ class VideoPlayerI18n {
30
31
  // English
31
32
  'en': {
32
33
  'subtitles': 'Subtitles (C)',
34
+ 'subtitlesoff': 'Off',
33
35
  'subtitlesdisable': 'Disable Subtitles',
34
36
  'subtitlesenable': 'Enable Subtitles',
35
37
  'play_pause': 'Play/Pause (Space)',
@@ -45,24 +47,21 @@ class VideoPlayerI18n {
45
47
  'prev_video': 'Previous video (P)',
46
48
  'playlist_next': 'Next',
47
49
  'playlist_prev': 'Previous',
48
- 'next_video': 'Next video (N)',
49
- 'prev_video': 'Previous video (P)',
50
- 'playlist_next': 'Next',
51
- 'playlist_prev': 'Previous',
52
50
  'settings_menu': 'Settings'
53
51
  },
54
52
 
55
53
  // Español
56
54
  'es': {
57
55
  'subtitles': 'Subtítulos (C)',
58
- 'subtitlesdisable': 'Disable Subtitles',
59
- 'subtitlesenable': 'Enable Subtitles',
56
+ 'subtitlesoff': 'Desactivados',
57
+ 'subtitlesdisable': 'Desactivar subtítulos',
58
+ 'subtitlesenable': 'Activar subtítulos',
60
59
  'play_pause': 'Reproducir/Pausar (Espacio)',
61
60
  'mute_unmute': 'Silenciar (M)',
62
61
  'volume': 'Volumen',
63
62
  'playback_speed': 'Velocidad de reproducción',
64
63
  'video_quality': 'Calidad de vídeo',
65
- 'picture_in_picture': 'Picture-in-Picture (P)',
64
+ 'picture_in_picture': 'Imagen en imagen (P)',
66
65
  'fullscreen': 'Pantalla completa (F)',
67
66
  'auto': 'Auto',
68
67
  'brand_logo': 'Logo de marca',
@@ -70,20 +69,21 @@ class VideoPlayerI18n {
70
69
  'prev_video': 'Vídeo anterior (P)',
71
70
  'playlist_next': 'Siguiente',
72
71
  'playlist_prev': 'Anterior',
73
- 'settings_menu': 'Settings'
72
+ 'settings_menu': 'Configuración'
74
73
  },
75
74
 
76
75
  // Français
77
76
  'fr': {
78
77
  'subtitles': 'Sous-titres (C)',
79
- 'subtitlesdisable': 'Disable Subtitles',
80
- 'subtitlesenable': 'Enable Subtitles',
78
+ 'subtitlesoff': 'Désactivés',
79
+ 'subtitlesdisable': 'Désactiver les sous-titres',
80
+ 'subtitlesenable': 'Activer les sous-titres',
81
81
  'play_pause': 'Lecture/Pause (Espace)',
82
82
  'mute_unmute': 'Muet (M)',
83
83
  'volume': 'Volume',
84
84
  'playback_speed': 'Vitesse de lecture',
85
85
  'video_quality': 'Qualité vidéo',
86
- 'picture_in_picture': 'Picture-in-Picture (P)',
86
+ 'picture_in_picture': 'Image dans l’image (P)',
87
87
  'fullscreen': 'Plein écran (F)',
88
88
  'auto': 'Auto',
89
89
  'brand_logo': 'Logo de marque',
@@ -91,20 +91,21 @@ class VideoPlayerI18n {
91
91
  'prev_video': 'Vidéo précédente (P)',
92
92
  'playlist_next': 'Suivant',
93
93
  'playlist_prev': 'Précédent',
94
- 'settings_menu': 'Settings'
94
+ 'settings_menu': 'Paramètres'
95
95
  },
96
96
 
97
97
  // Deutsch
98
98
  'de': {
99
99
  'subtitles': 'Untertitel (C)',
100
- 'subtitlesdisable': 'Disable Subtitles',
101
- 'subtitlesenable': 'Enable Subtitles',
100
+ 'subtitlesoff': 'Aus',
101
+ 'subtitlesdisable': 'Untertitel deaktivieren',
102
+ 'subtitlesenable': 'Untertitel aktivieren',
102
103
  'play_pause': 'Abspielen/Pausieren (Leertaste)',
103
104
  'mute_unmute': 'Stumm (M)',
104
105
  'volume': 'Lautstärke',
105
106
  'playback_speed': 'Wiedergabegeschwindigkeit',
106
107
  'video_quality': 'Videoqualität',
107
- 'picture_in_picture': 'Picture-in-Picture (P)',
108
+ 'picture_in_picture': 'Bild-in-Bild (P)',
108
109
  'fullscreen': 'Vollbild (F)',
109
110
  'auto': 'Auto',
110
111
  'brand_logo': 'Markenlogo',
@@ -112,20 +113,21 @@ class VideoPlayerI18n {
112
113
  'prev_video': 'Vorheriges Video (P)',
113
114
  'playlist_next': 'Weiter',
114
115
  'playlist_prev': 'Zurück',
115
- 'settings_menu': 'Settings'
116
+ 'settings_menu': 'Einstellungen'
116
117
  },
117
118
 
118
119
  // Português
119
120
  'pt': {
120
121
  'subtitles': 'Legendas (C)',
121
- 'subtitlesdisable': 'Disable Subtitles',
122
- 'subtitlesenable': 'Enable Subtitles',
122
+ 'subtitlesoff': 'Desativadas',
123
+ 'subtitlesdisable': 'Desativar legendas',
124
+ 'subtitlesenable': 'Ativar legendas',
123
125
  'play_pause': 'Reproduzir/Pausar (Espaço)',
124
126
  'mute_unmute': 'Silenciar (M)',
125
127
  'volume': 'Volume',
126
128
  'playback_speed': 'Velocidade de reprodução',
127
129
  'video_quality': 'Qualidade do vídeo',
128
- 'picture_in_picture': 'Picture-in-Picture (P)',
130
+ 'picture_in_picture': 'Imagem em imagem (P)',
129
131
  'fullscreen': 'Tela cheia (F)',
130
132
  'auto': 'Auto',
131
133
  'brand_logo': 'Logo da marca',
@@ -133,14 +135,15 @@ class VideoPlayerI18n {
133
135
  'prev_video': 'Vídeo anterior (P)',
134
136
  'playlist_next': 'Próximo',
135
137
  'playlist_prev': 'Anterior',
136
- 'settings_menu': 'Settings'
138
+ 'settings_menu': 'Configurações'
137
139
  },
138
140
 
139
141
  // 中文
140
142
  'zh': {
141
143
  'subtitles': '字幕 (C)',
142
- 'subtitlesdisable': 'Disable Subtitles',
143
- 'subtitlesenable': 'Enable Subtitles',
144
+ 'subtitlesoff': '关闭',
145
+ 'subtitlesdisable': '禁用字幕',
146
+ 'subtitlesenable': '启用字幕',
144
147
  'play_pause': '播放/暂停 (空格)',
145
148
  'mute_unmute': '静音 (M)',
146
149
  'volume': '音量',
@@ -154,14 +157,15 @@ class VideoPlayerI18n {
154
157
  'prev_video': '上一个视频 (P)',
155
158
  'playlist_next': '下一个',
156
159
  'playlist_prev': '上一个',
157
- 'settings_menu': 'Settings'
160
+ 'settings_menu': '设置'
158
161
  },
159
162
 
160
163
  // 日本語
161
164
  'ja': {
162
165
  'subtitles': '字幕 (C)',
163
- 'subtitlesdisable': 'Disable Subtitles',
164
- 'subtitlesenable': 'Enable Subtitles',
166
+ 'subtitlesoff': 'オフ',
167
+ 'subtitlesdisable': '字幕を無効にする',
168
+ 'subtitlesenable': '字幕を有効にする',
165
169
  'play_pause': '再生/一時停止 (スペース)',
166
170
  'mute_unmute': 'ミュート (M)',
167
171
  'volume': '音量',
@@ -175,14 +179,15 @@ class VideoPlayerI18n {
175
179
  'prev_video': '前の動画 (P)',
176
180
  'playlist_next': '次へ',
177
181
  'playlist_prev': '前へ',
178
- 'settings_menu': 'Settings'
182
+ 'settings_menu': '設定'
179
183
  },
180
184
 
181
185
  // Русский
182
186
  'ru': {
183
187
  'subtitles': 'Субтитры (C)',
184
- 'subtitlesdisable': 'Disable Subtitles',
185
- 'subtitlesenable': 'Enable Subtitles',
188
+ 'subtitlesoff': 'Выкл',
189
+ 'subtitlesdisable': 'Отключить субтитры',
190
+ 'subtitlesenable': 'Включить субтитры',
186
191
  'play_pause': 'Воспроизведение/Пауза (Пробел)',
187
192
  'mute_unmute': 'Звук (M)',
188
193
  'volume': 'Громкость',
@@ -196,14 +201,15 @@ class VideoPlayerI18n {
196
201
  'prev_video': 'Предыдущее видео (P)',
197
202
  'playlist_next': 'Далее',
198
203
  'playlist_prev': 'Назад',
199
- 'settings_menu': 'Settings'
204
+ 'settings_menu': 'Настройки'
200
205
  },
201
206
 
202
207
  // العربية
203
208
  'ar': {
204
209
  'subtitles': 'الترجمة (C)',
205
- 'subtitlesdisable': 'Disable Subtitles',
206
- 'subtitlesenable': 'Enable Subtitles',
210
+ 'subtitlesoff': 'إيقاف',
211
+ 'subtitlesdisable': 'تعطيل الترجمة',
212
+ 'subtitlesenable': 'تفعيل الترجمة',
207
213
  'play_pause': 'تشغيل/إيقاف مؤقت (مسافة)',
208
214
  'mute_unmute': 'كتم الصوت (M)',
209
215
  'volume': 'مستوى الصوت',
@@ -217,10 +223,187 @@ class VideoPlayerI18n {
217
223
  'prev_video': 'الفيديو السابق (P)',
218
224
  'playlist_next': 'التالي',
219
225
  'playlist_prev': 'السابق',
220
- 'settings_menu': 'Settings'
226
+ 'settings_menu': 'الإعدادات'
227
+ },
228
+
229
+ // 한국어 (Korean)
230
+ 'ko': {
231
+ 'subtitles': '자막 (C)',
232
+ 'subtitlesoff': '끄기',
233
+ 'subtitlesdisable': '자막 비활성화',
234
+ 'subtitlesenable': '자막 활성화',
235
+ 'play_pause': '재생/일시정지 (스페이스)',
236
+ 'mute_unmute': '음소거 (M)',
237
+ 'volume': '음량',
238
+ 'playback_speed': '재생 속도',
239
+ 'video_quality': '비디오 품질',
240
+ 'picture_in_picture': '화면 속 화면 (P)',
241
+ 'fullscreen': '전체 화면 (F)',
242
+ 'auto': '자동',
243
+ 'brand_logo': '브랜드 로고',
244
+ 'next_video': '다음 비디오 (N)',
245
+ 'prev_video': '이전 비디오 (P)',
246
+ 'playlist_next': '다음',
247
+ 'playlist_prev': '이전',
248
+ 'settings_menu': '설정'
249
+ },
250
+
251
+ // Polski
252
+ 'pl': {
253
+ 'subtitles': 'Napisy (C)',
254
+ 'subtitlesoff': 'Wyłączone',
255
+ 'subtitlesdisable': 'Wyłącz napisy',
256
+ 'subtitlesenable': 'Włącz napisy',
257
+ 'play_pause': 'Odtwarzaj/Pauza (Spacja)',
258
+ 'mute_unmute': 'Wycisz (M)',
259
+ 'volume': 'Głośność',
260
+ 'playback_speed': 'Prędkość odtwarzania',
261
+ 'video_quality': 'Jakość wideo',
262
+ 'picture_in_picture': 'Obraz w obrazie (P)',
263
+ 'fullscreen': 'Pełny ekran (F)',
264
+ 'auto': 'Auto',
265
+ 'brand_logo': 'Logo marki',
266
+ 'next_video': 'Następne wideo (N)',
267
+ 'prev_video': 'Poprzednie wideo (P)',
268
+ 'playlist_next': 'Dalej',
269
+ 'playlist_prev': 'Wstecz',
270
+ 'settings_menu': 'Ustawienia'
271
+ },
272
+
273
+ // Magyar
274
+ 'hu': {
275
+ 'subtitles': 'Feliratok (C)',
276
+ 'subtitlesoff': 'Kikapcsolva',
277
+ 'subtitlesdisable': 'Feliratok kikapcsolása',
278
+ 'subtitlesenable': 'Feliratok bekapcsolása',
279
+ 'play_pause': 'Lejátszás/Szünet (Szóköz)',
280
+ 'mute_unmute': 'Némítás (M)',
281
+ 'volume': 'Hangerő',
282
+ 'playback_speed': 'Lejátszási sebesség',
283
+ 'video_quality': 'Videó minősége',
284
+ 'picture_in_picture': 'Kép a képben (P)',
285
+ 'fullscreen': 'Teljes képernyő (F)',
286
+ 'auto': 'Auto',
287
+ 'brand_logo': 'Márka logó',
288
+ 'next_video': 'Következő videó (N)',
289
+ 'prev_video': 'Előző videó (P)',
290
+ 'playlist_next': 'Következő',
291
+ 'playlist_prev': 'Előző',
292
+ 'settings_menu': 'Beállítások'
293
+ },
294
+
295
+ // Türkçe
296
+ 'tr': {
297
+ 'subtitles': 'Altyazılar (C)',
298
+ 'subtitlesoff': 'Kapalı',
299
+ 'subtitlesdisable': 'Altyazıları kapat',
300
+ 'subtitlesenable': 'Altyazıları aç',
301
+ 'play_pause': 'Oynat/Duraklat (Boşluk)',
302
+ 'mute_unmute': 'Sessize al (M)',
303
+ 'volume': 'Ses düzeyi',
304
+ 'playback_speed': 'Oynatma hızı',
305
+ 'video_quality': 'Video kalitesi',
306
+ 'picture_in_picture': 'Resim içinde resim (P)',
307
+ 'fullscreen': 'Tam ekran (F)',
308
+ 'auto': 'Otomatik',
309
+ 'brand_logo': 'Marka logosu',
310
+ 'next_video': 'Sonraki video (N)',
311
+ 'prev_video': 'Önceki video (P)',
312
+ 'playlist_next': 'Sonraki',
313
+ 'playlist_prev': 'Önceki',
314
+ 'settings_menu': 'Ayarlar'
315
+ },
316
+
317
+ // Nederlands
318
+ 'nl': {
319
+ 'subtitles': 'Ondertitels (C)',
320
+ 'subtitlesoff': 'Uit',
321
+ 'subtitlesdisable': 'Ondertitels uitschakelen',
322
+ 'subtitlesenable': 'Ondertitels inschakelen',
323
+ 'play_pause': 'Afspelen/Pauzeren (Spatie)',
324
+ 'mute_unmute': 'Dempen (M)',
325
+ 'volume': 'Volume',
326
+ 'playback_speed': 'Afspeelsnelheid',
327
+ 'video_quality': 'Videokwaliteit',
328
+ 'picture_in_picture': 'Beeld-in-beeld (P)',
329
+ 'fullscreen': 'Volledig scherm (F)',
330
+ 'auto': 'Auto',
331
+ 'brand_logo': 'Merklogo',
332
+ 'next_video': 'Volgende video (N)',
333
+ 'prev_video': 'Vorige video (P)',
334
+ 'playlist_next': 'Volgende',
335
+ 'playlist_prev': 'Vorige',
336
+ 'settings_menu': 'Instellingen'
337
+ },
338
+
339
+ // हिन्दी (Hindi)
340
+ 'hi': {
341
+ 'subtitles': 'उपशीर्षक (C)',
342
+ 'subtitlesoff': 'बंद',
343
+ 'subtitlesdisable': 'उपशीर्षक अक्षम करें',
344
+ 'subtitlesenable': 'उपशीर्षक सक्षम करें',
345
+ 'play_pause': 'चलाएं/रोकें (स्पेस)',
346
+ 'mute_unmute': 'म्यूट (M)',
347
+ 'volume': 'वॉल्यूम',
348
+ 'playback_speed': 'प्लेबैक गति',
349
+ 'video_quality': 'वीडियो गुणवत्ता',
350
+ 'picture_in_picture': 'चित्र-में-चित्र (P)',
351
+ 'fullscreen': 'पूर्ण स्क्रीन (F)',
352
+ 'auto': 'स्वतः',
353
+ 'brand_logo': 'ब्रांड लोगो',
354
+ 'next_video': 'अगला वीडियो (N)',
355
+ 'prev_video': 'पिछला वीडियो (P)',
356
+ 'playlist_next': 'अगला',
357
+ 'playlist_prev': 'पिछला',
358
+ 'settings_menu': 'सेटिंग्स'
359
+ },
360
+
361
+ // Svenska
362
+ 'sv': {
363
+ 'subtitles': 'Undertexter (C)',
364
+ 'subtitlesoff': 'Av',
365
+ 'subtitlesdisable': 'Inaktivera undertexter',
366
+ 'subtitlesenable': 'Aktivera undertexter',
367
+ 'play_pause': 'Spela/Pausa (Blanksteg)',
368
+ 'mute_unmute': 'Stäng av ljud (M)',
369
+ 'volume': 'Volym',
370
+ 'playback_speed': 'Uppspelningshastighet',
371
+ 'video_quality': 'Videokvalitet',
372
+ 'picture_in_picture': 'Bild i bild (P)',
373
+ 'fullscreen': 'Fullskärm (F)',
374
+ 'auto': 'Auto',
375
+ 'brand_logo': 'Varumärkeslogotyp',
376
+ 'next_video': 'Nästa video (N)',
377
+ 'prev_video': 'Föregående video (P)',
378
+ 'playlist_next': 'Nästa',
379
+ 'playlist_prev': 'Föregående',
380
+ 'settings_menu': 'Inställningar'
381
+ },
382
+
383
+ // Bahasa Indonesia
384
+ 'id': {
385
+ 'subtitles': 'Teks (C)',
386
+ 'subtitlesoff': 'Mati',
387
+ 'subtitlesdisable': 'Nonaktifkan teks',
388
+ 'subtitlesenable': 'Aktifkan teks',
389
+ 'play_pause': 'Putar/Jeda (Spasi)',
390
+ 'mute_unmute': 'Bisu (M)',
391
+ 'volume': 'Volume',
392
+ 'playback_speed': 'Kecepatan pemutaran',
393
+ 'video_quality': 'Kualitas video',
394
+ 'picture_in_picture': 'Gambar-dalam-gambar (P)',
395
+ 'fullscreen': 'Layar penuh (F)',
396
+ 'auto': 'Otomatis',
397
+ 'brand_logo': 'Logo merek',
398
+ 'next_video': 'Video berikutnya (N)',
399
+ 'prev_video': 'Video sebelumnya (P)',
400
+ 'playlist_next': 'Berikutnya',
401
+ 'playlist_prev': 'Sebelumnya',
402
+ 'settings_menu': 'Pengaturan'
221
403
  }
222
404
  };
223
405
 
406
+
224
407
  // THEN detect language (after defining translations)
225
408
  this.currentLanguage = this.detectLanguage();
226
409
  }
@@ -925,6 +1108,11 @@ markPlayerReady() {
925
1108
  this.video.style.pointerEvents = '';
926
1109
  }
927
1110
 
1111
+ // UPDATE SETTINGS MENU VISIBILITY IF APPLICABLE
1112
+ if (typeof this.updateSettingsMenuVisibility === 'function') {
1113
+ this.updateSettingsMenuVisibility();
1114
+ }
1115
+
928
1116
  // INITIALIZE AUTO-HIDE AFTER EVERYTHING IS READY
929
1117
  setTimeout(() => {
930
1118
  if (this.options.autoHide && !this.autoHideInitialized) {
@@ -3303,30 +3491,12 @@ createControls() {
3303
3491
  <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>
3304
3492
  </button>
3305
3493
 
3306
- ${this.options.showSubtitles ? `
3307
- <div class="subtitles-control" style="display: none;">
3308
- <button class="control-btn subtitles-btn" data-tooltip="subtitles">
3309
- <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>
3494
+ <div class="settings-control">
3495
+ <button class="control-btn settings-btn" data-tooltip="settings_menu">
3496
+ <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>
3310
3497
  </button>
3311
- <div class="subtitles-menu">
3312
- <div class="subtitles-option active" data-track="off">Off</div>
3313
- </div>
3314
- </div>
3315
- ` : ''}
3316
-
3317
- ${this.options.showSpeedControl ? `
3318
- <div class="speed-control">
3319
- <button class="control-btn speed-btn" data-tooltip="playback_speed">1x</button>
3320
- <div class="speed-menu">
3321
- <div class="speed-option" data-speed="0.5">0.5x</div>
3322
- <div class="speed-option" data-speed="0.75">0.75x</div>
3323
- <div class="speed-option active" data-speed="1">1x</div>
3324
- <div class="speed-option" data-speed="1.25">1.25x</div>
3325
- <div class="speed-option" data-speed="1.5">1.5x</div>
3326
- <div class="speed-option" data-speed="2">2x</div>
3327
- </div>
3498
+ <div class="settings-menu"></div>
3328
3499
  </div>
3329
- ` : ''}
3330
3500
 
3331
3501
  ${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
3332
3502
  <div class="quality-control">
@@ -3339,8 +3509,8 @@ createControls() {
3339
3509
  <div class="quality-menu">
3340
3510
  <div class="quality-option selected" data-quality="auto">${this.t('auto')}</div>
3341
3511
  ${this.originalSources.map(s =>
3342
- `<div class="quality-option" data-quality="${s.quality}">${s.quality}</div>`
3343
- ).join('')}
3512
+ `<div class="quality-option" data-quality="${s.quality}">${s.quality}</div>`
3513
+ ).join('')}
3344
3514
  </div>
3345
3515
  </div>
3346
3516
  ` : ''}
@@ -3352,13 +3522,6 @@ createControls() {
3352
3522
  </button>
3353
3523
  ` : ''}
3354
3524
 
3355
- <div class="settings-control">
3356
- <button class="control-btn settings-btn" data-tooltip="settings_menu">
3357
- <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>
3358
- </button>
3359
- <div class="settings-menu"></div>
3360
- </div>
3361
-
3362
3525
  ${this.options.showFullscreen ? `
3363
3526
  <button class="control-btn fullscreen-btn" data-tooltip="fullscreen">
3364
3527
  <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>
@@ -3455,44 +3618,22 @@ checkScreenSize() {
3455
3618
  }
3456
3619
  }
3457
3620
 
3458
- /* Update settings menu visibility based on screen size */
3621
+ /* Update settings menu visibility */
3459
3622
  updateSettingsMenuVisibility() {
3460
3623
  const settingsControl = this.controls?.querySelector('.settings-control');
3461
3624
  if (!settingsControl) return;
3462
3625
 
3463
- if (this.isSmallScreen) {
3464
- // Show settings menu and hide individual controls
3465
- settingsControl.style.display = 'block';
3626
+ // always show settings
3627
+ settingsControl.style.display = 'block';
3466
3628
 
3467
- // Hide controls that will be moved to settings menu
3468
- const pipBtn = this.controls.querySelector('.pip-btn');
3469
- const speedControl = this.controls.querySelector('.speed-control');
3470
- const subtitlesControl = this.controls.querySelector('.subtitles-control');
3629
+ // Populate settings menu
3630
+ this.populateSettingsMenu();
3471
3631
 
3472
- if (pipBtn) pipBtn.style.display = 'none';
3473
- if (speedControl) speedControl.style.display = 'none';
3474
- if (subtitlesControl) subtitlesControl.style.display = 'none';
3475
-
3476
- this.populateSettingsMenu();
3477
- } else {
3478
- // Hide settings menu and show individual controls
3479
- settingsControl.style.display = 'none';
3480
-
3481
- // Show original controls
3482
- const pipBtn = this.controls.querySelector('.pip-btn');
3483
- const speedControl = this.controls.querySelector('.speed-control');
3484
- const subtitlesControl = this.controls.querySelector('.subtitles-control');
3485
-
3486
- if (pipBtn && this.options.showPictureInPicture && this.isPiPSupported) {
3487
- pipBtn.style.display = 'flex';
3488
- }
3489
- if (speedControl && this.options.showSpeedControl) {
3490
- speedControl.style.display = 'block';
3491
- }
3492
- if (subtitlesControl && this.options.showSubtitles && this.textTracks.length > 0) {
3493
- subtitlesControl.style.display = 'block';
3494
- }
3495
- }
3632
+ // hide speed and subtitles controls
3633
+ const speedControl = this.controls.querySelector('.speed-control');
3634
+ const subtitlesControl = this.controls.querySelector('.subtitles-control');
3635
+ if (speedControl) speedControl.style.display = 'none';
3636
+ if (subtitlesControl) subtitlesControl.style.display = 'none';
3496
3637
  }
3497
3638
 
3498
3639
  /**
@@ -3504,54 +3645,40 @@ populateSettingsMenu() {
3504
3645
 
3505
3646
  let menuHTML = '';
3506
3647
 
3507
- // Picture-in-Picture option
3508
- if (this.options.showPictureInPicture && this.isPiPSupported) {
3509
- const pipLabel = this.t('picture_in_picture') || 'Picture-in-Picture';
3510
- menuHTML += `<div class="settings-option" data-action="pip">
3511
- <span class="settings-option-label">${pipLabel}</span>
3512
- </div>`;
3513
- }
3514
-
3515
- // Speed Control - expandable
3648
+ // SPEED - always included
3516
3649
  if (this.options.showSpeedControl) {
3517
3650
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
3518
3651
  const currentSpeed = this.video ? this.video.playbackRate : 1;
3519
-
3520
- menuHTML += `
3521
- <div class="settings-expandable-wrapper">
3522
- <div class="settings-option expandable-trigger" data-action="speed-expand">
3523
- <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
3524
- <span class="expand-arrow">▼</span>
3525
- </div>
3526
- <div class="settings-expandable-content" style="display: none;">`;
3652
+ menuHTML += `<div class="settings-expandable-wrapper">
3653
+ <div class="settings-option expandable-trigger" data-action="speed-expand">
3654
+ <span class="settings-option-label">${speedLabel} <strong>${currentSpeed}x</strong></span>
3655
+ <span class="expand-arrow">▶</span>
3656
+ </div>
3657
+ <div class="settings-expandable-content" style="display: none;">`;
3527
3658
 
3528
3659
  const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
3529
3660
  speeds.forEach(speed => {
3530
3661
  const isActive = Math.abs(speed - currentSpeed) < 0.01;
3531
3662
  menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
3532
3663
  });
3533
-
3534
3664
  menuHTML += `</div></div>`;
3535
3665
  }
3536
3666
 
3537
- // Subtitles - expandable
3667
+ // SUBTITLES - always included
3538
3668
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
3539
3669
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3540
3670
  const currentTrack = this.currentSubtitleTrack;
3541
- const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
3671
+ const currentLabel = this.subtitlesEnabled ? (currentTrack ? currentTrack.label : 'Unknown') : (this.t('subtitlesoff') || 'Off');
3542
3672
 
3543
- menuHTML += `
3544
- <div class="settings-expandable-wrapper">
3545
- <div class="settings-option expandable-trigger" data-action="subtitles-expand">
3546
- <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
3547
- <span class="expand-arrow">▼</span>
3548
- </div>
3549
- <div class="settings-expandable-content" style="display: none;">`;
3673
+ menuHTML += `<div class="settings-expandable-wrapper">
3674
+ <div class="settings-option expandable-trigger" data-action="subtitles-expand">
3675
+ <span class="settings-option-label">${subtitlesLabel} <strong>${currentLabel}</strong></span>
3676
+ <span class="expand-arrow">▶</span>
3677
+ </div>
3678
+ <div class="settings-expandable-content" style="display: none;">`;
3550
3679
 
3551
- // Off option
3552
3680
  menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
3553
3681
 
3554
- // Subtitle tracks
3555
3682
  this.textTracks.forEach((trackData, index) => {
3556
3683
  const isActive = this.currentSubtitleTrack === trackData.track;
3557
3684
  menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
@@ -3561,9 +3688,6 @@ populateSettingsMenu() {
3561
3688
  }
3562
3689
 
3563
3690
  settingsMenu.innerHTML = menuHTML;
3564
-
3565
- // Add scrollbar if needed
3566
- this.addSettingsMenuScrollbar();
3567
3691
  }
3568
3692
 
3569
3693
  /**
@@ -3573,12 +3697,27 @@ addSettingsMenuScrollbar() {
3573
3697
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3574
3698
  if (!settingsMenu) return;
3575
3699
 
3576
- const playerHeight = this.container.offsetHeight;
3577
- const maxMenuHeight = playerHeight - 100;
3700
+ const settingsBtn = document.querySelector('.settings-btn');
3701
+ if (!settingsBtn) return;
3578
3702
 
3579
- settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3580
- settingsMenu.style.overflowY = 'auto';
3581
- settingsMenu.style.overflowX = 'hidden';
3703
+ // helper to update menu height
3704
+ const updateMenuHeight = () => {
3705
+ if (settingsMenu.classList.contains('active')) {
3706
+ const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
3707
+ const btnRect = settingsBtn.getBoundingClientRect();
3708
+ const spaceBelow = containerRect.bottom - btnRect.bottom;
3709
+ const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
3710
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3711
+ settingsMenu.style.overflowY = 'auto';
3712
+ settingsMenu.style.overflowX = 'hidden';
3713
+ }
3714
+ };
3715
+
3716
+ // run initially
3717
+ updateMenuHeight();
3718
+
3719
+ // recalculate on window resize
3720
+ window.addEventListener('resize', updateMenuHeight);
3582
3721
 
3583
3722
  // Add scrollbar styling
3584
3723
  if (!document.getElementById('player-settings-scrollbar-style')) {
@@ -3611,9 +3750,43 @@ addSettingsMenuScrollbar() {
3611
3750
  * Bind settings menu events
3612
3751
  */
3613
3752
  bindSettingsMenuEvents() {
3753
+ const settingsBtn = this.controls?.querySelector('.settings-btn');
3614
3754
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3615
- if (!settingsMenu) return;
3755
+ if (!settingsMenu || !settingsBtn) return;
3616
3756
 
3757
+ // toggle menu on button click
3758
+ settingsBtn.addEventListener('click', (e) => {
3759
+ e.stopPropagation();
3760
+ settingsMenu.classList.toggle('active');
3761
+
3762
+ // when menu is opened, set max height and overflow
3763
+ if (settingsMenu.classList.contains('active')) {
3764
+ const settingsBtn = document.querySelector('.settings-btn');
3765
+ const containerRect = settingsMenu.parentElement.parentElement.getBoundingClientRect();
3766
+ const btnRect = settingsBtn.getBoundingClientRect();
3767
+ const spaceBelow = containerRect.bottom - btnRect.bottom;
3768
+ const maxMenuHeight = Math.max(100, Math.min(250, spaceBelow - 20));
3769
+
3770
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3771
+ settingsMenu.style.overflowY = 'auto';
3772
+ settingsMenu.style.overflowX = 'hidden';
3773
+ } else {
3774
+ settingsMenu.style.maxHeight = 'none';
3775
+ settingsMenu.style.overflowY = 'visible';
3776
+ }
3777
+
3778
+ });
3779
+
3780
+ // close menu when clicking outside
3781
+ document.addEventListener('click', (e) => {
3782
+ if (!settingsBtn?.contains(e.target) && !settingsMenu?.contains(e.target)) {
3783
+ settingsMenu?.classList.remove('active');
3784
+ settingsMenu.style.maxHeight = 'none';
3785
+ settingsMenu.style.overflowY = 'visible';
3786
+ }
3787
+ });
3788
+
3789
+ // manage clicks inside the menu
3617
3790
  settingsMenu.addEventListener('click', (e) => {
3618
3791
  e.stopPropagation();
3619
3792
 
@@ -5719,46 +5892,99 @@ parseTimeToSeconds(timeStr) {
5719
5892
  * Create visual chapter markers on the progress bar
5720
5893
  */
5721
5894
  createChapterMarkers() {
5722
- if (!this.progressContainer || !this.video || !this.chapters) {
5895
+ if (!this.progressContainer || !this.video || !this.chapters) return;
5896
+
5897
+ const duration = this.video.duration;
5898
+ if (!duration || isNaN(duration)) {
5899
+ // Wait for metadata
5900
+ const loadedMetadataHandler = () => {
5901
+ this.createChapterMarkers();
5902
+ this.video.removeEventListener('loadedmetadata', loadedMetadataHandler);
5903
+ };
5904
+ this.video.addEventListener('loadedmetadata', loadedMetadataHandler);
5723
5905
  return;
5724
5906
  }
5725
5907
 
5726
- // Create container for chapter markers
5908
+ // Remove existing markers
5909
+ const existingMarkers = this.progressContainer.querySelector('.chapter-markers-container');
5910
+ if (existingMarkers) {
5911
+ existingMarkers.remove();
5912
+ }
5913
+
5914
+ // Create container for chapter segments
5727
5915
  const markersContainer = document.createElement('div');
5728
5916
  markersContainer.className = 'chapter-markers-container';
5729
5917
 
5918
+ // Create segments for each chapter
5730
5919
  this.chapters.forEach((chapter, index) => {
5731
- const marker = document.createElement('div');
5732
- marker.className = 'chapter-marker';
5733
- marker.setAttribute('data-chapter-index', index);
5734
- marker.setAttribute('data-chapter-time', chapter.time);
5735
- marker.setAttribute('data-chapter-title', chapter.title);
5736
-
5737
- // Set custom color if provided
5738
- if (chapter.color) {
5739
- marker.style.backgroundColor = chapter.color;
5920
+ const nextChapter = this.chapters[index + 1];
5921
+ const startPercent = (chapter.time / duration) * 100;
5922
+ const endPercent = nextChapter ? (nextChapter.time / duration) * 100 : 100;
5923
+
5924
+ // Calculate segment width minus the gap
5925
+ const gapSize = nextChapter ? 6 : 0; // 6px gap between segments
5926
+ const widthPercent = endPercent - startPercent;
5927
+
5928
+ // Create segment container
5929
+ const segment = document.createElement('div');
5930
+ segment.className = 'chapter-segment';
5931
+ segment.style.cssText = `
5932
+ position: absolute;
5933
+ left: ${startPercent}%;
5934
+ top: 0;
5935
+ width: calc(${widthPercent}% - ${gapSize}px);
5936
+ height: 100%;
5937
+ background: rgba(255, 255, 255, 0.3);
5938
+ cursor: pointer;
5939
+ z-index: 3;
5940
+ transition: background 0.2s;
5941
+ pointer-events: none;
5942
+ `;
5943
+
5944
+ segment.setAttribute('data-chapter-index', index);
5945
+ segment.setAttribute('data-chapter-time', chapter.time);
5946
+ segment.setAttribute('data-chapter-title', chapter.title);
5947
+
5948
+ markersContainer.appendChild(segment);
5949
+
5950
+ // Add marker at the START of next segment (transparent divider)
5951
+ if (nextChapter) {
5952
+ const marker = document.createElement('div');
5953
+ marker.className = 'chapter-marker';
5954
+ marker.style.cssText = `
5955
+ position: absolute !important;
5956
+ left: ${endPercent}% !important;
5957
+ top: 0 !important;
5958
+ width: 6px !important;
5959
+ height: 100% !important;
5960
+ background: transparent !important;
5961
+ border: none !important;
5962
+ box-shadow: none !important;
5963
+ margin-left: -3px !important;
5964
+ cursor: pointer !important;
5965
+ z-index: 10 !important;
5966
+ `;
5967
+
5968
+ marker.setAttribute('data-chapter-time', nextChapter.time);
5969
+ marker.setAttribute('data-chapter-title', nextChapter.title);
5970
+
5971
+ // Click on marker to jump to chapter start
5972
+ marker.addEventListener('click', (e) => {
5973
+ e.stopPropagation();
5974
+ this.jumpToChapter(index + 1);
5975
+ });
5976
+
5977
+ markersContainer.appendChild(marker);
5740
5978
  }
5741
-
5742
- markersContainer.appendChild(marker);
5743
5979
  });
5744
5980
 
5745
5981
  // Insert markers container into progress container
5746
5982
  this.progressContainer.appendChild(markersContainer);
5747
5983
  this.chapterMarkersContainer = markersContainer;
5748
5984
 
5749
- // Update marker positions when video duration is known
5750
- if (this.video.duration && !isNaN(this.video.duration)) {
5751
- this.updateChapterMarkerPositions();
5752
- } else {
5753
- // Wait for metadata to be loaded
5754
- const loadedMetadataHandler = () => {
5755
- this.updateChapterMarkerPositions();
5756
- this.video.removeEventListener('loadedmetadata', loadedMetadataHandler);
5757
- };
5758
- this.video.addEventListener('loadedmetadata', loadedMetadataHandler);
5985
+ if (this.options.debug) {
5986
+ console.log(`Chapter markers created: ${this.chapters.length} segments`);
5759
5987
  }
5760
-
5761
- if (this.options.debug) console.log('📚 Chapter markers created on timeline');
5762
5988
  }
5763
5989
 
5764
5990
  /**
@@ -5783,70 +6009,278 @@ updateChapterMarkerPositions() {
5783
6009
  }
5784
6010
 
5785
6011
  /**
5786
- * Create chapter tooltip
6012
+ * Create chapter tooltip with title and image
5787
6013
  */
5788
6014
  createChapterTooltip() {
5789
- if (!this.progressContainer) {
5790
- return;
6015
+ if (!this.progressContainer) return;
6016
+
6017
+ // Remove existing chapter tooltip
6018
+ let chapterTooltip = this.progressContainer.querySelector('.chapter-tooltip');
6019
+ if (chapterTooltip) {
6020
+ chapterTooltip.remove();
5791
6021
  }
5792
6022
 
5793
- const tooltip = document.createElement('div');
5794
- tooltip.className = 'chapter-tooltip';
5795
- tooltip.style.opacity = '0';
5796
- tooltip.style.visibility = 'hidden';
6023
+ // Create chapter tooltip container (positioned ABOVE the time tooltip)
6024
+ chapterTooltip = document.createElement('div');
6025
+ chapterTooltip.className = 'chapter-tooltip';
6026
+ chapterTooltip.style.cssText = `
6027
+ position: absolute;
6028
+ bottom: calc(100% + 35px);
6029
+ left: 0;
6030
+ background: rgba(28, 28, 28, 0.95);
6031
+ color: #fff;
6032
+ border-radius: 4px;
6033
+ pointer-events: none;
6034
+ visibility: hidden;
6035
+ opacity: 0;
6036
+ z-index: 100001;
6037
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
6038
+ max-width: 300px;
6039
+ overflow: hidden;
6040
+ transform: translateX(-50%);
6041
+ transition: opacity 0.15s, visibility 0.15s;
6042
+ display: flex;
6043
+ flex-direction: column;
6044
+ `;
5797
6045
 
5798
- // Tooltip content structure
5799
- tooltip.innerHTML = `
5800
- <div class="chapter-tooltip-image"></div>
5801
- <div class="chapter-tooltip-title"></div>
5802
- <div class="chapter-tooltip-time"></div>
6046
+ // Create inner content structure
6047
+ chapterTooltip.innerHTML = `
6048
+ <div class="chapter-tooltip-content" style="display: flex; flex-direction: column; gap: 8px; padding: 8px;">
6049
+ <div class="chapter-tooltip-image" style="
6050
+ width: 100%;
6051
+ height: 120px;
6052
+ background-size: cover;
6053
+ background-position: center;
6054
+ border-radius: 3px;
6055
+ display: none;
6056
+ "></div>
6057
+ <div class="chapter-tooltip-info" style="display: flex; flex-direction: column; gap: 4px;">
6058
+ <div class="chapter-tooltip-title" style="
6059
+ font-size: 13px;
6060
+ font-weight: 600;
6061
+ color: #fff;
6062
+ max-width: 280px;
6063
+ overflow: hidden;
6064
+ text-overflow: ellipsis;
6065
+ white-space: nowrap;
6066
+ "></div>
6067
+ <div class="chapter-tooltip-time" style="
6068
+ font-size: 12px;
6069
+ font-weight: 400;
6070
+ color: rgba(255, 255, 255, 0.8);
6071
+ "></div>
6072
+ </div>
6073
+ </div>
5803
6074
  `;
5804
6075
 
5805
- this.progressContainer.appendChild(tooltip);
5806
- this.chapterTooltip = tooltip;
6076
+ this.progressContainer.appendChild(chapterTooltip);
6077
+ this.chapterTooltip = chapterTooltip;
5807
6078
 
5808
- if (this.options.debug) console.log('📚 Chapter tooltip created');
6079
+ if (this.options.debug) {
6080
+ console.log('Chapter tooltip created');
6081
+ }
5809
6082
  }
5810
6083
 
5811
6084
  /**
5812
- * Bind chapter-related events
6085
+ * Bind chapter-related events - tooltip on progressbar mousemove
5813
6086
  */
5814
6087
  bindChapterEvents() {
5815
- if (!this.chapterMarkersContainer || !this.chapterTooltip) {
5816
- return;
6088
+ if (!this.progressContainer) return;
6089
+
6090
+ // Remove existing chapter tooltip if present
6091
+ let chapterTooltip = this.progressContainer.querySelector('.chapter-tooltip-hover');
6092
+ if (chapterTooltip) {
6093
+ chapterTooltip.remove();
5817
6094
  }
5818
6095
 
5819
- // Hover on chapter markers
5820
- const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
6096
+ // Create chapter tooltip
6097
+ chapterTooltip = document.createElement('div');
6098
+ chapterTooltip.className = 'chapter-tooltip-hover';
6099
+ chapterTooltip.style.cssText = `
6100
+ position: absolute;
6101
+ bottom: calc(100% + 35px);
6102
+ left: 0;
6103
+ background: rgba(28, 28, 28, 0.95);
6104
+ color: #fff;
6105
+ border-radius: 4px;
6106
+ padding: 8px;
6107
+ pointer-events: none;
6108
+ visibility: hidden;
6109
+ opacity: 0;
6110
+ z-index: 100001;
6111
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
6112
+ max-width: 300px;
6113
+ transform: translateX(-50%);
6114
+ transition: opacity 0.15s, visibility 0.15s;
6115
+ `;
5821
6116
 
5822
- markers.forEach((marker, index) => {
5823
- marker.addEventListener('mouseenter', (e) => {
5824
- this.showChapterTooltip(index, e);
5825
- });
6117
+ this.progressContainer.appendChild(chapterTooltip);
6118
+ this.chapterTooltip = chapterTooltip;
5826
6119
 
5827
- marker.addEventListener('mousemove', (e) => {
5828
- this.updateChapterTooltipPosition(e);
5829
- });
6120
+ // Get player container for edge detection
6121
+ const getPlayerBounds = () => {
6122
+ return this.container ? this.container.getBoundingClientRect() : null;
6123
+ };
5830
6124
 
5831
- marker.addEventListener('mouseleave', () => {
5832
- this.hideChapterTooltip();
5833
- });
6125
+ // Mousemove handler to show tooltip with title and image
6126
+ this.progressContainer.addEventListener('mousemove', (e) => {
6127
+ if (!this.video || !this.video.duration || !this.chapters || this.chapters.length === 0) {
6128
+ return;
6129
+ }
5834
6130
 
5835
- // Click to jump to chapter
5836
- marker.addEventListener('click', (e) => {
5837
- e.stopPropagation();
5838
- this.jumpToChapter(index);
5839
- });
6131
+ const rect = this.progressContainer.getBoundingClientRect();
6132
+ const playerRect = getPlayerBounds();
6133
+ const mouseX = e.clientX - rect.left;
6134
+ const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
6135
+ const time = percentage * this.video.duration;
6136
+
6137
+ // Find chapter at current time
6138
+ let currentChapter = null;
6139
+ for (let i = this.chapters.length - 1; i >= 0; i--) {
6140
+ if (time >= this.chapters[i].time) {
6141
+ currentChapter = this.chapters[i];
6142
+ break;
6143
+ }
6144
+ }
6145
+
6146
+ if (currentChapter) {
6147
+ // Build tooltip HTML
6148
+ let tooltipHTML = '<div style="display: flex; flex-direction: column; gap: 6px;">';
6149
+
6150
+ // Add image if available
6151
+ if (currentChapter.image) {
6152
+ tooltipHTML += `
6153
+ <div style="
6154
+ width: 100%;
6155
+ height: 120px;
6156
+ background-image: url('${currentChapter.image}');
6157
+ background-size: cover;
6158
+ background-position: center;
6159
+ border-radius: 3px;
6160
+ "></div>
6161
+ `;
6162
+ }
6163
+
6164
+ // Add title
6165
+ tooltipHTML += `
6166
+ <div style="
6167
+ font-size: 13px;
6168
+ font-weight: 600;
6169
+ max-width: 280px;
6170
+ overflow: hidden;
6171
+ text-overflow: ellipsis;
6172
+ white-space: nowrap;
6173
+ ">
6174
+ ${currentChapter.title}
6175
+ </div>
6176
+ `;
6177
+
6178
+ // Add time
6179
+ tooltipHTML += `
6180
+ <div style="
6181
+ font-size: 12px;
6182
+ font-weight: 400;
6183
+ color: rgba(255, 255, 255, 0.8);
6184
+ ">
6185
+ ${this.formatTime(currentChapter.time)}
6186
+ </div>
6187
+ `;
6188
+
6189
+ tooltipHTML += '</div>';
6190
+
6191
+ chapterTooltip.innerHTML = tooltipHTML;
6192
+ chapterTooltip.style.visibility = 'visible';
6193
+ chapterTooltip.style.opacity = '1';
6194
+
6195
+ // Position tooltip with edge detection
6196
+ setTimeout(() => {
6197
+ const tooltipWidth = chapterTooltip.offsetWidth;
6198
+ const tooltipHalfWidth = tooltipWidth / 2;
6199
+ const absoluteX = e.clientX;
6200
+
6201
+ if (playerRect) {
6202
+ // Left edge
6203
+ if (absoluteX - tooltipHalfWidth < playerRect.left) {
6204
+ chapterTooltip.style.left = `${playerRect.left - rect.left + tooltipHalfWidth}px`;
6205
+ }
6206
+ // Right edge
6207
+ else if (absoluteX + tooltipHalfWidth > playerRect.right) {
6208
+ chapterTooltip.style.left = `${playerRect.right - rect.left - tooltipHalfWidth}px`;
6209
+ }
6210
+ // Normal center
6211
+ else {
6212
+ chapterTooltip.style.left = `${mouseX}px`;
6213
+ }
6214
+ } else {
6215
+ chapterTooltip.style.left = `${mouseX}px`;
6216
+ }
6217
+ }, 0);
6218
+ } else {
6219
+ chapterTooltip.style.visibility = 'hidden';
6220
+ chapterTooltip.style.opacity = '0';
6221
+ }
6222
+ });
6223
+
6224
+ // Mouseleave handler
6225
+ this.progressContainer.addEventListener('mouseleave', () => {
6226
+ chapterTooltip.style.visibility = 'hidden';
6227
+ chapterTooltip.style.opacity = '0';
5840
6228
  });
5841
6229
 
5842
6230
  // Update active chapter during playback
5843
6231
  if (this.video) {
5844
- this.video.addEventListener('timeupdate', () => {
5845
- this.updateActiveChapter();
5846
- });
6232
+ this.video.addEventListener('timeupdate', () => this.updateActiveChapter());
5847
6233
  }
5848
6234
 
5849
- if (this.options.debug) console.log('📚 Chapter events bound');
6235
+ if (this.options.debug) {
6236
+ console.log('Chapter events bound with tooltip');
6237
+ }
6238
+ }
6239
+
6240
+ /**
6241
+ * Update chapter name in title overlay dynamically
6242
+ */
6243
+ updateChapterInTitleOverlay() {
6244
+ if (!this.video || !this.chapters || this.chapters.length === 0) return;
6245
+
6246
+ const titleOverlay = this.container ? this.container.querySelector('.title-overlay') : null;
6247
+ if (!titleOverlay) return;
6248
+
6249
+ // Find or create chapter name element
6250
+ let chapterElement = titleOverlay.querySelector('.chapter-name');
6251
+ if (!chapterElement) {
6252
+ chapterElement = document.createElement('div');
6253
+ chapterElement.className = 'chapter-name';
6254
+ chapterElement.style.cssText = `
6255
+ font-size: 13px;
6256
+ font-weight: 500;
6257
+ color: rgba(255, 255, 255, 0.9);
6258
+ margin-top: 6px;
6259
+ max-width: 400px;
6260
+ overflow: hidden;
6261
+ text-overflow: ellipsis;
6262
+ white-space: nowrap;
6263
+ `;
6264
+ titleOverlay.appendChild(chapterElement);
6265
+ }
6266
+
6267
+ // Find current chapter
6268
+ const currentTime = this.video.currentTime;
6269
+ let currentChapter = null;
6270
+ for (let i = this.chapters.length - 1; i >= 0; i--) {
6271
+ if (currentTime >= this.chapters[i].time) {
6272
+ currentChapter = this.chapters[i];
6273
+ break;
6274
+ }
6275
+ }
6276
+
6277
+ // Update or hide chapter name
6278
+ if (currentChapter) {
6279
+ chapterElement.textContent = currentChapter.title;
6280
+ chapterElement.style.display = 'block';
6281
+ } else {
6282
+ chapterElement.style.display = 'none';
6283
+ }
5850
6284
  }
5851
6285
 
5852
6286
  /**
@@ -5950,9 +6384,7 @@ jumpToChapter(chapterIndex) {
5950
6384
  * Update active chapter marker during playback
5951
6385
  */
5952
6386
  updateActiveChapter() {
5953
- if (!this.video || !this.chapterMarkersContainer || !this.chapters) {
5954
- return;
5955
- }
6387
+ if (!this.video || !this.chapterMarkersContainer || !this.chapters) return;
5956
6388
 
5957
6389
  const currentTime = this.video.currentTime;
5958
6390
  const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
@@ -5974,6 +6406,9 @@ updateActiveChapter() {
5974
6406
  marker.classList.remove('active');
5975
6407
  }
5976
6408
  });
6409
+
6410
+ // Update chapter name in title overlay
6411
+ this.updateChapterInTitleOverlay();
5977
6412
  }
5978
6413
 
5979
6414
  /**