myetv-player 1.2.0 → 1.3.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 +131 -0
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +547 -102
  4. package/dist/myetv-player.min.js +486 -93
  5. package/package.json +35 -17
  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
  }
@@ -5719,46 +5902,99 @@ parseTimeToSeconds(timeStr) {
5719
5902
  * Create visual chapter markers on the progress bar
5720
5903
  */
5721
5904
  createChapterMarkers() {
5722
- if (!this.progressContainer || !this.video || !this.chapters) {
5905
+ if (!this.progressContainer || !this.video || !this.chapters) return;
5906
+
5907
+ const duration = this.video.duration;
5908
+ if (!duration || isNaN(duration)) {
5909
+ // Wait for metadata
5910
+ const loadedMetadataHandler = () => {
5911
+ this.createChapterMarkers();
5912
+ this.video.removeEventListener('loadedmetadata', loadedMetadataHandler);
5913
+ };
5914
+ this.video.addEventListener('loadedmetadata', loadedMetadataHandler);
5723
5915
  return;
5724
5916
  }
5725
5917
 
5726
- // Create container for chapter markers
5918
+ // Remove existing markers
5919
+ const existingMarkers = this.progressContainer.querySelector('.chapter-markers-container');
5920
+ if (existingMarkers) {
5921
+ existingMarkers.remove();
5922
+ }
5923
+
5924
+ // Create container for chapter segments
5727
5925
  const markersContainer = document.createElement('div');
5728
5926
  markersContainer.className = 'chapter-markers-container';
5729
5927
 
5928
+ // Create segments for each chapter
5730
5929
  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;
5930
+ const nextChapter = this.chapters[index + 1];
5931
+ const startPercent = (chapter.time / duration) * 100;
5932
+ const endPercent = nextChapter ? (nextChapter.time / duration) * 100 : 100;
5933
+
5934
+ // Calculate segment width minus the gap
5935
+ const gapSize = nextChapter ? 6 : 0; // 6px gap between segments
5936
+ const widthPercent = endPercent - startPercent;
5937
+
5938
+ // Create segment container
5939
+ const segment = document.createElement('div');
5940
+ segment.className = 'chapter-segment';
5941
+ segment.style.cssText = `
5942
+ position: absolute;
5943
+ left: ${startPercent}%;
5944
+ top: 0;
5945
+ width: calc(${widthPercent}% - ${gapSize}px);
5946
+ height: 100%;
5947
+ background: rgba(255, 255, 255, 0.3);
5948
+ cursor: pointer;
5949
+ z-index: 3;
5950
+ transition: background 0.2s;
5951
+ pointer-events: none;
5952
+ `;
5953
+
5954
+ segment.setAttribute('data-chapter-index', index);
5955
+ segment.setAttribute('data-chapter-time', chapter.time);
5956
+ segment.setAttribute('data-chapter-title', chapter.title);
5957
+
5958
+ markersContainer.appendChild(segment);
5959
+
5960
+ // Add marker at the START of next segment (transparent divider)
5961
+ if (nextChapter) {
5962
+ const marker = document.createElement('div');
5963
+ marker.className = 'chapter-marker';
5964
+ marker.style.cssText = `
5965
+ position: absolute !important;
5966
+ left: ${endPercent}% !important;
5967
+ top: 0 !important;
5968
+ width: 6px !important;
5969
+ height: 100% !important;
5970
+ background: transparent !important;
5971
+ border: none !important;
5972
+ box-shadow: none !important;
5973
+ margin-left: -3px !important;
5974
+ cursor: pointer !important;
5975
+ z-index: 10 !important;
5976
+ `;
5977
+
5978
+ marker.setAttribute('data-chapter-time', nextChapter.time);
5979
+ marker.setAttribute('data-chapter-title', nextChapter.title);
5980
+
5981
+ // Click on marker to jump to chapter start
5982
+ marker.addEventListener('click', (e) => {
5983
+ e.stopPropagation();
5984
+ this.jumpToChapter(index + 1);
5985
+ });
5986
+
5987
+ markersContainer.appendChild(marker);
5740
5988
  }
5741
-
5742
- markersContainer.appendChild(marker);
5743
5989
  });
5744
5990
 
5745
5991
  // Insert markers container into progress container
5746
5992
  this.progressContainer.appendChild(markersContainer);
5747
5993
  this.chapterMarkersContainer = markersContainer;
5748
5994
 
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);
5995
+ if (this.options.debug) {
5996
+ console.log(`Chapter markers created: ${this.chapters.length} segments`);
5759
5997
  }
5760
-
5761
- if (this.options.debug) console.log('📚 Chapter markers created on timeline');
5762
5998
  }
5763
5999
 
5764
6000
  /**
@@ -5783,70 +6019,278 @@ updateChapterMarkerPositions() {
5783
6019
  }
5784
6020
 
5785
6021
  /**
5786
- * Create chapter tooltip
6022
+ * Create chapter tooltip with title and image
5787
6023
  */
5788
6024
  createChapterTooltip() {
5789
- if (!this.progressContainer) {
5790
- return;
6025
+ if (!this.progressContainer) return;
6026
+
6027
+ // Remove existing chapter tooltip
6028
+ let chapterTooltip = this.progressContainer.querySelector('.chapter-tooltip');
6029
+ if (chapterTooltip) {
6030
+ chapterTooltip.remove();
5791
6031
  }
5792
6032
 
5793
- const tooltip = document.createElement('div');
5794
- tooltip.className = 'chapter-tooltip';
5795
- tooltip.style.opacity = '0';
5796
- tooltip.style.visibility = 'hidden';
6033
+ // Create chapter tooltip container (positioned ABOVE the time tooltip)
6034
+ chapterTooltip = document.createElement('div');
6035
+ chapterTooltip.className = 'chapter-tooltip';
6036
+ chapterTooltip.style.cssText = `
6037
+ position: absolute;
6038
+ bottom: calc(100% + 35px);
6039
+ left: 0;
6040
+ background: rgba(28, 28, 28, 0.95);
6041
+ color: #fff;
6042
+ border-radius: 4px;
6043
+ pointer-events: none;
6044
+ visibility: hidden;
6045
+ opacity: 0;
6046
+ z-index: 100001;
6047
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
6048
+ max-width: 300px;
6049
+ overflow: hidden;
6050
+ transform: translateX(-50%);
6051
+ transition: opacity 0.15s, visibility 0.15s;
6052
+ display: flex;
6053
+ flex-direction: column;
6054
+ `;
5797
6055
 
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>
6056
+ // Create inner content structure
6057
+ chapterTooltip.innerHTML = `
6058
+ <div class="chapter-tooltip-content" style="display: flex; flex-direction: column; gap: 8px; padding: 8px;">
6059
+ <div class="chapter-tooltip-image" style="
6060
+ width: 100%;
6061
+ height: 120px;
6062
+ background-size: cover;
6063
+ background-position: center;
6064
+ border-radius: 3px;
6065
+ display: none;
6066
+ "></div>
6067
+ <div class="chapter-tooltip-info" style="display: flex; flex-direction: column; gap: 4px;">
6068
+ <div class="chapter-tooltip-title" style="
6069
+ font-size: 13px;
6070
+ font-weight: 600;
6071
+ color: #fff;
6072
+ max-width: 280px;
6073
+ overflow: hidden;
6074
+ text-overflow: ellipsis;
6075
+ white-space: nowrap;
6076
+ "></div>
6077
+ <div class="chapter-tooltip-time" style="
6078
+ font-size: 12px;
6079
+ font-weight: 400;
6080
+ color: rgba(255, 255, 255, 0.8);
6081
+ "></div>
6082
+ </div>
6083
+ </div>
5803
6084
  `;
5804
6085
 
5805
- this.progressContainer.appendChild(tooltip);
5806
- this.chapterTooltip = tooltip;
6086
+ this.progressContainer.appendChild(chapterTooltip);
6087
+ this.chapterTooltip = chapterTooltip;
5807
6088
 
5808
- if (this.options.debug) console.log('📚 Chapter tooltip created');
6089
+ if (this.options.debug) {
6090
+ console.log('Chapter tooltip created');
6091
+ }
5809
6092
  }
5810
6093
 
5811
6094
  /**
5812
- * Bind chapter-related events
6095
+ * Bind chapter-related events - tooltip on progressbar mousemove
5813
6096
  */
5814
6097
  bindChapterEvents() {
5815
- if (!this.chapterMarkersContainer || !this.chapterTooltip) {
5816
- return;
6098
+ if (!this.progressContainer) return;
6099
+
6100
+ // Remove existing chapter tooltip if present
6101
+ let chapterTooltip = this.progressContainer.querySelector('.chapter-tooltip-hover');
6102
+ if (chapterTooltip) {
6103
+ chapterTooltip.remove();
5817
6104
  }
5818
6105
 
5819
- // Hover on chapter markers
5820
- const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
6106
+ // Create chapter tooltip
6107
+ chapterTooltip = document.createElement('div');
6108
+ chapterTooltip.className = 'chapter-tooltip-hover';
6109
+ chapterTooltip.style.cssText = `
6110
+ position: absolute;
6111
+ bottom: calc(100% + 35px);
6112
+ left: 0;
6113
+ background: rgba(28, 28, 28, 0.95);
6114
+ color: #fff;
6115
+ border-radius: 4px;
6116
+ padding: 8px;
6117
+ pointer-events: none;
6118
+ visibility: hidden;
6119
+ opacity: 0;
6120
+ z-index: 100001;
6121
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
6122
+ max-width: 300px;
6123
+ transform: translateX(-50%);
6124
+ transition: opacity 0.15s, visibility 0.15s;
6125
+ `;
5821
6126
 
5822
- markers.forEach((marker, index) => {
5823
- marker.addEventListener('mouseenter', (e) => {
5824
- this.showChapterTooltip(index, e);
5825
- });
6127
+ this.progressContainer.appendChild(chapterTooltip);
6128
+ this.chapterTooltip = chapterTooltip;
5826
6129
 
5827
- marker.addEventListener('mousemove', (e) => {
5828
- this.updateChapterTooltipPosition(e);
5829
- });
6130
+ // Get player container for edge detection
6131
+ const getPlayerBounds = () => {
6132
+ return this.container ? this.container.getBoundingClientRect() : null;
6133
+ };
5830
6134
 
5831
- marker.addEventListener('mouseleave', () => {
5832
- this.hideChapterTooltip();
5833
- });
6135
+ // Mousemove handler to show tooltip with title and image
6136
+ this.progressContainer.addEventListener('mousemove', (e) => {
6137
+ if (!this.video || !this.video.duration || !this.chapters || this.chapters.length === 0) {
6138
+ return;
6139
+ }
5834
6140
 
5835
- // Click to jump to chapter
5836
- marker.addEventListener('click', (e) => {
5837
- e.stopPropagation();
5838
- this.jumpToChapter(index);
5839
- });
6141
+ const rect = this.progressContainer.getBoundingClientRect();
6142
+ const playerRect = getPlayerBounds();
6143
+ const mouseX = e.clientX - rect.left;
6144
+ const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
6145
+ const time = percentage * this.video.duration;
6146
+
6147
+ // Find chapter at current time
6148
+ let currentChapter = null;
6149
+ for (let i = this.chapters.length - 1; i >= 0; i--) {
6150
+ if (time >= this.chapters[i].time) {
6151
+ currentChapter = this.chapters[i];
6152
+ break;
6153
+ }
6154
+ }
6155
+
6156
+ if (currentChapter) {
6157
+ // Build tooltip HTML
6158
+ let tooltipHTML = '<div style="display: flex; flex-direction: column; gap: 6px;">';
6159
+
6160
+ // Add image if available
6161
+ if (currentChapter.image) {
6162
+ tooltipHTML += `
6163
+ <div style="
6164
+ width: 100%;
6165
+ height: 120px;
6166
+ background-image: url('${currentChapter.image}');
6167
+ background-size: cover;
6168
+ background-position: center;
6169
+ border-radius: 3px;
6170
+ "></div>
6171
+ `;
6172
+ }
6173
+
6174
+ // Add title
6175
+ tooltipHTML += `
6176
+ <div style="
6177
+ font-size: 13px;
6178
+ font-weight: 600;
6179
+ max-width: 280px;
6180
+ overflow: hidden;
6181
+ text-overflow: ellipsis;
6182
+ white-space: nowrap;
6183
+ ">
6184
+ ${currentChapter.title}
6185
+ </div>
6186
+ `;
6187
+
6188
+ // Add time
6189
+ tooltipHTML += `
6190
+ <div style="
6191
+ font-size: 12px;
6192
+ font-weight: 400;
6193
+ color: rgba(255, 255, 255, 0.8);
6194
+ ">
6195
+ ${this.formatTime(currentChapter.time)}
6196
+ </div>
6197
+ `;
6198
+
6199
+ tooltipHTML += '</div>';
6200
+
6201
+ chapterTooltip.innerHTML = tooltipHTML;
6202
+ chapterTooltip.style.visibility = 'visible';
6203
+ chapterTooltip.style.opacity = '1';
6204
+
6205
+ // Position tooltip with edge detection
6206
+ setTimeout(() => {
6207
+ const tooltipWidth = chapterTooltip.offsetWidth;
6208
+ const tooltipHalfWidth = tooltipWidth / 2;
6209
+ const absoluteX = e.clientX;
6210
+
6211
+ if (playerRect) {
6212
+ // Left edge
6213
+ if (absoluteX - tooltipHalfWidth < playerRect.left) {
6214
+ chapterTooltip.style.left = `${playerRect.left - rect.left + tooltipHalfWidth}px`;
6215
+ }
6216
+ // Right edge
6217
+ else if (absoluteX + tooltipHalfWidth > playerRect.right) {
6218
+ chapterTooltip.style.left = `${playerRect.right - rect.left - tooltipHalfWidth}px`;
6219
+ }
6220
+ // Normal center
6221
+ else {
6222
+ chapterTooltip.style.left = `${mouseX}px`;
6223
+ }
6224
+ } else {
6225
+ chapterTooltip.style.left = `${mouseX}px`;
6226
+ }
6227
+ }, 0);
6228
+ } else {
6229
+ chapterTooltip.style.visibility = 'hidden';
6230
+ chapterTooltip.style.opacity = '0';
6231
+ }
6232
+ });
6233
+
6234
+ // Mouseleave handler
6235
+ this.progressContainer.addEventListener('mouseleave', () => {
6236
+ chapterTooltip.style.visibility = 'hidden';
6237
+ chapterTooltip.style.opacity = '0';
5840
6238
  });
5841
6239
 
5842
6240
  // Update active chapter during playback
5843
6241
  if (this.video) {
5844
- this.video.addEventListener('timeupdate', () => {
5845
- this.updateActiveChapter();
5846
- });
6242
+ this.video.addEventListener('timeupdate', () => this.updateActiveChapter());
6243
+ }
6244
+
6245
+ if (this.options.debug) {
6246
+ console.log('Chapter events bound with tooltip');
6247
+ }
6248
+ }
6249
+
6250
+ /**
6251
+ * Update chapter name in title overlay dynamically
6252
+ */
6253
+ updateChapterInTitleOverlay() {
6254
+ if (!this.video || !this.chapters || this.chapters.length === 0) return;
6255
+
6256
+ const titleOverlay = this.container ? this.container.querySelector('.title-overlay') : null;
6257
+ if (!titleOverlay) return;
6258
+
6259
+ // Find or create chapter name element
6260
+ let chapterElement = titleOverlay.querySelector('.chapter-name');
6261
+ if (!chapterElement) {
6262
+ chapterElement = document.createElement('div');
6263
+ chapterElement.className = 'chapter-name';
6264
+ chapterElement.style.cssText = `
6265
+ font-size: 13px;
6266
+ font-weight: 500;
6267
+ color: rgba(255, 255, 255, 0.9);
6268
+ margin-top: 6px;
6269
+ max-width: 400px;
6270
+ overflow: hidden;
6271
+ text-overflow: ellipsis;
6272
+ white-space: nowrap;
6273
+ `;
6274
+ titleOverlay.appendChild(chapterElement);
5847
6275
  }
5848
6276
 
5849
- if (this.options.debug) console.log('📚 Chapter events bound');
6277
+ // Find current chapter
6278
+ const currentTime = this.video.currentTime;
6279
+ let currentChapter = null;
6280
+ for (let i = this.chapters.length - 1; i >= 0; i--) {
6281
+ if (currentTime >= this.chapters[i].time) {
6282
+ currentChapter = this.chapters[i];
6283
+ break;
6284
+ }
6285
+ }
6286
+
6287
+ // Update or hide chapter name
6288
+ if (currentChapter) {
6289
+ chapterElement.textContent = currentChapter.title;
6290
+ chapterElement.style.display = 'block';
6291
+ } else {
6292
+ chapterElement.style.display = 'none';
6293
+ }
5850
6294
  }
5851
6295
 
5852
6296
  /**
@@ -5950,9 +6394,7 @@ jumpToChapter(chapterIndex) {
5950
6394
  * Update active chapter marker during playback
5951
6395
  */
5952
6396
  updateActiveChapter() {
5953
- if (!this.video || !this.chapterMarkersContainer || !this.chapters) {
5954
- return;
5955
- }
6397
+ if (!this.video || !this.chapterMarkersContainer || !this.chapters) return;
5956
6398
 
5957
6399
  const currentTime = this.video.currentTime;
5958
6400
  const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
@@ -5974,6 +6416,9 @@ updateActiveChapter() {
5974
6416
  marker.classList.remove('active');
5975
6417
  }
5976
6418
  });
6419
+
6420
+ // Update chapter name in title overlay
6421
+ this.updateChapterInTitleOverlay();
5977
6422
  }
5978
6423
 
5979
6424
  /**