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
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