myetv-player 1.0.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/.github/workflows/npm-publish.yml +30 -0
- package/LICENSE +21 -0
- package/README.md +866 -0
- package/build.js +189 -0
- package/css/README.md +1 -0
- package/css/myetv-player.css +13702 -0
- package/css/myetv-player.min.css +1 -0
- package/dist/README.md +1 -0
- package/dist/myetv-player.js +6408 -0
- package/dist/myetv-player.min.js +6183 -0
- package/package.json +27 -0
- package/plugins/README.md +1 -0
- package/plugins/google-analytics/README.md +1 -0
- package/plugins/google-analytics/myetv-player-g-analytics-plugin.js +548 -0
- package/plugins/youtube/README.md +1 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +418 -0
- package/scss/README.md +1 -0
- package/scss/_audio-player.scss +21 -0
- package/scss/_base.scss +131 -0
- package/scss/_controls.scss +30 -0
- package/scss/_loading.scss +111 -0
- package/scss/_menus.scss +4070 -0
- package/scss/_mixins.scss +112 -0
- package/scss/_poster.scss +8 -0
- package/scss/_progress-bar.scss +2203 -0
- package/scss/_resolution.scss +68 -0
- package/scss/_responsive.scss +1532 -0
- package/scss/_themes.scss +30 -0
- package/scss/_title-overlay.scss +2262 -0
- package/scss/_tooltips.scss +7 -0
- package/scss/_variables.scss +49 -0
- package/scss/_video.scss +2401 -0
- package/scss/_volume.scss +1981 -0
- package/scss/_watermark.scss +8 -0
- package/scss/myetv-player.scss +51 -0
- package/scss/package.json +16 -0
- package/src/README.md +1 -0
- package/src/chapters.js +521 -0
- package/src/controls.js +1005 -0
- package/src/core.js +1650 -0
- package/src/events.js +330 -0
- package/src/fullscreen.js +82 -0
- package/src/i18n.js +348 -0
- package/src/playlist.js +177 -0
- package/src/plugins.js +384 -0
- package/src/quality.js +921 -0
- package/src/streaming.js +346 -0
- package/src/subtitles.js +426 -0
- package/src/utils.js +51 -0
- package/src/watermark.js +195 -0
package/src/events.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
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
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
170
|
+
this.updateDuration();
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
this.initializeSubtitles();
|
|
173
|
+
}, 100);
|
|
174
|
+
});
|
|
175
|
+
this.video.addEventListener('timeupdate', () => this.updateProgress());
|
|
176
|
+
this.video.addEventListener('progress', () => this.updateBuffer());
|
|
177
|
+
this.video.addEventListener('waiting', () => {
|
|
178
|
+
if (!this.isChangingQuality) {
|
|
179
|
+
this.showLoading();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
this.video.addEventListener('canplay', () => {
|
|
183
|
+
if (!this.isChangingQuality) {
|
|
184
|
+
this.hideLoading();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
this.video.addEventListener('ended', () => this.onVideoEnded());
|
|
188
|
+
this.video.addEventListener('loadstart', () => {
|
|
189
|
+
if (!this.isChangingQuality) {
|
|
190
|
+
this.showLoading();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
this.video.addEventListener('loadeddata', () => {
|
|
194
|
+
if (!this.isChangingQuality) {
|
|
195
|
+
this.hideLoading();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// Complete video click logic with doubleTapPause support (DESKTOP)
|
|
199
|
+
this.video.addEventListener('click', () => {
|
|
200
|
+
if (!this.options.pauseClick) return;
|
|
201
|
+
|
|
202
|
+
if (this.options.doubleTapPause) {
|
|
203
|
+
// DOUBLE TAP MODE: primo click mostra controlli, secondo pausa
|
|
204
|
+
const controlsVisible = this.controls && this.controls.classList.contains('show');
|
|
205
|
+
|
|
206
|
+
if (controlsVisible) {
|
|
207
|
+
// Controlbar VISIBILE - pausa video
|
|
208
|
+
this.togglePlayPause();
|
|
209
|
+
} else {
|
|
210
|
+
// Controlbar NASCOSTA - solo mostra controlli
|
|
211
|
+
this.showControlsNow();
|
|
212
|
+
this.resetAutoHideTimer();
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// NORMAL MODE: sempre pausa (comportamento originale)
|
|
216
|
+
this.togglePlayPause();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
this.video.addEventListener('volumechange', () => this.updateVolumeSliderVisual());
|
|
220
|
+
|
|
221
|
+
// Complete touch logic with doubleTapPause support (MOBILE)
|
|
222
|
+
this.video.addEventListener('touchend', (e) => {
|
|
223
|
+
// Prevent click event from firing after touchend
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
|
|
226
|
+
if (!this.options.pauseClick) return;
|
|
227
|
+
|
|
228
|
+
if (this.options.doubleTapPause) {
|
|
229
|
+
// DOUBLE TAP MODE: primo tap mostra controlli, secondo pausa (SAME as desktop)
|
|
230
|
+
const controlsVisible = this.controls && this.controls.classList.contains('show');
|
|
231
|
+
|
|
232
|
+
if (controlsVisible) {
|
|
233
|
+
// Controlbar VISIBILE - pausa video
|
|
234
|
+
this.togglePlayPause();
|
|
235
|
+
} else {
|
|
236
|
+
// Controlbar NASCOSTA - solo mostra controlli
|
|
237
|
+
this.showControlsNow();
|
|
238
|
+
this.resetAutoHideTimer();
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
// NORMAL MODE: sempre pausa (comportamento originale, SAME as desktop)
|
|
242
|
+
this.togglePlayPause();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// CRITICAL: Start auto-hide when video starts playing
|
|
247
|
+
this.video.addEventListener('play', () => {
|
|
248
|
+
if (this.options.autoHide && this.autoHideInitialized) {
|
|
249
|
+
this.showControlsNow();
|
|
250
|
+
this.resetAutoHideTimer();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Picture-in-Picture Events
|
|
255
|
+
this.video.addEventListener('enterpictureinpicture', () => {
|
|
256
|
+
this.onEnterPiP();
|
|
257
|
+
this.triggerEvent('pipchange', {
|
|
258
|
+
active: true,
|
|
259
|
+
mode: 'enter'
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this.video.addEventListener('leavepictureinpicture', () => {
|
|
264
|
+
this.onLeavePiP();
|
|
265
|
+
this.triggerEvent('pipchange', {
|
|
266
|
+
active: false,
|
|
267
|
+
mode: 'exit'
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (this.playPauseBtn) {
|
|
273
|
+
this.playPauseBtn.addEventListener('click', () => this.togglePlayPause());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (this.muteBtn) {
|
|
277
|
+
this.muteBtn.addEventListener('click', () => this.toggleMute());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (this.fullscreenBtn) {
|
|
281
|
+
this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (this.pipBtn) {
|
|
285
|
+
this.pipBtn.addEventListener('click', () => this.togglePictureInPicture());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.subtitlesBtn) {
|
|
289
|
+
this.subtitlesBtn.addEventListener('click', () => this.toggleSubtitles());
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (this.volumeSlider) {
|
|
293
|
+
this.volumeSlider.addEventListener('input', (e) => {
|
|
294
|
+
this.updateVolume(e.target.value);
|
|
295
|
+
this.updateVolumeSliderVisual();
|
|
296
|
+
this.initVolumeTooltip();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (this.progressContainer) {
|
|
301
|
+
this.progressContainer.addEventListener('click', (e) => this.seek(e));
|
|
302
|
+
this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.setupSeekTooltip();
|
|
306
|
+
|
|
307
|
+
// NOTE: Auto-hide events are handled in initAutoHide() after everything is ready
|
|
308
|
+
|
|
309
|
+
if (this.speedMenu) {
|
|
310
|
+
this.speedMenu.addEventListener('click', (e) => this.changeSpeed(e));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (this.qualityMenu) {
|
|
314
|
+
this.qualityMenu.addEventListener('click', (e) => this.changeQuality(e));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (this.subtitlesMenu) {
|
|
318
|
+
this.subtitlesMenu.addEventListener('click', (e) => this.handleSubtitlesMenuClick(e));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
document.addEventListener('fullscreenchange', () => this.updateFullscreenButton());
|
|
322
|
+
document.addEventListener('webkitfullscreenchange', () => this.updateFullscreenButton());
|
|
323
|
+
document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
|
|
324
|
+
|
|
325
|
+
document.addEventListener('mousemove', (e) => this.continueSeeking(e));
|
|
326
|
+
document.addEventListener('mouseup', () => this.endSeeking());
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Events methods for main class
|
|
330
|
+
// All original functionality preserved exactly
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|