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/subtitles.js DELETED
@@ -1,524 +0,0 @@
1
- /* Subtitles Module for MYETV Video Player
2
- * Conservative modularization - original code preserved exactly
3
- * Created by https://www.myetv.tv https://oskarcosimo.com
4
- */
5
-
6
- initializeSubtitles() {
7
- this.detectTextTracks();
8
- this.updateSubtitlesUI();
9
- this.bindSubtitleEvents();
10
- this.initializeCustomSubtitles();
11
-
12
- if (this.options.debug) console.log('📝 Detected ' + this.textTracks.length + ' subtitles traces');
13
- }
14
-
15
- initializeCustomSubtitles() {
16
- // Initialize player variables
17
- this.customSubtitles = [];
18
- this.currentCustomSubtitles = [];
19
- this.customSubtitlesEnabled = false;
20
- this.customOverlayElement = null;
21
- this.customUpdateInterval = null;
22
- this.currentCustomTrackIndex = -1;
23
-
24
- this.createCustomSubtitleOverlay();
25
- this.loadCustomSubtitleTracks();
26
-
27
- }
28
-
29
- createCustomSubtitleOverlay() {
30
- var existing = document.querySelector('.custom-subtitle-overlay');
31
- if (existing && existing.parentNode) {
32
- existing.parentNode.removeChild(existing);
33
- }
34
-
35
- this.customOverlayElement = document.createElement('div');
36
- this.customOverlayElement.className = 'custom-subtitle-overlay';
37
-
38
- // ENHANCED styles with responsive defaults
39
- this.customOverlayElement.style.cssText =
40
- 'position: absolute;' +
41
- 'bottom: 80px;' +
42
- 'left: 50%;' +
43
- 'transform: translateX(-50%);' +
44
- 'z-index: 999;' +
45
- 'color: white;' +
46
- 'font-family: Arial, sans-serif;' +
47
- 'font-size: clamp(12px, 4vw, 18px);' +
48
- 'font-weight: bold;' +
49
- 'text-align: center;' +
50
- 'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
51
- 'background-color: rgba(0, 0, 0, 0.6);' +
52
- 'padding: 8px 16px;' +
53
- 'border-radius: 6px;' +
54
- 'max-width: 80%;' +
55
- 'line-height: 1.3;' +
56
- 'white-space: pre-line;' +
57
- 'display: none;' +
58
- 'pointer-events: none;' +
59
- 'box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);';
60
-
61
- var playerContainer = this.video.parentElement;
62
- if (playerContainer) {
63
- playerContainer.style.position = 'relative';
64
- // ENSURE proper layer stacking
65
- if (!playerContainer.style.zIndex) {
66
- playerContainer.style.zIndex = '1';
67
- }
68
- playerContainer.appendChild(this.customOverlayElement);
69
- }
70
-
71
- if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
72
- }
73
-
74
- customTimeToSeconds(timeString) {
75
- if (!timeString) return 0;
76
-
77
- var parts = timeString.split(',');
78
- if (parts.length !== 2) return 0;
79
-
80
- var time = parts[0];
81
- var millis = parts[1];
82
-
83
- var timeParts = time.split(':');
84
- if (timeParts.length !== 3) return 0;
85
-
86
- var hours = parseInt(timeParts[0], 10);
87
- var minutes = parseInt(timeParts[1], 10);
88
- var seconds = parseInt(timeParts[2], 10);
89
- var milliseconds = parseInt(millis, 10);
90
-
91
- if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
92
- console.error('❌ customTimeToSeconds failed for:', timeString);
93
- return 0;
94
- }
95
-
96
- return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
97
- }
98
-
99
- parseCustomSRT(srtText) {
100
- var subtitles = [];
101
- var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
102
- var blocks = normalizedText.trim().split('\n\n');
103
-
104
- for (var i = 0; i < blocks.length; i++) {
105
- var block = blocks[i];
106
- var lines = block.trim().split('\n');
107
-
108
- if (lines.length >= 3) {
109
- var timeLine = lines[1].trim();
110
- var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
111
-
112
- if (timeMatch) {
113
- var startTime = this.customTimeToSeconds(timeMatch[1]);
114
- var endTime = this.customTimeToSeconds(timeMatch[2]);
115
- var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
116
-
117
- if (text && text.length > 0 && startTime < endTime) {
118
- subtitles.push({
119
- start: startTime,
120
- end: endTime,
121
- text: text
122
- });
123
- }
124
- }
125
- }
126
- }
127
-
128
- if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
129
- return subtitles;
130
- }
131
-
132
- loadCustomSubtitleTracks() {
133
- var self = this;
134
- var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
135
- if (tracks.length === 0) return;
136
-
137
- tracks.forEach(function (track, index) {
138
- var src = track.getAttribute('src');
139
- var label = track.getAttribute('label') || 'Unknown';
140
- var srclang = track.getAttribute('srclang') || '';
141
-
142
- // CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
143
- var trackObj = {
144
- label: label,
145
- language: srclang,
146
- subtitles: [],
147
- trackIndex: index
148
- };
149
- self.customSubtitles.push(trackObj);
150
-
151
- fetch(src)
152
- .then(function (response) {
153
- return response.text();
154
- })
155
- .then(function (srtText) {
156
- var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
157
- var blocks = normalizedText.trim().split('\n\n');
158
-
159
- for (var i = 0; i < blocks.length; i++) {
160
- var block = blocks[i].trim();
161
- if (!block) continue;
162
- var lines = block.split('\n');
163
-
164
- if (lines.length >= 3) {
165
- var timeLine = lines[1].trim();
166
- var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
167
-
168
- if (timeMatch) {
169
- var startParts = timeMatch[1].split(',');
170
- var startTimeParts = startParts[0].split(':');
171
- var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
172
-
173
- var endParts = timeMatch[2].split(',');
174
- var endTimeParts = endParts[0].split(':');
175
- var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
176
-
177
- var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
178
-
179
- if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
180
- trackObj.subtitles.push({
181
- start: startTime,
182
- end: endTime,
183
- text: text
184
- });
185
- }
186
- }
187
- }
188
- }
189
-
190
- if (self.options.debug) {
191
- console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
192
- }
193
- })
194
- .catch(function (error) {
195
- console.error('❌ Error loading ' + label + ':', error);
196
- });
197
- });
198
- }
199
-
200
- sanitizeSubtitleText(text) {
201
- if (!text) return '';
202
-
203
- // Remove HTML tags
204
- var sanitized = text.replace(/<[^>]*>/g, '');
205
-
206
- // Remove styling tags common in SRT files
207
- sanitized = sanitized.replace(/{\\.*?}/g, '');
208
- sanitized = sanitized.replace(/\\N/g, '\n');
209
-
210
- // Clean up multiple spaces
211
- sanitized = sanitized.replace(/\s+/g, ' ').trim();
212
-
213
- // Decode HTML entities if present
214
- var tempDiv = document.createElement('div');
215
- tempDiv.innerHTML = sanitized;
216
- sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
217
-
218
- return sanitized;
219
- }
220
-
221
- enableCustomSubtitleTrack(trackIndex) {
222
- if (trackIndex < 0 || trackIndex >= this.customSubtitles.length) return false;
223
-
224
- this.disableCustomSubtitles();
225
-
226
- this.customSubtitlesEnabled = true;
227
- this.currentCustomTrackIndex = trackIndex;
228
- this.currentCustomSubtitles = this.customSubtitles[trackIndex].subtitles;
229
-
230
- var self = this;
231
- this.customUpdateInterval = setInterval(function () {
232
- if (self.customSubtitlesEnabled && self.currentCustomSubtitles.length > 0) {
233
- self.updateCustomSubtitleDisplay();
234
- }
235
- }, 100);
236
-
237
- if (this.options.debug) {
238
- console.log('✅ Custom subtitles enabled: ' + this.customSubtitles[trackIndex].label);
239
- }
240
-
241
- return true;
242
- }
243
-
244
- updateCustomSubtitleDisplay() {
245
- if (!this.customSubtitlesEnabled || this.currentCustomSubtitles.length === 0) return;
246
-
247
- var currentTime = this.video.currentTime;
248
- var currentSubtitle = null;
249
-
250
- for (var i = 0; i < this.currentCustomSubtitles.length; i++) {
251
- var sub = this.currentCustomSubtitles[i];
252
- if (currentTime >= sub.start && currentTime <= sub.end) {
253
- currentSubtitle = sub;
254
- break;
255
- }
256
- }
257
-
258
- if (currentSubtitle) {
259
- this.customOverlayElement.textContent = currentSubtitle.text;
260
- this.customOverlayElement.style.display = 'block';
261
- } else {
262
- this.customOverlayElement.style.display = 'none';
263
- this.customOverlayElement.textContent = '';
264
- }
265
- }
266
-
267
- disableCustomSubtitles() {
268
- this.customSubtitlesEnabled = false;
269
- this.currentCustomTrackIndex = -1;
270
-
271
- if (this.customOverlayElement) {
272
- this.customOverlayElement.style.display = 'none';
273
- this.customOverlayElement.textContent = '';
274
- }
275
-
276
- if (this.customUpdateInterval) {
277
- clearInterval(this.customUpdateInterval);
278
- this.customUpdateInterval = null;
279
- }
280
-
281
- if (this.options.debug) console.log('❌ Custom subtitles disabled');
282
- }
283
-
284
- detectTextTracks() {
285
- this.textTracks = [];
286
-
287
- if (this.video.textTracks) {
288
- if (this.options.debug) console.log('🔍 Detecting text tracks... Found: ' + this.video.textTracks.length);
289
-
290
- for (var i = 0; i < this.video.textTracks.length; i++) {
291
- var track = this.video.textTracks[i];
292
-
293
- if (track.kind === 'subtitles' || track.kind === 'captions') {
294
- this.textTracks.push({
295
- track: track,
296
- label: track.label || 'Track ' + (i + 1),
297
- language: track.language || 'unknown',
298
- kind: track.kind,
299
- index: i
300
- });
301
- }
302
- }
303
-
304
- if (this.options.debug) console.log('📊 Total subtitle tracks detected: ' + this.textTracks.length);
305
- }
306
- }
307
-
308
- enableSubtitleTrack(trackIndex) {
309
- if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
310
-
311
- // Disable all tracks first
312
- this.disableAllTracks();
313
-
314
- // Enable ONLY the custom subtitle system (not native browser)
315
- var success = this.enableCustomSubtitleTrack(trackIndex);
316
-
317
- if (success) {
318
- this.currentSubtitleTrack = this.textTracks[trackIndex].track;
319
- this.subtitlesEnabled = true;
320
-
321
- // Make sure native tracks stay DISABLED
322
- if (this.video.textTracks && this.video.textTracks[trackIndex]) {
323
- this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
324
- }
325
-
326
- this.updateSubtitlesButton();
327
- this.populateSubtitlesMenu();
328
-
329
- if (this.options.debug) {
330
- console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
331
- }
332
-
333
- // Trigger subtitle change event
334
- this.triggerEvent('subtitlechange', {
335
- enabled: true,
336
- trackIndex: trackIndex,
337
- trackLabel: this.textTracks[trackIndex].label,
338
- trackLanguage: this.textTracks[trackIndex].language
339
- });
340
- } else {
341
- if (this.options.debug) {
342
- console.error('❌ Failed to enable custom subtitles for track', trackIndex);
343
- }
344
- }
345
- }
346
-
347
- disableSubtitles() {
348
- this.disableCustomSubtitles();
349
- this.disableAllTracks();
350
-
351
- this.currentSubtitleTrack = null;
352
- this.subtitlesEnabled = false;
353
-
354
- this.updateSubtitlesButton();
355
- this.populateSubtitlesMenu();
356
-
357
- if (this.options.debug) console.log('📝 Subtitles disabled');
358
-
359
- this.triggerEvent('subtitlechange', {
360
- enabled: false,
361
- trackIndex: -1
362
- });
363
- }
364
-
365
- disableAllTracks() {
366
- if (!this.video || !this.video.textTracks) return;
367
-
368
- // Disable all native tracks
369
- for (var i = 0; i < this.video.textTracks.length; i++) {
370
- this.video.textTracks[i].mode = 'hidden';
371
- }
372
-
373
- // Also disable custom subtitles
374
- this.disableCustomSubtitles();
375
- }
376
-
377
- getAvailableSubtitles() {
378
- return this.textTracks.map(function (t) {
379
- return {
380
- label: t.label,
381
- language: t.language,
382
- kind: t.kind
383
- };
384
- });
385
- }
386
-
387
- setSubtitleTrack(trackIndex) {
388
- if (trackIndex === -1) {
389
- this.disableSubtitles();
390
- } else {
391
- this.enableSubtitleTrack(trackIndex);
392
- }
393
- return this;
394
- }
395
-
396
- getCurrentSubtitleTrack() {
397
- if (!this.subtitlesEnabled || !this.currentSubtitleTrack) return -1;
398
-
399
- for (var i = 0; i < this.textTracks.length; i++) {
400
- if (this.textTracks[i].track === this.currentSubtitleTrack) {
401
- return i;
402
- }
403
- }
404
- return -1;
405
- }
406
-
407
- isSubtitlesEnabled() {
408
- return this.subtitlesEnabled;
409
- }
410
-
411
- updateSubtitlesButton() {
412
- var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
413
- if (!subtitlesBtn) return;
414
-
415
- subtitlesBtn.classList.remove('active');
416
-
417
- if (this.subtitlesEnabled) {
418
- subtitlesBtn.title = this.t('subtitlesdisable');
419
- } else {
420
- subtitlesBtn.title = this.t('subtitlesenable');
421
- }
422
- }
423
-
424
- populateSubtitlesMenu() {
425
- var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
426
- if (!subtitlesMenu) return;
427
-
428
- var menuHTML = '<div class="subtitles-option ' + (!this.subtitlesEnabled ? 'active' : '') + '" data-track="off">Off</div>';
429
-
430
- for (var i = 0; i < this.textTracks.length; i++) {
431
- var trackData = this.textTracks[i];
432
- var isActive = this.currentSubtitleTrack === trackData.track;
433
- menuHTML += '<div class="subtitles-option ' + (isActive ? 'active' : '') + '" data-track="' + i + '">' + trackData.label + '</div>';
434
- }
435
-
436
- subtitlesMenu.innerHTML = menuHTML;
437
- }
438
-
439
- updateSubtitlesUI() {
440
- var subtitlesControl = this.controls && this.controls.querySelector('.subtitles-control');
441
-
442
- if (this.textTracks.length > 0 && this.options.showSubtitles) {
443
- if (subtitlesControl) subtitlesControl.style.display = 'block';
444
- this.populateSubtitlesMenu();
445
- } else {
446
- if (subtitlesControl) subtitlesControl.style.display = 'none';
447
- }
448
-
449
- this.updateSubtitlesButton();
450
- }
451
-
452
- bindSubtitleEvents() {
453
- var self = this;
454
-
455
- if (this.video.textTracks) {
456
- this.isChangingSubtitles = false; // flag to prevent loops
457
-
458
- this.video.textTracks.addEventListener('change', function () {
459
- // ignore changes initiated by the player itself
460
- if (self.isChangingSubtitles) {
461
- return;
462
- }
463
-
464
- // only update ui
465
- self.updateSubtitlesUI();
466
- });
467
- }
468
-
469
- // Add timeupdate listener for custom subtitle display
470
- this.video.addEventListener('timeupdate', () => {
471
- if (this.customSubtitlesEnabled) {
472
- this.updateCustomSubtitleDisplay();
473
- }
474
- });
475
-
476
- // Menu click events
477
- var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
478
- if (subtitlesMenu) {
479
- subtitlesMenu.addEventListener('click', function (e) {
480
- var option = e.target.closest('.subtitles-option');
481
- if (!option) return;
482
-
483
- self.isChangingSubtitles = true; // active flag
484
-
485
- var trackIndex = option.getAttribute('data-track');
486
- if (trackIndex === 'off') {
487
- self.disableSubtitles();
488
- } else {
489
- self.enableSubtitleTrack(parseInt(trackIndex));
490
- }
491
-
492
- setTimeout(function () {
493
- self.isChangingSubtitles = false; // disable flag
494
- }, 100);
495
- });
496
- }
497
- }
498
-
499
- handleSubtitlesMenuClick(e) {
500
- var option = e.target.closest('.subtitles-option');
501
- if (!option) return; // This prevents button clicks from toggling
502
-
503
- var trackIndex = option.getAttribute('data-track');
504
-
505
- if (trackIndex === 'off') {
506
- this.disableSubtitles();
507
- } else {
508
- // Don't check for 'toggle' - just enable the track
509
- this.enableSubtitleTrack(parseInt(trackIndex));
510
- }
511
-
512
- this.updateSubtitlesButton();
513
- this.populateSubtitlesMenu();
514
- }
515
-
516
- toggleSubtitles() {
517
- if (this.textTracks.length === 0) return;
518
-
519
- if (this.subtitlesEnabled) {
520
- this.disableSubtitles();
521
- } else {
522
- this.enableSubtitleTrack(0);
523
- }
524
- }
package/src/utils.js DELETED
@@ -1,65 +0,0 @@
1
- // Utils Module for MYETV Video Player
2
- // Conservative modularization - original code preserved exactly
3
- // Created by https://www.myetv.tv https://oskarcosimo.com
4
-
5
- getBufferedTime() {
6
- if (!this.video || !this.video.buffered || this.video.buffered.length === 0) return 0;
7
- try {
8
- return this.video.buffered.end(this.video.buffered.length - 1);
9
- } catch (error) {
10
- return 0;
11
- }
12
- }
13
-
14
- clearTitleTimeout() {
15
- if (this.titleTimeout) {
16
- clearTimeout(this.titleTimeout);
17
- this.titleTimeout = null;
18
- }
19
- }
20
-
21
- skipTime(seconds) {
22
- if (!this.video || !this.video.duration || this.isChangingQuality) return;
23
-
24
- this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
25
- }
26
-
27
- updateTimeDisplay() {
28
- // update current time
29
- if (this.currentTimeEl && this.video) {
30
- this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
31
- }
32
-
33
- // update duration or show badge if encoding
34
- if (this.durationEl && this.video) {
35
- const duration = this.video.duration;
36
-
37
- // check if duration is valid
38
- if (!duration || isNaN(duration) || !isFinite(duration)) {
39
- // Video in encoding - show badge instead of duration
40
- this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
41
- this.durationEl.classList.add('encoding-state');
42
- } else {
43
- // valid duration - show normal
44
- this.durationEl.textContent = this.formatTime(duration);
45
- this.durationEl.classList.remove('encoding-state');
46
- }
47
- }
48
- }
49
-
50
-
51
- formatTime(seconds) {
52
- if (isNaN(seconds) || seconds < 0) return '0:00';
53
-
54
- const hours = Math.floor(seconds / 3600);
55
- const minutes = Math.floor((seconds % 3600) / 60);
56
- const secs = Math.floor(seconds % 60);
57
-
58
- if (hours > 0) {
59
- return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
60
- }
61
- return `${minutes}:${secs.toString().padStart(2, '0')}`;
62
- }
63
-
64
- // Utils methods for main class
65
- // All original functionality preserved exactly