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/events.js DELETED
@@ -1,537 +0,0 @@
1
- // Events Module for MYETV Video Player
2
- // Conservative modularization - original code preserved exactly
3
- // Created by https://www.myetv.tv https://oskarcosimo.com
4
-
5
- addEventListener(eventType, callback) {
6
- if (typeof callback !== 'function') {
7
- if (this.options.debug) console.warn(`Callback for event '${eventType}' is not a function`);
8
- return this;
9
- }
10
-
11
- if (!this.eventCallbacks[eventType]) {
12
- this.eventCallbacks[eventType] = [];
13
- }
14
-
15
- this.eventCallbacks[eventType].push(callback);
16
- if (this.options.debug) console.log(`Event '${eventType}' registered`);
17
- return this;
18
- }
19
-
20
- removeEventListener(eventType, callback) {
21
- if (!this.eventCallbacks[eventType]) return this;
22
-
23
- const index = this.eventCallbacks[eventType].indexOf(callback);
24
- if (index > -1) {
25
- this.eventCallbacks[eventType].splice(index, 1);
26
- if (this.options.debug) console.log(`Event '${eventType}' removed`);
27
- }
28
- return this;
29
- }
30
-
31
- triggerEvent(eventType, data = {}) {
32
- if (!this.eventCallbacks[eventType]) return;
33
-
34
- this.eventCallbacks[eventType].forEach(callback => {
35
- try {
36
- callback({
37
- type: eventType,
38
- timestamp: Date.now(),
39
- player: this,
40
- ...data
41
- });
42
- } catch (error) {
43
- if (this.options.debug) console.error(`Error in event '${eventType}':`, error);
44
- }
45
- });
46
- }
47
-
48
- getEventData() {
49
- const state = this.getPlayerState();
50
- return {
51
- played: state.isPlaying,
52
- paused: state.isPaused,
53
- subtitleEnabled: state.subtitlesEnabled,
54
- pipMode: state.isPictureInPicture,
55
- fullscreenMode: state.isFullscreen,
56
- speed: state.playbackRate,
57
- controlBarLength: state.currentTime,
58
- volumeIsMuted: state.isMuted,
59
- // Additional useful data
60
- duration: state.duration,
61
- volume: state.volume,
62
- quality: state.currentQuality,
63
- buffered: this.getBufferedTime()
64
- };
65
- }
66
-
67
- bindSubtitleEvents() {
68
- if (this.video.textTracks) {
69
- for (let i = 0; i < this.video.textTracks.length; i++) {
70
- const track = this.video.textTracks[i];
71
-
72
- track.addEventListener('cuechange', () => {
73
- if (this.options.debug) console.log('Cue change detected:', track.activeCues);
74
- });
75
- }
76
- }
77
- }
78
-
79
- setupVolumeTooltipEvents() {
80
- const volumeSlider = this.controls?.querySelector('.volume-slider');
81
- if (!volumeSlider) return;
82
-
83
- let isMouseOverVolume = false;
84
- let isDragging = false;
85
-
86
- volumeSlider.addEventListener('mouseenter', () => {
87
- isMouseOverVolume = true;
88
- // IMPORTANT: Set position FIRST, then show tooltip
89
- this.updateVolumeTooltipPosition(this.video.volume);
90
- this.updateVolumeTooltip();
91
- if (this.volumeTooltip) {
92
- this.volumeTooltip.classList.add('visible');
93
- }
94
- });
95
-
96
- volumeSlider.addEventListener('mouseleave', () => {
97
- isMouseOverVolume = false;
98
- if (!isDragging) {
99
- setTimeout(() => {
100
- if (!isMouseOverVolume && !isDragging && this.volumeTooltip) {
101
- this.volumeTooltip.classList.remove('visible');
102
- }
103
- }, 150);
104
- }
105
- });
106
-
107
- volumeSlider.addEventListener('mousemove', (e) => {
108
- if (isMouseOverVolume && this.volumeTooltip && !isDragging) {
109
- const rect = volumeSlider.getBoundingClientRect();
110
- const offsetX = e.clientX - rect.left;
111
- const sliderWidth = rect.width;
112
-
113
- const thumbSize = 14;
114
- const availableWidth = sliderWidth - thumbSize;
115
- let volumeAtPosition = (offsetX - thumbSize / 2) / availableWidth;
116
- volumeAtPosition = Math.max(0, Math.min(1, volumeAtPosition));
117
-
118
- const hoverVolume = Math.round(volumeAtPosition * 100);
119
-
120
- // Set position first, then update text to avoid flicker
121
- this.updateVolumeTooltipPosition(volumeAtPosition);
122
- this.volumeTooltip.textContent = hoverVolume + '%';
123
- }
124
- });
125
-
126
- // During dragging - set position immediately
127
- volumeSlider.addEventListener('mousedown', () => {
128
- isDragging = true;
129
- if (this.volumeTooltip) {
130
- // Ensure position is set before showing
131
- this.updateVolumeTooltipPosition(this.video.volume);
132
- this.volumeTooltip.classList.add('visible');
133
- }
134
- });
135
-
136
- document.addEventListener('mouseup', () => {
137
- if (isDragging) {
138
- isDragging = false;
139
- setTimeout(() => {
140
- if (!isMouseOverVolume && this.volumeTooltip) {
141
- this.volumeTooltip.classList.remove('visible');
142
- }
143
- }, 500);
144
- }
145
- });
146
-
147
- volumeSlider.addEventListener('input', (e) => {
148
- const volumeValue = parseFloat(e.target.value);
149
- const volume = Math.round(volumeValue * 100);
150
- if (this.volumeTooltip) {
151
- // Update position first, then text
152
- this.updateVolumeTooltipPosition(volumeValue);
153
- this.volumeTooltip.textContent = volume + '%';
154
- }
155
- isDragging = true;
156
- });
157
-
158
- volumeSlider.addEventListener('change', () => {
159
- // Ensure final position is correct
160
- this.updateVolumeTooltip();
161
- setTimeout(() => {
162
- isDragging = false;
163
- }, 100);
164
- });
165
- }
166
-
167
- bindEvents() {
168
- if (this.video) {
169
-
170
- // Playback events
171
- this.video.addEventListener('playing', () => {
172
- this.hideLoading();
173
- this.closeAllMenus();
174
-
175
- // Update play/pause button when video actually starts playing
176
- if (this.playIcon && this.pauseIcon) {
177
- this.playIcon.classList.add('hidden');
178
- this.pauseIcon.classList.remove('hidden');
179
- }
180
-
181
- // Trigger playing event - video is now actually playing
182
- this.triggerEvent('playing', {
183
- currentTime: this.getCurrentTime(),
184
- duration: this.getDuration()
185
- });
186
- });
187
-
188
- this.video.addEventListener('waiting', () => {
189
- if (!this.isChangingQuality) {
190
- this.showLoading();
191
- // Trigger waiting event - video is buffering
192
- this.triggerEvent('waiting', {
193
- currentTime: this.getCurrentTime()
194
- });
195
- }
196
- });
197
-
198
- this.video.addEventListener('seeking', () => {
199
- // Trigger seeking event - seek operation started
200
- this.triggerEvent('seeking', {
201
- currentTime: this.getCurrentTime(),
202
- targetTime: this.video.currentTime
203
- });
204
- });
205
-
206
- this.video.addEventListener('seeked', () => {
207
- // Trigger seeked event - seek operation completed
208
- this.triggerEvent('seeked', {
209
- currentTime: this.getCurrentTime()
210
- });
211
- });
212
-
213
- // Loading events
214
- this.video.addEventListener('loadstart', () => {
215
- if (!this.isChangingQuality) {
216
- this.showLoading();
217
- }
218
- // Trigger loadstart event - browser started loading media
219
- this.triggerEvent('loadstart');
220
- });
221
-
222
- this.video.addEventListener('loadedmetadata', () => {
223
- this.updateDuration();
224
-
225
- // Trigger loadedmetadata event - video metadata loaded
226
- this.triggerEvent('loadedmetadata', {
227
- duration: this.getDuration(),
228
- videoWidth: this.video.videoWidth,
229
- videoHeight: this.video.videoHeight
230
- });
231
-
232
- // Initialize subtitles after metadata is loaded
233
- setTimeout(() => {
234
- this.initializeSubtitles();
235
- }, 100);
236
- });
237
-
238
- this.video.addEventListener('loadeddata', () => {
239
- if (!this.isChangingQuality) {
240
- this.hideLoading();
241
- }
242
- // Trigger loadeddata event - current frame data loaded
243
- this.triggerEvent('loadeddata', {
244
- currentTime: this.getCurrentTime()
245
- });
246
- });
247
-
248
- this.video.addEventListener('canplay', () => {
249
- if (!this.isChangingQuality) {
250
- this.hideLoading();
251
- }
252
- // Trigger canplay event - video can start playing
253
- this.triggerEvent('canplay', {
254
- currentTime: this.getCurrentTime(),
255
- duration: this.getDuration()
256
- });
257
- });
258
-
259
- this.video.addEventListener('progress', () => {
260
- this.updateBuffer();
261
- // Trigger progress event - browser is downloading media
262
- this.triggerEvent('progress', {
263
- buffered: this.getBufferedTime(),
264
- duration: this.getDuration()
265
- });
266
- });
267
-
268
- this.video.addEventListener('durationchange', () => {
269
- this.updateDuration();
270
- // Trigger durationchange event - video duration changed
271
- this.triggerEvent('durationchange', {
272
- duration: this.getDuration()
273
- });
274
- });
275
-
276
- // Error events
277
- this.video.addEventListener('error', (e) => {
278
- this.onVideoError(e);
279
- // Trigger error event - media loading/playback error occurred
280
- this.triggerEvent('error', {
281
- code: this.video.error?.code,
282
- message: this.video.error?.message,
283
- src: this.video.currentSrc || this.video.src
284
- });
285
- });
286
-
287
- this.video.addEventListener('stalled', () => {
288
- // Trigger stalled event - browser is trying to fetch data but it's not available
289
- this.triggerEvent('stalled', {
290
- currentTime: this.getCurrentTime()
291
- });
292
- });
293
-
294
-
295
- this.video.addEventListener('timeupdate', () => this.updateProgress());
296
-
297
- this.video.addEventListener('ended', () => this.onVideoEnded());
298
-
299
- // Complete video click logic with doubleTapPause support (DESKTOP)
300
- this.video.addEventListener('click', () => {
301
- if (!this.options.pauseClick) return;
302
-
303
- if (this.options.doubleTapPause) {
304
- // DOUBLE TAP MODE: primo click mostra controlli, secondo pausa
305
- const controlsVisible = this.controls && this.controls.classList.contains('show');
306
-
307
- if (controlsVisible) {
308
- // Controlbar VISIBILE - pausa video
309
- this.togglePlayPause();
310
- } else {
311
- // Controlbar NASCOSTA - solo mostra controlli
312
- this.showControlsNow();
313
- this.resetAutoHideTimer();
314
- }
315
- } else {
316
- // NORMAL MODE: sempre pausa (comportamento originale)
317
- this.togglePlayPause();
318
- }
319
- });
320
- this.video.addEventListener('volumechange', () => this.updateVolumeSliderVisual());
321
-
322
- // Complete touch logic with doubleTapPause support (MOBILE)
323
- this.video.addEventListener('touchend', (e) => {
324
- // Prevent click event from firing after touchend
325
- e.preventDefault();
326
-
327
- if (!this.options.pauseClick) return;
328
-
329
- if (this.options.doubleTapPause) {
330
- // DOUBLE TAP MODE: primo tap mostra controlli, secondo pausa (SAME as desktop)
331
- const controlsVisible = this.controls && this.controls.classList.contains('show');
332
-
333
- if (controlsVisible) {
334
- // Controlbar VISIBILE - pausa video
335
- this.togglePlayPause();
336
- } else {
337
- // Controlbar NASCOSTA - solo mostra controlli
338
- this.showControlsNow();
339
- this.resetAutoHideTimer();
340
- }
341
- } else {
342
- // NORMAL MODE: sempre pausa (comportamento originale, SAME as desktop)
343
- this.togglePlayPause();
344
- }
345
- });
346
-
347
- // CRITICAL: Start auto-hide when video starts playing
348
- this.video.addEventListener('play', () => {
349
- if (this.options.autoHide && this.autoHideInitialized) {
350
- this.showControlsNow();
351
- this.resetAutoHideTimer();
352
- }
353
- });
354
-
355
- // Picture-in-Picture Events
356
- this.video.addEventListener('enterpictureinpicture', () => {
357
- this.onEnterPiP();
358
- this.triggerEvent('pipchange', {
359
- active: true,
360
- mode: 'enter'
361
- });
362
- });
363
-
364
- this.video.addEventListener('leavepictureinpicture', () => {
365
- this.onLeavePiP();
366
- this.triggerEvent('pipchange', {
367
- active: false,
368
- mode: 'exit'
369
- });
370
- });
371
- }
372
-
373
- if (this.playPauseBtn) {
374
- this.playPauseBtn.addEventListener('click', () => this.togglePlayPause());
375
- }
376
-
377
- if (this.muteBtn) {
378
- this.muteBtn.addEventListener('click', () => this.toggleMute());
379
- }
380
-
381
- if (this.fullscreenBtn) {
382
- this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
383
- }
384
-
385
- if (this.pipBtn) {
386
- this.pipBtn.addEventListener('click', () => this.togglePictureInPicture());
387
- }
388
-
389
- if (this.volumeSlider) {
390
- let isDraggingVolume = false;
391
-
392
- // Input event
393
- this.volumeSlider.addEventListener('input', (e) => {
394
- this.updateVolume(e.target.value);
395
- this.updateVolumeSliderVisual();
396
- this.initVolumeTooltip();
397
- });
398
-
399
- // MOUSE DRAG - Start
400
- this.volumeSlider.addEventListener('mousedown', (e) => {
401
- isDraggingVolume = true;
402
- if (this.volumeTooltip) {
403
- this.volumeTooltip.classList.add('visible');
404
- }
405
- });
406
-
407
- // MOUSE DRAG - Move
408
- document.addEventListener('mousemove', (e) => {
409
- if (isDraggingVolume && this.volumeSlider) {
410
- const rect = this.volumeSlider.getBoundingClientRect();
411
- const clickX = e.clientX - rect.left;
412
- const percentage = Math.max(0, Math.min(1, clickX / rect.width));
413
- const value = Math.round(percentage * 100);
414
-
415
- this.volumeSlider.value = value;
416
- this.updateVolume(value);
417
- this.updateVolumeSliderVisual();
418
- if (this.volumeTooltip) {
419
- this.updateVolumeTooltipPosition(value / 100);
420
- }
421
- }
422
- });
423
-
424
- // MOUSE DRAG - End
425
- document.addEventListener('mouseup', () => {
426
- if (isDraggingVolume) {
427
- isDraggingVolume = false;
428
- if (this.volumeTooltip) {
429
- setTimeout(() => {
430
- this.volumeTooltip.classList.remove('visible');
431
- }, 300);
432
- }
433
- }
434
- });
435
-
436
- // TOUCH DRAG - Start
437
- this.volumeSlider.addEventListener('touchstart', (e) => {
438
- isDraggingVolume = true;
439
- if (this.volumeTooltip) {
440
- this.volumeTooltip.classList.add('visible');
441
- }
442
- }, { passive: true });
443
-
444
- // TOUCH DRAG - Move
445
- this.volumeSlider.addEventListener('touchmove', (e) => {
446
- if (isDraggingVolume) {
447
- const touch = e.touches[0];
448
- const rect = this.volumeSlider.getBoundingClientRect();
449
- const touchX = touch.clientX - rect.left;
450
- const percentage = Math.max(0, Math.min(1, touchX / rect.width));
451
- const value = Math.round(percentage * 100);
452
-
453
- this.volumeSlider.value = value;
454
- this.updateVolume(value);
455
- this.updateVolumeSliderVisual();
456
- if (this.volumeTooltip) {
457
- this.updateVolumeTooltipPosition(value / 100);
458
- }
459
- }
460
- }, { passive: true });
461
-
462
- // TOUCH DRAG - End
463
- this.volumeSlider.addEventListener('touchend', () => {
464
- if (isDraggingVolume) {
465
- isDraggingVolume = false;
466
- if (this.volumeTooltip) {
467
- setTimeout(() => {
468
- this.volumeTooltip.classList.remove('visible');
469
- }, 300);
470
- }
471
- }
472
- }, { passive: true });
473
- }
474
-
475
- if (this.progressContainer) {
476
- // Mouse events (desktop)
477
- this.progressContainer.addEventListener('click', (e) => this.seek(e));
478
- this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
479
- if (this.progressHandle) {
480
- this.progressHandle.addEventListener('mousedown', this.startSeeking.bind(this));
481
- this.progressHandle.addEventListener('touchstart', this.startSeeking.bind(this), { passive: false });
482
- }
483
-
484
- // Touch events (mobile)
485
- this.progressContainer.addEventListener('touchstart', (e) => {
486
- e.preventDefault(); // Prevent scrolling when touching the seek bar
487
- this.startSeeking(e);
488
- }, { passive: false });
489
-
490
- this.setupSeekTooltip();
491
- }
492
-
493
- // Add touch events directly on the handle for better mobile dragging
494
- if (this.progressHandle) {
495
- this.progressHandle.addEventListener('touchstart', (e) => {
496
- e.preventDefault(); // Prevent default touch behavior
497
- e.stopPropagation(); // Stop event from bubbling to progressContainer
498
- this.startSeeking(e);
499
- }, { passive: false });
500
- }
501
-
502
- // NOTE: Auto-hide events are handled in initAutoHide() after everything is ready
503
-
504
- if (this.speedMenu) {
505
- this.speedMenu.addEventListener('click', (e) => this.changeSpeed(e));
506
- }
507
-
508
- if (this.qualityMenu) {
509
- this.qualityMenu.addEventListener('click', (e) => this.changeQuality(e));
510
- }
511
-
512
- if (this.subtitlesMenu) {
513
- this.subtitlesMenu.addEventListener('click', (e) => this.handleSubtitlesMenuClick(e));
514
- }
515
-
516
- document.addEventListener('fullscreenchange', () => this.updateFullscreenButton());
517
- document.addEventListener('webkitfullscreenchange', () => this.updateFullscreenButton());
518
- document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
519
-
520
- document.addEventListener('mousemove', (e) => this.continueSeeking(e));
521
- document.addEventListener('mouseup', () => this.endSeeking());
522
-
523
- // Touch events for seeking (mobile)
524
- document.addEventListener('touchmove', (e) => {
525
- if (this.isUserSeeking) {
526
- e.preventDefault(); // Prevent scrolling while seeking
527
- this.continueSeeking(e);
528
- }
529
- }, { passive: false });
530
-
531
- document.addEventListener('touchend', () => this.endSeeking());
532
- document.addEventListener('touchcancel', () => this.endSeeking());
533
-
534
- }
535
-
536
- // Events methods for main class
537
- // All original functionality preserved exactly
package/src/fullscreen.js DELETED
@@ -1,82 +0,0 @@
1
- // Fullscreen Module for MYETV Video Player
2
- // Conservative modularization - original code preserved exactly
3
- // Created by https://www.myetv.tv https://oskarcosimo.com
4
-
5
- isFullscreenActive() {
6
- return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
7
- }
8
-
9
- checkPiPSupport() {
10
- return 'pictureInPictureEnabled' in document;
11
- }
12
-
13
- enterFullscreen() {
14
- const element = this.container.parentElement || this.container;
15
-
16
- if (element.requestFullscreen) {
17
- element.requestFullscreen();
18
- } else if (element.webkitRequestFullscreen) {
19
- element.webkitRequestFullscreen();
20
- } else if (element.mozRequestFullScreen) {
21
- element.mozRequestFullScreen();
22
- }
23
- }
24
-
25
- exitFullscreen() {
26
- if (document.exitFullscreen) {
27
- document.exitFullscreen();
28
- } else if (document.webkitExitFullscreen) {
29
- document.webkitExitFullscreen();
30
- } else if (document.mozCancelFullScreen) {
31
- document.mozCancelFullScreen();
32
- }
33
- }
34
-
35
- async enterPictureInPicture() {
36
- if (!this.isPiPSupported || !this.video) return;
37
-
38
- try {
39
- await this.video.requestPictureInPicture();
40
- } catch (error) {
41
- if (this.options.debug) console.error('Errore avvio Picture-in-Picture:', error);
42
- }
43
- }
44
-
45
- async exitPictureInPicture() {
46
- if (!this.isPiPSupported) return;
47
-
48
- try {
49
- await document.exitPictureInPicture();
50
- } catch (error) {
51
- if (this.options.debug) console.error('Errore uscita Picture-in-Picture:', error);
52
- }
53
- }
54
-
55
- onEnterPiP() {
56
- if (this.pipIcon) this.pipIcon.classList.add('hidden');
57
- if (this.pipExitIcon) this.pipExitIcon.classList.remove('hidden');
58
-
59
- if (this.controls) {
60
- this.controls.style.opacity = '0';
61
- }
62
-
63
- if (this.titleOverlay) {
64
- this.titleOverlay.style.opacity = '0';
65
- }
66
- }
67
-
68
- onLeavePiP() {
69
- if (this.pipIcon) this.pipIcon.classList.remove('hidden');
70
- if (this.pipExitIcon) this.pipExitIcon.classList.add('hidden');
71
-
72
- if (this.controls) {
73
- this.controls.style.opacity = '';
74
- }
75
-
76
- if (this.titleOverlay) {
77
- this.titleOverlay.style.opacity = '';
78
- }
79
- }
80
-
81
- // Fullscreen methods for main class
82
- // All original functionality preserved exactly