myetv-player 1.1.6 → 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 +153 -0
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +654 -129
  4. package/dist/myetv-player.min.js +579 -115
  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 +891 -14
  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 -188
  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 -1360
  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 -1243
  37. package/src/core.js +0 -1922
  38. package/src/events.js +0 -456
  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,456 +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
- // Trigger playing event - video is now actually playing
175
- this.triggerEvent('playing', {
176
- currentTime: this.getCurrentTime(),
177
- duration: this.getDuration()
178
- });
179
- });
180
-
181
- this.video.addEventListener('waiting', () => {
182
- if (!this.isChangingQuality) {
183
- this.showLoading();
184
- // Trigger waiting event - video is buffering
185
- this.triggerEvent('waiting', {
186
- currentTime: this.getCurrentTime()
187
- });
188
- }
189
- });
190
-
191
- this.video.addEventListener('seeking', () => {
192
- // Trigger seeking event - seek operation started
193
- this.triggerEvent('seeking', {
194
- currentTime: this.getCurrentTime(),
195
- targetTime: this.video.currentTime
196
- });
197
- });
198
-
199
- this.video.addEventListener('seeked', () => {
200
- // Trigger seeked event - seek operation completed
201
- this.triggerEvent('seeked', {
202
- currentTime: this.getCurrentTime()
203
- });
204
- });
205
-
206
- // Loading events
207
- this.video.addEventListener('loadstart', () => {
208
- if (!this.isChangingQuality) {
209
- this.showLoading();
210
- }
211
- // Trigger loadstart event - browser started loading media
212
- this.triggerEvent('loadstart');
213
- });
214
-
215
- this.video.addEventListener('loadedmetadata', () => {
216
- this.updateDuration();
217
-
218
- // Trigger loadedmetadata event - video metadata loaded
219
- this.triggerEvent('loadedmetadata', {
220
- duration: this.getDuration(),
221
- videoWidth: this.video.videoWidth,
222
- videoHeight: this.video.videoHeight
223
- });
224
-
225
- // Initialize subtitles after metadata is loaded
226
- setTimeout(() => {
227
- this.initializeSubtitles();
228
- }, 100);
229
- });
230
-
231
- this.video.addEventListener('loadeddata', () => {
232
- if (!this.isChangingQuality) {
233
- this.hideLoading();
234
- }
235
- // Trigger loadeddata event - current frame data loaded
236
- this.triggerEvent('loadeddata', {
237
- currentTime: this.getCurrentTime()
238
- });
239
- });
240
-
241
- this.video.addEventListener('canplay', () => {
242
- if (!this.isChangingQuality) {
243
- this.hideLoading();
244
- }
245
- // Trigger canplay event - video can start playing
246
- this.triggerEvent('canplay', {
247
- currentTime: this.getCurrentTime(),
248
- duration: this.getDuration()
249
- });
250
- });
251
-
252
- this.video.addEventListener('progress', () => {
253
- this.updateBuffer();
254
- // Trigger progress event - browser is downloading media
255
- this.triggerEvent('progress', {
256
- buffered: this.getBufferedTime(),
257
- duration: this.getDuration()
258
- });
259
- });
260
-
261
- this.video.addEventListener('durationchange', () => {
262
- this.updateDuration();
263
- // Trigger durationchange event - video duration changed
264
- this.triggerEvent('durationchange', {
265
- duration: this.getDuration()
266
- });
267
- });
268
-
269
- // Error events
270
- this.video.addEventListener('error', (e) => {
271
- this.onVideoError(e);
272
- // Trigger error event - media loading/playback error occurred
273
- this.triggerEvent('error', {
274
- code: this.video.error?.code,
275
- message: this.video.error?.message,
276
- src: this.video.currentSrc || this.video.src
277
- });
278
- });
279
-
280
- this.video.addEventListener('stalled', () => {
281
- // Trigger stalled event - browser is trying to fetch data but it's not available
282
- this.triggerEvent('stalled', {
283
- currentTime: this.getCurrentTime()
284
- });
285
- });
286
-
287
-
288
- this.video.addEventListener('timeupdate', () => this.updateProgress());
289
-
290
- this.video.addEventListener('ended', () => this.onVideoEnded());
291
-
292
- // Complete video click logic with doubleTapPause support (DESKTOP)
293
- this.video.addEventListener('click', () => {
294
- if (!this.options.pauseClick) return;
295
-
296
- if (this.options.doubleTapPause) {
297
- // DOUBLE TAP MODE: primo click mostra controlli, secondo pausa
298
- const controlsVisible = this.controls && this.controls.classList.contains('show');
299
-
300
- if (controlsVisible) {
301
- // Controlbar VISIBILE - pausa video
302
- this.togglePlayPause();
303
- } else {
304
- // Controlbar NASCOSTA - solo mostra controlli
305
- this.showControlsNow();
306
- this.resetAutoHideTimer();
307
- }
308
- } else {
309
- // NORMAL MODE: sempre pausa (comportamento originale)
310
- this.togglePlayPause();
311
- }
312
- });
313
- this.video.addEventListener('volumechange', () => this.updateVolumeSliderVisual());
314
-
315
- // Complete touch logic with doubleTapPause support (MOBILE)
316
- this.video.addEventListener('touchend', (e) => {
317
- // Prevent click event from firing after touchend
318
- e.preventDefault();
319
-
320
- if (!this.options.pauseClick) return;
321
-
322
- if (this.options.doubleTapPause) {
323
- // DOUBLE TAP MODE: primo tap mostra controlli, secondo pausa (SAME as desktop)
324
- const controlsVisible = this.controls && this.controls.classList.contains('show');
325
-
326
- if (controlsVisible) {
327
- // Controlbar VISIBILE - pausa video
328
- this.togglePlayPause();
329
- } else {
330
- // Controlbar NASCOSTA - solo mostra controlli
331
- this.showControlsNow();
332
- this.resetAutoHideTimer();
333
- }
334
- } else {
335
- // NORMAL MODE: sempre pausa (comportamento originale, SAME as desktop)
336
- this.togglePlayPause();
337
- }
338
- });
339
-
340
- // CRITICAL: Start auto-hide when video starts playing
341
- this.video.addEventListener('play', () => {
342
- if (this.options.autoHide && this.autoHideInitialized) {
343
- this.showControlsNow();
344
- this.resetAutoHideTimer();
345
- }
346
- });
347
-
348
- // Picture-in-Picture Events
349
- this.video.addEventListener('enterpictureinpicture', () => {
350
- this.onEnterPiP();
351
- this.triggerEvent('pipchange', {
352
- active: true,
353
- mode: 'enter'
354
- });
355
- });
356
-
357
- this.video.addEventListener('leavepictureinpicture', () => {
358
- this.onLeavePiP();
359
- this.triggerEvent('pipchange', {
360
- active: false,
361
- mode: 'exit'
362
- });
363
- });
364
- }
365
-
366
- if (this.playPauseBtn) {
367
- this.playPauseBtn.addEventListener('click', () => this.togglePlayPause());
368
- }
369
-
370
- if (this.muteBtn) {
371
- this.muteBtn.addEventListener('click', () => this.toggleMute());
372
- }
373
-
374
- if (this.fullscreenBtn) {
375
- this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
376
- }
377
-
378
- if (this.pipBtn) {
379
- this.pipBtn.addEventListener('click', () => this.togglePictureInPicture());
380
- }
381
-
382
- if (this.subtitlesBtn) {
383
- this.subtitlesBtn.addEventListener('click', () => this.toggleSubtitles());
384
- }
385
-
386
- if (this.volumeSlider) {
387
- this.volumeSlider.addEventListener('input', (e) => {
388
- this.updateVolume(e.target.value);
389
- this.updateVolumeSliderVisual();
390
- this.initVolumeTooltip();
391
- });
392
- }
393
-
394
- if (this.progressContainer) {
395
- // Mouse events (desktop)
396
- this.progressContainer.addEventListener('click', (e) => this.seek(e));
397
- this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
398
- if (this.progressHandle) {
399
- this.progressHandle.addEventListener('mousedown', this.startSeeking.bind(this));
400
- this.progressHandle.addEventListener('touchstart', this.startSeeking.bind(this), { passive: false });
401
- }
402
-
403
- // Touch events (mobile)
404
- this.progressContainer.addEventListener('touchstart', (e) => {
405
- e.preventDefault(); // Prevent scrolling when touching the seek bar
406
- this.startSeeking(e);
407
- }, { passive: false });
408
-
409
- this.setupSeekTooltip();
410
- }
411
-
412
- // Add touch events directly on the handle for better mobile dragging
413
- if (this.progressHandle) {
414
- this.progressHandle.addEventListener('touchstart', (e) => {
415
- e.preventDefault(); // Prevent default touch behavior
416
- e.stopPropagation(); // Stop event from bubbling to progressContainer
417
- this.startSeeking(e);
418
- }, { passive: false });
419
- }
420
-
421
- // NOTE: Auto-hide events are handled in initAutoHide() after everything is ready
422
-
423
- if (this.speedMenu) {
424
- this.speedMenu.addEventListener('click', (e) => this.changeSpeed(e));
425
- }
426
-
427
- if (this.qualityMenu) {
428
- this.qualityMenu.addEventListener('click', (e) => this.changeQuality(e));
429
- }
430
-
431
- if (this.subtitlesMenu) {
432
- this.subtitlesMenu.addEventListener('click', (e) => this.handleSubtitlesMenuClick(e));
433
- }
434
-
435
- document.addEventListener('fullscreenchange', () => this.updateFullscreenButton());
436
- document.addEventListener('webkitfullscreenchange', () => this.updateFullscreenButton());
437
- document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
438
-
439
- document.addEventListener('mousemove', (e) => this.continueSeeking(e));
440
- document.addEventListener('mouseup', () => this.endSeeking());
441
-
442
- // Touch events for seeking (mobile)
443
- document.addEventListener('touchmove', (e) => {
444
- if (this.isUserSeeking) {
445
- e.preventDefault(); // Prevent scrolling while seeking
446
- this.continueSeeking(e);
447
- }
448
- }, { passive: false });
449
-
450
- document.addEventListener('touchend', () => this.endSeeking());
451
- document.addEventListener('touchcancel', () => this.endSeeking());
452
-
453
- }
454
-
455
- // Events methods for main class
456
- // 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