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.
- package/css/myetv-player.css +242 -168
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +638 -203
- package/dist/myetv-player.min.js +548 -170
- package/package.json +35 -16
- package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
- package/plugins/vimeo/myetv-player-vimeo.js +80 -49
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
- package/.github/workflows/codeql.yml +0 -100
- package/.github/workflows/npm-publish.yml +0 -30
- package/SECURITY.md +0 -50
- package/build.js +0 -195
- package/scss/README.md +0 -161
- package/scss/_audio-player.scss +0 -21
- package/scss/_base.scss +0 -116
- package/scss/_controls.scss +0 -204
- package/scss/_loading.scss +0 -111
- package/scss/_menus.scss +0 -432
- package/scss/_mixins.scss +0 -112
- package/scss/_poster.scss +0 -8
- package/scss/_progress-bar.scss +0 -319
- package/scss/_resolution.scss +0 -68
- package/scss/_responsive.scss +0 -1368
- package/scss/_themes.scss +0 -30
- package/scss/_title-overlay.scss +0 -60
- package/scss/_tooltips.scss +0 -7
- package/scss/_variables.scss +0 -49
- package/scss/_video.scss +0 -221
- package/scss/_volume.scss +0 -122
- package/scss/_watermark.scss +0 -128
- package/scss/myetv-player.scss +0 -51
- package/scss/package.json +0 -16
- package/src/README.md +0 -560
- package/src/chapters.js +0 -521
- package/src/controls.js +0 -1242
- package/src/core.js +0 -1922
- package/src/events.js +0 -537
- package/src/fullscreen.js +0 -82
- package/src/i18n.js +0 -374
- package/src/playlist.js +0 -177
- package/src/plugins.js +0 -384
- package/src/quality.js +0 -963
- package/src/streaming.js +0 -346
- package/src/subtitles.js +0 -524
- package/src/utils.js +0 -65
- package/src/watermark.js +0 -246
package/src/i18n.js
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
// i18n Module for MYETV Video Player
|
|
2
|
-
// Conservative modularization - original code preserved exactly
|
|
3
|
-
// Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
|
-
|
|
5
|
-
// i18n System for Video Player - Javascript locales - - Created by [https://www.myetv.tv](https://www.myetv.tv) [https://oskarcosimo.com](https://oskarcosimo.com)
|
|
6
|
-
class VideoPlayerI18n {
|
|
7
|
-
constructor() {
|
|
8
|
-
// First define the translations
|
|
9
|
-
this.translations = {
|
|
10
|
-
// Italiano
|
|
11
|
-
'it': {
|
|
12
|
-
'subtitles': 'Sottotitoli (C)',
|
|
13
|
-
'subtitlesdisable': 'Disabilita Sottotitoli',
|
|
14
|
-
'subtitlesenable': 'Abilita Sottotitoli',
|
|
15
|
-
'play_pause': 'Play/Pausa (Spazio)',
|
|
16
|
-
'mute_unmute': 'Muta/Smuta (M)',
|
|
17
|
-
'volume': 'Volume',
|
|
18
|
-
'playback_speed': 'Velocità riproduzione',
|
|
19
|
-
'video_quality': 'Qualità video',
|
|
20
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
21
|
-
'fullscreen': 'Schermo intero (F)',
|
|
22
|
-
'auto': 'Auto',
|
|
23
|
-
'brand_logo': 'Logo',
|
|
24
|
-
'next_video': 'Video successivo (N)',
|
|
25
|
-
'prev_video': 'Video precedente (P)',
|
|
26
|
-
'playlist_next': 'Avanti',
|
|
27
|
-
'playlist_prev': 'Indietro',
|
|
28
|
-
'settings_menu': 'Impostazioni'
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
// English
|
|
32
|
-
'en': {
|
|
33
|
-
'subtitles': 'Subtitles (C)',
|
|
34
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
35
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
36
|
-
'play_pause': 'Play/Pause (Space)',
|
|
37
|
-
'mute_unmute': 'Mute/Unmute (M)',
|
|
38
|
-
'volume': 'Volume',
|
|
39
|
-
'playback_speed': 'Playback speed',
|
|
40
|
-
'video_quality': 'Video quality',
|
|
41
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
42
|
-
'fullscreen': 'Fullscreen (F)',
|
|
43
|
-
'auto': 'Auto',
|
|
44
|
-
'brand_logo': 'Brand logo',
|
|
45
|
-
'next_video': 'Next video (N)',
|
|
46
|
-
'prev_video': 'Previous video (P)',
|
|
47
|
-
'playlist_next': 'Next',
|
|
48
|
-
'playlist_prev': 'Previous',
|
|
49
|
-
'next_video': 'Next video (N)',
|
|
50
|
-
'prev_video': 'Previous video (P)',
|
|
51
|
-
'playlist_next': 'Next',
|
|
52
|
-
'playlist_prev': 'Previous',
|
|
53
|
-
'settings_menu': 'Settings'
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// Español
|
|
57
|
-
'es': {
|
|
58
|
-
'subtitles': 'Subtítulos (C)',
|
|
59
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
60
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
61
|
-
'play_pause': 'Reproducir/Pausar (Espacio)',
|
|
62
|
-
'mute_unmute': 'Silenciar (M)',
|
|
63
|
-
'volume': 'Volumen',
|
|
64
|
-
'playback_speed': 'Velocidad de reproducción',
|
|
65
|
-
'video_quality': 'Calidad de vídeo',
|
|
66
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
67
|
-
'fullscreen': 'Pantalla completa (F)',
|
|
68
|
-
'auto': 'Auto',
|
|
69
|
-
'brand_logo': 'Logo de marca',
|
|
70
|
-
'next_video': 'Siguiente vídeo (N)',
|
|
71
|
-
'prev_video': 'Vídeo anterior (P)',
|
|
72
|
-
'playlist_next': 'Siguiente',
|
|
73
|
-
'playlist_prev': 'Anterior',
|
|
74
|
-
'settings_menu': 'Settings'
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
// Français
|
|
78
|
-
'fr': {
|
|
79
|
-
'subtitles': 'Sous-titres (C)',
|
|
80
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
81
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
82
|
-
'play_pause': 'Lecture/Pause (Espace)',
|
|
83
|
-
'mute_unmute': 'Muet (M)',
|
|
84
|
-
'volume': 'Volume',
|
|
85
|
-
'playback_speed': 'Vitesse de lecture',
|
|
86
|
-
'video_quality': 'Qualité vidéo',
|
|
87
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
88
|
-
'fullscreen': 'Plein écran (F)',
|
|
89
|
-
'auto': 'Auto',
|
|
90
|
-
'brand_logo': 'Logo de marque',
|
|
91
|
-
'next_video': 'Vidéo suivante (N)',
|
|
92
|
-
'prev_video': 'Vidéo précédente (P)',
|
|
93
|
-
'playlist_next': 'Suivant',
|
|
94
|
-
'playlist_prev': 'Précédent',
|
|
95
|
-
'settings_menu': 'Settings'
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
// Deutsch
|
|
99
|
-
'de': {
|
|
100
|
-
'subtitles': 'Untertitel (C)',
|
|
101
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
102
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
103
|
-
'play_pause': 'Abspielen/Pausieren (Leertaste)',
|
|
104
|
-
'mute_unmute': 'Stumm (M)',
|
|
105
|
-
'volume': 'Lautstärke',
|
|
106
|
-
'playback_speed': 'Wiedergabegeschwindigkeit',
|
|
107
|
-
'video_quality': 'Videoqualität',
|
|
108
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
109
|
-
'fullscreen': 'Vollbild (F)',
|
|
110
|
-
'auto': 'Auto',
|
|
111
|
-
'brand_logo': 'Markenlogo',
|
|
112
|
-
'next_video': 'Nächstes Video (N)',
|
|
113
|
-
'prev_video': 'Vorheriges Video (P)',
|
|
114
|
-
'playlist_next': 'Weiter',
|
|
115
|
-
'playlist_prev': 'Zurück',
|
|
116
|
-
'settings_menu': 'Settings'
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// Português
|
|
120
|
-
'pt': {
|
|
121
|
-
'subtitles': 'Legendas (C)',
|
|
122
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
123
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
124
|
-
'play_pause': 'Reproduzir/Pausar (Espaço)',
|
|
125
|
-
'mute_unmute': 'Silenciar (M)',
|
|
126
|
-
'volume': 'Volume',
|
|
127
|
-
'playback_speed': 'Velocidade de reprodução',
|
|
128
|
-
'video_quality': 'Qualidade do vídeo',
|
|
129
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
130
|
-
'fullscreen': 'Tela cheia (F)',
|
|
131
|
-
'auto': 'Auto',
|
|
132
|
-
'brand_logo': 'Logo da marca',
|
|
133
|
-
'next_video': 'Próximo vídeo (N)',
|
|
134
|
-
'prev_video': 'Vídeo anterior (P)',
|
|
135
|
-
'playlist_next': 'Próximo',
|
|
136
|
-
'playlist_prev': 'Anterior',
|
|
137
|
-
'settings_menu': 'Settings'
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
// 中文
|
|
141
|
-
'zh': {
|
|
142
|
-
'subtitles': '字幕 (C)',
|
|
143
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
144
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
145
|
-
'play_pause': '播放/暂停 (空格)',
|
|
146
|
-
'mute_unmute': '静音 (M)',
|
|
147
|
-
'volume': '音量',
|
|
148
|
-
'playback_speed': '播放速度',
|
|
149
|
-
'video_quality': '视频质量',
|
|
150
|
-
'picture_in_picture': '画中画 (P)',
|
|
151
|
-
'fullscreen': '全屏 (F)',
|
|
152
|
-
'auto': '自动',
|
|
153
|
-
'brand_logo': '品牌标志',
|
|
154
|
-
'next_video': '下一个视频 (N)',
|
|
155
|
-
'prev_video': '上一个视频 (P)',
|
|
156
|
-
'playlist_next': '下一个',
|
|
157
|
-
'playlist_prev': '上一个',
|
|
158
|
-
'settings_menu': 'Settings'
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
// 日本語
|
|
162
|
-
'ja': {
|
|
163
|
-
'subtitles': '字幕 (C)',
|
|
164
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
165
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
166
|
-
'play_pause': '再生/一時停止 (スペース)',
|
|
167
|
-
'mute_unmute': 'ミュート (M)',
|
|
168
|
-
'volume': '音量',
|
|
169
|
-
'playback_speed': '再生速度',
|
|
170
|
-
'video_quality': '動画品質',
|
|
171
|
-
'picture_in_picture': 'ピクチャーインピクチャー (P)',
|
|
172
|
-
'fullscreen': 'フルスクリーン (F)',
|
|
173
|
-
'auto': '自動',
|
|
174
|
-
'brand_logo': 'ブランドロゴ',
|
|
175
|
-
'next_video': '次の動画 (N)',
|
|
176
|
-
'prev_video': '前の動画 (P)',
|
|
177
|
-
'playlist_next': '次へ',
|
|
178
|
-
'playlist_prev': '前へ',
|
|
179
|
-
'settings_menu': 'Settings'
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
// Русский
|
|
183
|
-
'ru': {
|
|
184
|
-
'subtitles': 'Субтитры (C)',
|
|
185
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
186
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
187
|
-
'play_pause': 'Воспроизведение/Пауза (Пробел)',
|
|
188
|
-
'mute_unmute': 'Звук (M)',
|
|
189
|
-
'volume': 'Громкость',
|
|
190
|
-
'playback_speed': 'Скорость воспроизведения',
|
|
191
|
-
'video_quality': 'Качество видео',
|
|
192
|
-
'picture_in_picture': 'Картинка в картинке (P)',
|
|
193
|
-
'fullscreen': 'Полный экран (F)',
|
|
194
|
-
'auto': 'Авто',
|
|
195
|
-
'brand_logo': 'Логотип бренда',
|
|
196
|
-
'next_video': 'Следующее видео (N)',
|
|
197
|
-
'prev_video': 'Предыдущее видео (P)',
|
|
198
|
-
'playlist_next': 'Далее',
|
|
199
|
-
'playlist_prev': 'Назад',
|
|
200
|
-
'settings_menu': 'Settings'
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
// العربية
|
|
204
|
-
'ar': {
|
|
205
|
-
'subtitles': 'الترجمة (C)',
|
|
206
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
207
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
208
|
-
'play_pause': 'تشغيل/إيقاف مؤقت (مسافة)',
|
|
209
|
-
'mute_unmute': 'كتم الصوت (M)',
|
|
210
|
-
'volume': 'مستوى الصوت',
|
|
211
|
-
'playback_speed': 'سرعة التشغيل',
|
|
212
|
-
'video_quality': 'جودة الفيديو',
|
|
213
|
-
'picture_in_picture': 'صورة في صورة (P)',
|
|
214
|
-
'fullscreen': 'ملء الشاشة (F)',
|
|
215
|
-
'auto': 'تلقائي',
|
|
216
|
-
'brand_logo': 'شعار العلامة التجارية',
|
|
217
|
-
'next_video': 'الفيديو التالي (N)',
|
|
218
|
-
'prev_video': 'الفيديو السابق (P)',
|
|
219
|
-
'playlist_next': 'التالي',
|
|
220
|
-
'playlist_prev': 'السابق',
|
|
221
|
-
'settings_menu': 'Settings'
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// THEN detect language (after defining translations)
|
|
226
|
-
this.currentLanguage = this.detectLanguage();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Detect browser language
|
|
230
|
-
detectLanguage() {
|
|
231
|
-
try {
|
|
232
|
-
const lang = navigator.language || navigator.userLanguage || 'en';
|
|
233
|
-
const shortLang = lang.split('-')[0].toLowerCase();
|
|
234
|
-
|
|
235
|
-
// If we have the translation, use it, otherwise fallback to English
|
|
236
|
-
return this.translations[shortLang] ? shortLang : 'en';
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.warn('Language detection error:', error);
|
|
239
|
-
return 'en';
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Get translation for key
|
|
244
|
-
t(key) {
|
|
245
|
-
try {
|
|
246
|
-
return this.translations[this.currentLanguage]?.[key] ||
|
|
247
|
-
this.translations['en']?.[key] ||
|
|
248
|
-
key;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.warn('Translation error for key:', key, error);
|
|
251
|
-
return key;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Change language
|
|
256
|
-
setLanguage(lang) {
|
|
257
|
-
try {
|
|
258
|
-
if (this.translations[lang]) {
|
|
259
|
-
this.currentLanguage = lang;
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
return false;
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.warn('Language change error:', error);
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Get supported languages
|
|
270
|
-
getSupportedLanguages() {
|
|
271
|
-
try {
|
|
272
|
-
return Object.keys(this.translations);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
console.warn('Error getting languages:', error);
|
|
275
|
-
return ['en'];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Get current language
|
|
280
|
-
getCurrentLanguage() {
|
|
281
|
-
return this.currentLanguage || 'en';
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Add new translations
|
|
285
|
-
addTranslations(lang, translations) {
|
|
286
|
-
try {
|
|
287
|
-
// SECURITY: Prevent prototype pollution by validating lang parameter
|
|
288
|
-
if (!this.isValidLanguageKey(lang)) {
|
|
289
|
-
console.warn('Invalid language key rejected:', lang);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (!this.translations[lang]) {
|
|
294
|
-
this.translations[lang] = {};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// SECURITY: Only copy safe properties from translations object
|
|
298
|
-
for (const key in translations) {
|
|
299
|
-
if (translations.hasOwnProperty(key) && this.isValidLanguageKey(key)) {
|
|
300
|
-
this.translations[lang][key] = translations[key];
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.warn('Error adding translations:', error);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// SECURITY: Validate that a key is safe (not a prototype polluting key)
|
|
309
|
-
isValidLanguageKey(key) {
|
|
310
|
-
if (typeof key !== 'string') return false;
|
|
311
|
-
|
|
312
|
-
// Reject dangerous prototype-polluting keys
|
|
313
|
-
const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
|
|
314
|
-
if (dangerousKeys.includes(key.toLowerCase())) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Accept only alphanumeric keys with underscore/dash (e.g., 'en', 'it', 'play_pause')
|
|
319
|
-
return /^[a-zA-Z0-9_-]+$/.test(key);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Safe initialization with error handling
|
|
324
|
-
let VideoPlayerTranslations;
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
VideoPlayerTranslations = new VideoPlayerI18n();
|
|
328
|
-
} catch (error) {
|
|
329
|
-
console.warn('i18n initialization error, using fallback:', error);
|
|
330
|
-
|
|
331
|
-
// Fallback if initialization fails
|
|
332
|
-
VideoPlayerTranslations = {
|
|
333
|
-
currentLanguage: 'en',
|
|
334
|
-
t: function (key) {
|
|
335
|
-
const fallback = {
|
|
336
|
-
'subtitles': 'Subtitles (C)',
|
|
337
|
-
'subtitlesdisable': 'Disable Subtitles',
|
|
338
|
-
'subtitlesenable': 'Enable Subtitles',
|
|
339
|
-
'play_pause': 'Play/Pause (Space)',
|
|
340
|
-
'mute_unmute': 'Mute/Unmute (M)',
|
|
341
|
-
'volume': 'Volume',
|
|
342
|
-
'playback_speed': 'Playback speed',
|
|
343
|
-
'video_quality': 'Video quality',
|
|
344
|
-
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
345
|
-
'fullscreen': 'Fullscreen (F)',
|
|
346
|
-
'auto': 'Auto',
|
|
347
|
-
'brand_logo': 'Brand logo',
|
|
348
|
-
'settings_menu': 'Settings'
|
|
349
|
-
};
|
|
350
|
-
return fallback[key] || key;
|
|
351
|
-
},
|
|
352
|
-
setLanguage: function () { return false; },
|
|
353
|
-
getCurrentLanguage: function () { return 'en'; },
|
|
354
|
-
getSupportedLanguages: function () { return ['en']; }
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Helper function for ease of use
|
|
359
|
-
function t(key) {
|
|
360
|
-
try {
|
|
361
|
-
return VideoPlayerTranslations.t(key);
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.warn('Helper t() error:', error);
|
|
364
|
-
return key;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Ensure it's globally available
|
|
369
|
-
if (typeof window !== 'undefined') {
|
|
370
|
-
window.VideoPlayerTranslations = VideoPlayerTranslations;
|
|
371
|
-
window.t = t;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
export default VideoPlayerI18n;
|
package/src/playlist.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
// Playlist Module for MYETV Video Player
|
|
2
|
-
// Conservative modularization - original code preserved exactly
|
|
3
|
-
// Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
|
-
|
|
5
|
-
detectPlaylist() {
|
|
6
|
-
if (!this.options.playlistEnabled) {
|
|
7
|
-
this.isPlaylistActive = false;
|
|
8
|
-
this.hidePlaylistControls();
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const playlistId = this.video.getAttribute('data-playlist-id');
|
|
13
|
-
const playlistIndex = parseInt(this.video.getAttribute('data-playlist-index'));
|
|
14
|
-
|
|
15
|
-
if (playlistId && !isNaN(playlistIndex)) {
|
|
16
|
-
this.playlistId = playlistId;
|
|
17
|
-
this.currentPlaylistIndex = playlistIndex;
|
|
18
|
-
this.loadPlaylistData();
|
|
19
|
-
this.isPlaylistActive = true;
|
|
20
|
-
this.showPlaylistControls();
|
|
21
|
-
|
|
22
|
-
if (this.options.debug) {
|
|
23
|
-
console.log(`🎵 Playlist detected: ${playlistId}, video ${playlistIndex}/${this.playlist.length - 1}`);
|
|
24
|
-
}
|
|
25
|
-
} else {
|
|
26
|
-
this.isPlaylistActive = false;
|
|
27
|
-
this.hidePlaylistControls();
|
|
28
|
-
|
|
29
|
-
if (this.options.debug) {
|
|
30
|
-
console.log('🎵 No playlist detected');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
loadPlaylistData() {
|
|
36
|
-
// Find all videos with the same playlist-id
|
|
37
|
-
const playlistVideos = document.querySelectorAll(`[data-playlist-id="${this.playlistId}"]`);
|
|
38
|
-
|
|
39
|
-
this.playlist = Array.from(playlistVideos).map(video => ({
|
|
40
|
-
element: video,
|
|
41
|
-
index: parseInt(video.getAttribute('data-playlist-index')),
|
|
42
|
-
title: video.getAttribute('data-video-title') || `Video ${video.getAttribute('data-playlist-index') || 'Unknown'}`
|
|
43
|
-
})).sort((a, b) => a.index - b.index);
|
|
44
|
-
|
|
45
|
-
if (this.options.debug) {
|
|
46
|
-
console.log(`🎵 Loaded playlist with ${this.playlist.length} videos:`,
|
|
47
|
-
this.playlist.map(v => `${v.index}: ${v.title}`));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
nextVideo() {
|
|
52
|
-
if (!this.isPlaylistActive) {
|
|
53
|
-
if (this.options.debug) console.warn('🎵 No playlist active');
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let nextIndex = this.currentPlaylistIndex + 1;
|
|
58
|
-
|
|
59
|
-
if (nextIndex >= this.playlist.length) {
|
|
60
|
-
if (this.options.playlistLoop) {
|
|
61
|
-
nextIndex = 0;
|
|
62
|
-
} else {
|
|
63
|
-
if (this.options.debug) console.log('🎵 End of playlist reached');
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return this.goToPlaylistIndex(nextIndex);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
prevVideo() {
|
|
72
|
-
if (!this.isPlaylistActive) {
|
|
73
|
-
if (this.options.debug) console.warn('🎵 No playlist active');
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let prevIndex = this.currentPlaylistIndex - 1;
|
|
78
|
-
|
|
79
|
-
if (prevIndex < 0) {
|
|
80
|
-
if (this.options.playlistLoop) {
|
|
81
|
-
prevIndex = this.playlist.length - 1;
|
|
82
|
-
} else {
|
|
83
|
-
if (this.options.debug) console.log('🎵 Beginning of playlist reached');
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return this.goToPlaylistIndex(prevIndex);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
goToPlaylistIndex(index) {
|
|
92
|
-
if (!this.isPlaylistActive || index < 0 || index >= this.playlist.length) {
|
|
93
|
-
if (this.options.debug) console.warn(`🎵 Invalid playlist index: ${index}`);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const fromIndex = this.currentPlaylistIndex;
|
|
98
|
-
const targetVideo = this.playlist[index];
|
|
99
|
-
const currentTime = this.video.currentTime || 0;
|
|
100
|
-
const wasPlaying = !this.video.paused;
|
|
101
|
-
|
|
102
|
-
// Trigger playlist change event
|
|
103
|
-
this.triggerEvent('playlistchange', {
|
|
104
|
-
fromIndex: fromIndex,
|
|
105
|
-
toIndex: index,
|
|
106
|
-
fromTitle: this.playlist[fromIndex]?.title,
|
|
107
|
-
toTitle: targetVideo.title,
|
|
108
|
-
playlistId: this.playlistId
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (this.options.debug) {
|
|
112
|
-
console.log(`🎵 Switching from video ${fromIndex} to ${index}: "${targetVideo.title}"`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Switch to new video
|
|
116
|
-
this.switchToVideo(targetVideo.element, wasPlaying);
|
|
117
|
-
this.currentPlaylistIndex = index;
|
|
118
|
-
this.updatePlaylistButtons();
|
|
119
|
-
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
getPlaylistInfo() {
|
|
124
|
-
return {
|
|
125
|
-
isActive: this.isPlaylistActive,
|
|
126
|
-
currentIndex: this.currentPlaylistIndex,
|
|
127
|
-
totalVideos: this.playlist.length,
|
|
128
|
-
playlistId: this.playlistId,
|
|
129
|
-
currentTitle: this.playlist[this.currentPlaylistIndex]?.title || '',
|
|
130
|
-
canGoPrev: this.currentPlaylistIndex > 0 || this.options.playlistLoop,
|
|
131
|
-
canGoNext: this.currentPlaylistIndex < this.playlist.length - 1 || this.options.playlistLoop
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
setPlaylistOptions(options = {}) {
|
|
136
|
-
if (options.autoPlay !== undefined) {
|
|
137
|
-
this.options.playlistAutoPlay = options.autoPlay;
|
|
138
|
-
}
|
|
139
|
-
if (options.loop !== undefined) {
|
|
140
|
-
this.options.playlistLoop = options.loop;
|
|
141
|
-
}
|
|
142
|
-
if (options.enabled !== undefined) {
|
|
143
|
-
this.options.playlistEnabled = options.enabled;
|
|
144
|
-
if (!options.enabled) {
|
|
145
|
-
this.hidePlaylistControls();
|
|
146
|
-
this.isPlaylistActive = false;
|
|
147
|
-
} else {
|
|
148
|
-
this.detectPlaylist();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (this.isPlaylistActive) {
|
|
153
|
-
this.updatePlaylistButtons();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (this.options.debug) {
|
|
157
|
-
console.log('🎵 Playlist options updated:', {
|
|
158
|
-
autoPlay: this.options.playlistAutoPlay,
|
|
159
|
-
loop: this.options.playlistLoop,
|
|
160
|
-
enabled: this.options.playlistEnabled
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return this;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
getPlaylistVideos() {
|
|
168
|
-
return this.playlist.map(video => ({
|
|
169
|
-
index: video.index,
|
|
170
|
-
title: video.title,
|
|
171
|
-
element: video.element,
|
|
172
|
-
isCurrent: video.index === this.currentPlaylistIndex
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Playlist methods for main class
|
|
177
|
-
// All original functionality preserved exactly
|