myetv-player 1.0.0 → 1.0.6
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/codeql.yml +100 -0
- package/README.md +36 -58
- package/SECURITY.md +50 -0
- package/css/myetv-player.css +301 -218
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +1713 -1503
- package/dist/myetv-player.min.js +1670 -1471
- package/package.json +6 -1
- package/plugins/README.md +1016 -0
- package/plugins/cloudflare/README.md +1068 -0
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
- package/plugins/facebook/README.md +1024 -0
- package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
- package/plugins/gamepad-remote-controller/README.md +816 -0
- package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
- package/plugins/google-adsense-ads/README.md +1 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
- package/plugins/google-ima-ads/README.md +1 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
- package/plugins/twitch/README.md +1185 -0
- package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
- package/plugins/vast-vpaid-ads/README.md +1 -0
- package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
- package/plugins/vimeo/README.md +1416 -0
- package/plugins/vimeo/myetv-player-vimeo.js +640 -0
- package/plugins/youtube/README.md +851 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
- package/scss/README.md +160 -0
- package/scss/_menus.scss +840 -672
- package/scss/_responsive.scss +67 -105
- package/scss/_volume.scss +67 -105
- package/src/README.md +559 -0
- package/src/controls.js +16 -4
- package/src/core.js +1192 -1062
- package/src/i18n.js +27 -1
- package/src/quality.js +478 -436
- package/src/subtitles.js +2 -2
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/* VAST/VPAID Custom Plugin for MYETV Video Player
|
|
2
|
+
* Custom implementation for VAST and VPAID ads
|
|
3
|
+
* No external dependencies
|
|
4
|
+
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* GLOBAL_START */
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* VAST/VPAID Custom Plugin
|
|
13
|
+
* Handles VAST and VPAID ads without external SDK
|
|
14
|
+
*/
|
|
15
|
+
class VASTPlugin {
|
|
16
|
+
constructor(player, options = {}) {
|
|
17
|
+
this.player = player;
|
|
18
|
+
this.options = {
|
|
19
|
+
vastUrl: options.vastUrl || '',
|
|
20
|
+
vastXML: options.vastXML || null,
|
|
21
|
+
skipDelay: options.skipDelay || 5, // seconds
|
|
22
|
+
maxRedirects: options.maxRedirects || 5,
|
|
23
|
+
timeout: options.timeout || 8000,
|
|
24
|
+
debug: options.debug || false,
|
|
25
|
+
adLabel: options.adLabel || 'Pubblicità',
|
|
26
|
+
skipText: options.skipText || 'Salta annuncio',
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.adOverlay = null;
|
|
31
|
+
this.isAdPlaying = false;
|
|
32
|
+
this.currentAd = null;
|
|
33
|
+
this.skipButton = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Setup plugin
|
|
38
|
+
*/
|
|
39
|
+
setup() {
|
|
40
|
+
this.createAdOverlay();
|
|
41
|
+
this.attachPlayerEvents();
|
|
42
|
+
|
|
43
|
+
if (this.options.debug) {
|
|
44
|
+
console.log('🎬 VAST Plugin initialized');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create ad overlay element
|
|
50
|
+
*/
|
|
51
|
+
createAdOverlay() {
|
|
52
|
+
this.adOverlay = document.createElement('div');
|
|
53
|
+
this.adOverlay.className = 'vast-ad-overlay';
|
|
54
|
+
this.adOverlay.style.cssText = `
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 0;
|
|
57
|
+
left: 0;
|
|
58
|
+
width: 100%;
|
|
59
|
+
height: 100%;
|
|
60
|
+
background: #000;
|
|
61
|
+
z-index: 1000;
|
|
62
|
+
display: none;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
align-items: center;
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
// Ad label
|
|
69
|
+
const adLabel = document.createElement('div');
|
|
70
|
+
adLabel.className = 'vast-ad-label';
|
|
71
|
+
adLabel.textContent = this.options.adLabel;
|
|
72
|
+
adLabel.style.cssText = `
|
|
73
|
+
position: absolute;
|
|
74
|
+
top: 10px;
|
|
75
|
+
left: 10px;
|
|
76
|
+
background: rgba(0, 0, 0, 0.7);
|
|
77
|
+
color: #fff;
|
|
78
|
+
padding: 5px 10px;
|
|
79
|
+
border-radius: 3px;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
z-index: 1001;
|
|
82
|
+
`;
|
|
83
|
+
this.adOverlay.appendChild(adLabel);
|
|
84
|
+
|
|
85
|
+
// Ad video element
|
|
86
|
+
this.adVideo = document.createElement('video');
|
|
87
|
+
this.adVideo.className = 'vast-ad-video';
|
|
88
|
+
this.adVideo.style.cssText = `
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
object-fit: contain;
|
|
92
|
+
`;
|
|
93
|
+
this.adOverlay.appendChild(this.adVideo);
|
|
94
|
+
|
|
95
|
+
// Skip button
|
|
96
|
+
this.skipButton = document.createElement('button');
|
|
97
|
+
this.skipButton.className = 'vast-skip-button';
|
|
98
|
+
this.skipButton.style.cssText = `
|
|
99
|
+
position: absolute;
|
|
100
|
+
bottom: 20px;
|
|
101
|
+
right: 20px;
|
|
102
|
+
background: rgba(0, 0, 0, 0.8);
|
|
103
|
+
color: #fff;
|
|
104
|
+
border: none;
|
|
105
|
+
padding: 10px 20px;
|
|
106
|
+
border-radius: 3px;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
z-index: 1002;
|
|
110
|
+
display: none;
|
|
111
|
+
`;
|
|
112
|
+
this.skipButton.addEventListener('click', () => this.skipAd());
|
|
113
|
+
this.adOverlay.appendChild(this.skipButton);
|
|
114
|
+
|
|
115
|
+
this.player.container.appendChild(this.adOverlay);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Load and parse VAST
|
|
120
|
+
*/
|
|
121
|
+
async loadVAST() {
|
|
122
|
+
try {
|
|
123
|
+
let vastXML;
|
|
124
|
+
|
|
125
|
+
if (this.options.vastXML) {
|
|
126
|
+
vastXML = this.options.vastXML;
|
|
127
|
+
} else if (this.options.vastUrl) {
|
|
128
|
+
const response = await fetch(this.options.vastUrl, {
|
|
129
|
+
method: 'GET',
|
|
130
|
+
timeout: this.options.timeout
|
|
131
|
+
});
|
|
132
|
+
vastXML = await response.text();
|
|
133
|
+
} else {
|
|
134
|
+
throw new Error('No VAST URL or XML provided');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parser = new DOMParser();
|
|
138
|
+
const vastDoc = parser.parseFromString(vastXML, 'text/xml');
|
|
139
|
+
|
|
140
|
+
// Parse VAST document
|
|
141
|
+
this.currentAd = this.parseVAST(vastDoc);
|
|
142
|
+
|
|
143
|
+
if (this.currentAd) {
|
|
144
|
+
this.playAd();
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error('No valid ad found in VAST response');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('🎬 VAST loading error:', error);
|
|
151
|
+
this.player.triggerEvent('aderror', { error });
|
|
152
|
+
this.resumeContent();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parse VAST XML
|
|
158
|
+
*/
|
|
159
|
+
parseVAST(vastDoc) {
|
|
160
|
+
const ad = vastDoc.querySelector('Ad');
|
|
161
|
+
if (!ad) return null;
|
|
162
|
+
|
|
163
|
+
const inline = ad.querySelector('InLine');
|
|
164
|
+
if (!inline) {
|
|
165
|
+
// Handle VAST wrapper
|
|
166
|
+
const wrapper = ad.querySelector('Wrapper');
|
|
167
|
+
if (wrapper) {
|
|
168
|
+
console.warn('🎬 VAST Wrapper not fully supported in this version');
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Get media file
|
|
174
|
+
const mediaFiles = inline.querySelectorAll('MediaFile');
|
|
175
|
+
let selectedMedia = null;
|
|
176
|
+
|
|
177
|
+
// Prefer MP4 format
|
|
178
|
+
for (const media of mediaFiles) {
|
|
179
|
+
const type = media.getAttribute('type');
|
|
180
|
+
if (type && type.includes('mp4')) {
|
|
181
|
+
selectedMedia = media;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!selectedMedia && mediaFiles.length > 0) {
|
|
187
|
+
selectedMedia = mediaFiles[0];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!selectedMedia) return null;
|
|
191
|
+
|
|
192
|
+
// Get tracking URLs
|
|
193
|
+
const impressions = Array.from(inline.querySelectorAll('Impression'))
|
|
194
|
+
.map(imp => imp.textContent.trim());
|
|
195
|
+
|
|
196
|
+
const clickThrough = inline.querySelector('ClickThrough')?.textContent.trim();
|
|
197
|
+
const clickTracking = Array.from(inline.querySelectorAll('ClickTracking'))
|
|
198
|
+
.map(click => click.textContent.trim());
|
|
199
|
+
|
|
200
|
+
// Get skip offset
|
|
201
|
+
const skipOffset = inline.querySelector('Linear')?.getAttribute('skipoffset');
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
mediaFileUrl: selectedMedia.textContent.trim(),
|
|
205
|
+
impressions,
|
|
206
|
+
clickThrough,
|
|
207
|
+
clickTracking,
|
|
208
|
+
skipOffset: this.parseSkipOffset(skipOffset),
|
|
209
|
+
duration: parseInt(inline.querySelector('Duration')?.textContent) || 0
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse skip offset
|
|
215
|
+
*/
|
|
216
|
+
parseSkipOffset(offset) {
|
|
217
|
+
if (!offset) return this.options.skipDelay;
|
|
218
|
+
|
|
219
|
+
// Parse formats like "00:00:05" or "5s" or "25%"
|
|
220
|
+
if (offset.includes(':')) {
|
|
221
|
+
const parts = offset.split(':');
|
|
222
|
+
return parseInt(parts[parts.length - 1]);
|
|
223
|
+
} else if (offset.endsWith('s')) {
|
|
224
|
+
return parseInt(offset);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return this.options.skipDelay;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Play ad
|
|
232
|
+
*/
|
|
233
|
+
playAd() {
|
|
234
|
+
if (!this.currentAd) return;
|
|
235
|
+
|
|
236
|
+
this.isAdPlaying = true;
|
|
237
|
+
this.adOverlay.style.display = 'flex';
|
|
238
|
+
this.player.video.pause();
|
|
239
|
+
|
|
240
|
+
// Set ad source
|
|
241
|
+
this.adVideo.src = this.currentAd.mediaFileUrl;
|
|
242
|
+
this.adVideo.play();
|
|
243
|
+
|
|
244
|
+
// Fire impression tracking
|
|
245
|
+
this.fireTrackingURLs(this.currentAd.impressions);
|
|
246
|
+
|
|
247
|
+
// Setup skip button
|
|
248
|
+
if (this.currentAd.skipOffset > 0) {
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
this.skipButton.textContent = this.options.skipText;
|
|
251
|
+
this.skipButton.style.display = 'block';
|
|
252
|
+
}, this.currentAd.skipOffset * 1000);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Ad ended
|
|
256
|
+
this.adVideo.addEventListener('ended', () => {
|
|
257
|
+
this.adEnded();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Ad click
|
|
261
|
+
this.adVideo.addEventListener('click', () => {
|
|
262
|
+
if (this.currentAd.clickThrough) {
|
|
263
|
+
window.open(this.currentAd.clickThrough, '_blank');
|
|
264
|
+
this.fireTrackingURLs(this.currentAd.clickTracking);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
this.player.triggerEvent('adstarted');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Skip ad
|
|
273
|
+
*/
|
|
274
|
+
skipAd() {
|
|
275
|
+
this.adVideo.pause();
|
|
276
|
+
this.adVideo.src = '';
|
|
277
|
+
this.adEnded();
|
|
278
|
+
this.player.triggerEvent('adskipped');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Ad ended
|
|
283
|
+
*/
|
|
284
|
+
adEnded() {
|
|
285
|
+
this.isAdPlaying = false;
|
|
286
|
+
this.adOverlay.style.display = 'none';
|
|
287
|
+
this.skipButton.style.display = 'none';
|
|
288
|
+
this.currentAd = null;
|
|
289
|
+
this.resumeContent();
|
|
290
|
+
this.player.triggerEvent('adcomplete');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Resume content playback
|
|
295
|
+
*/
|
|
296
|
+
resumeContent() {
|
|
297
|
+
this.player.video.play();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Fire tracking URLs
|
|
302
|
+
*/
|
|
303
|
+
fireTrackingURLs(urls) {
|
|
304
|
+
if (!urls || urls.length === 0) return;
|
|
305
|
+
|
|
306
|
+
urls.forEach(url => {
|
|
307
|
+
if (url) {
|
|
308
|
+
// Use Image object for tracking pixels
|
|
309
|
+
const img = new Image();
|
|
310
|
+
img.src = url;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Attach player events
|
|
317
|
+
*/
|
|
318
|
+
attachPlayerEvents() {
|
|
319
|
+
this.player.video.addEventListener('play', () => {
|
|
320
|
+
if (!this.isAdPlaying && this.options.vastUrl) {
|
|
321
|
+
this.loadVAST();
|
|
322
|
+
}
|
|
323
|
+
}, { once: true });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Dispose plugin
|
|
328
|
+
*/
|
|
329
|
+
dispose() {
|
|
330
|
+
if (this.adOverlay) {
|
|
331
|
+
this.adOverlay.remove();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (this.options.debug) {
|
|
335
|
+
console.log('🎬 VAST Plugin disposed');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Register plugin globally
|
|
341
|
+
if (typeof window.registerMYETVPlugin === 'function') {
|
|
342
|
+
window.registerMYETVPlugin('vast', VASTPlugin);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
})();
|
|
346
|
+
/* GLOBAL_END */
|