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.
Files changed (37) hide show
  1. package/.github/workflows/codeql.yml +100 -0
  2. package/README.md +36 -58
  3. package/SECURITY.md +50 -0
  4. package/css/myetv-player.css +301 -218
  5. package/css/myetv-player.min.css +1 -1
  6. package/dist/myetv-player.js +1713 -1503
  7. package/dist/myetv-player.min.js +1670 -1471
  8. package/package.json +6 -1
  9. package/plugins/README.md +1016 -0
  10. package/plugins/cloudflare/README.md +1068 -0
  11. package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
  12. package/plugins/facebook/README.md +1024 -0
  13. package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
  14. package/plugins/gamepad-remote-controller/README.md +816 -0
  15. package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
  16. package/plugins/google-adsense-ads/README.md +1 -0
  17. package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
  18. package/plugins/google-ima-ads/README.md +1 -0
  19. package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
  20. package/plugins/twitch/README.md +1185 -0
  21. package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
  22. package/plugins/vast-vpaid-ads/README.md +1 -0
  23. package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
  24. package/plugins/vimeo/README.md +1416 -0
  25. package/plugins/vimeo/myetv-player-vimeo.js +640 -0
  26. package/plugins/youtube/README.md +851 -0
  27. package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
  28. package/scss/README.md +160 -0
  29. package/scss/_menus.scss +840 -672
  30. package/scss/_responsive.scss +67 -105
  31. package/scss/_volume.scss +67 -105
  32. package/src/README.md +559 -0
  33. package/src/controls.js +16 -4
  34. package/src/core.js +1192 -1062
  35. package/src/i18n.js +27 -1
  36. package/src/quality.js +478 -436
  37. package/src/subtitles.js +2 -2
@@ -0,0 +1,569 @@
1
+ /**
2
+ * MYETV Player - Twitch Plugin
3
+ * File: myetv-player-twitch-plugin.js
4
+ * Integrates Twitch live streams and VODs with full API control
5
+ * Created by https://www.myetv.tv https://oskarcosimo.com
6
+ */
7
+
8
+ (function () {
9
+ 'use strict';
10
+
11
+ class TwitchPlugin {
12
+ constructor(player, options = {}) {
13
+ this.player = player;
14
+ this.options = {
15
+ // Video source (channel for live, video for VOD, collection for collections)
16
+ channel: options.channel || null,
17
+ video: options.video || null,
18
+ collection: options.collection || null,
19
+
20
+ // Parent domains (REQUIRED for embedding)
21
+ parent: options.parent || [window.location.hostname],
22
+
23
+ // Player dimensions
24
+ width: options.width || '100%',
25
+ height: options.height || '100%',
26
+
27
+ // Playback options
28
+ autoplay: options.autoplay !== false,
29
+ muted: options.muted || false,
30
+ time: options.time || '0h0m0s', // Start time for VODs (e.g., '1h30m45s')
31
+
32
+ // UI options
33
+ allowfullscreen: options.allowfullscreen !== false,
34
+
35
+ // Plugin options
36
+ debug: options.debug || false,
37
+ replaceNativePlayer: options.replaceNativePlayer !== false,
38
+ autoLoadFromData: options.autoLoadFromData !== false,
39
+
40
+ ...options
41
+ };
42
+
43
+ this.twitchPlayer = null;
44
+ this.twitchContainer = null;
45
+ this.isPlayerReady = false;
46
+ this.isLive = false;
47
+
48
+ // Get plugin API
49
+ this.api = player.getPluginAPI ? player.getPluginAPI() : {
50
+ player: player,
51
+ video: player.video,
52
+ container: player.container,
53
+ controls: player.controls,
54
+ debug: (msg) => {
55
+ if (this.options.debug) console.log('🎮 Twitch Plugin:', msg);
56
+ },
57
+ triggerEvent: (event, data) => player.triggerEvent(event, data)
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Setup plugin
63
+ */
64
+ setup() {
65
+ this.api.debug('Setup started');
66
+
67
+ // Auto-detect from data attributes
68
+ if (this.options.autoLoadFromData) {
69
+ this.autoDetectSource();
70
+ }
71
+
72
+ // Load Twitch Player SDK
73
+ this.loadTwitchSDK().then(() => {
74
+ if (this.options.channel || this.options.video || this.options.collection) {
75
+ this.createTwitchPlayer();
76
+ }
77
+ }).catch(error => {
78
+ console.error('🎮 Twitch Plugin: Failed to load SDK', error);
79
+ });
80
+
81
+ // Add custom methods to player
82
+ this.addCustomMethods();
83
+
84
+ this.api.debug('Setup completed');
85
+ }
86
+
87
+ /**
88
+ * Auto-detect source from data attributes
89
+ */
90
+ autoDetectSource() {
91
+ // Check data attributes
92
+ const dataChannel = this.api.video.getAttribute('data-twitch-channel');
93
+ const dataVideo = this.api.video.getAttribute('data-twitch-video');
94
+ const dataVideoType = this.api.video.getAttribute('data-video-type');
95
+
96
+ if (dataChannel && dataVideoType === 'twitch') {
97
+ this.options.channel = dataChannel;
98
+ this.api.debug('Channel detected: ' + dataChannel);
99
+ return;
100
+ }
101
+
102
+ if (dataVideo && dataVideoType === 'twitch') {
103
+ this.options.video = dataVideo;
104
+ this.api.debug('Video detected: ' + dataVideo);
105
+ return;
106
+ }
107
+
108
+ // Check video src
109
+ const src = this.api.video.src || this.api.video.currentSrc;
110
+ if (src && this.isTwitchUrl(src)) {
111
+ this.extractFromUrl(src);
112
+ return;
113
+ }
114
+
115
+ // Check source elements
116
+ const sources = this.api.video.querySelectorAll('source');
117
+ for (const source of sources) {
118
+ const sourceSrc = source.getAttribute('src');
119
+ const sourceType = source.getAttribute('type');
120
+
121
+ if ((sourceType === 'video/twitch' || this.isTwitchUrl(sourceSrc)) && sourceSrc) {
122
+ this.extractFromUrl(sourceSrc);
123
+ return;
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Check if URL is a Twitch URL
130
+ */
131
+ isTwitchUrl(url) {
132
+ if (!url) return false;
133
+ return /twitch\.tv\/(videos\/|[^/]+\/?$)/.test(url);
134
+ }
135
+
136
+ /**
137
+ * Extract channel or video from URL
138
+ */
139
+ extractFromUrl(url) {
140
+ // Extract video ID: https://www.twitch.tv/videos/123456789
141
+ const videoMatch = url.match(/twitch\.tv\/videos\/(\d+)/);
142
+ if (videoMatch) {
143
+ this.options.video = videoMatch[1];
144
+ this.api.debug('Video ID extracted: ' + this.options.video);
145
+ return;
146
+ }
147
+
148
+ // Extract channel: https://www.twitch.tv/channelname
149
+ const channelMatch = url.match(/twitch\.tv\/([^/]+)\/?$/);
150
+ if (channelMatch) {
151
+ this.options.channel = channelMatch[1];
152
+ this.api.debug('Channel extracted: ' + this.options.channel);
153
+ return;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Load Twitch Player SDK
159
+ */
160
+ loadTwitchSDK() {
161
+ return new Promise((resolve, reject) => {
162
+ // Check if already loaded
163
+ if (window.Twitch && window.Twitch.Player) {
164
+ this.api.debug('Twitch SDK already loaded');
165
+ resolve();
166
+ return;
167
+ }
168
+
169
+ // Inject script
170
+ const script = document.createElement('script');
171
+ script.src = 'https://player.twitch.tv/js/embed/v1.js';
172
+ script.async = true;
173
+ script.onload = () => {
174
+ this.api.debug('Twitch SDK loaded');
175
+ resolve();
176
+ };
177
+ script.onerror = () => reject(new Error('Failed to load Twitch SDK'));
178
+ document.head.appendChild(script);
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Create Twitch player
184
+ */
185
+ createTwitchPlayer() {
186
+ if (!this.options.channel && !this.options.video && !this.options.collection) {
187
+ this.api.debug('No channel, video, or collection specified');
188
+ return;
189
+ }
190
+
191
+ // Hide native player
192
+ if (this.options.replaceNativePlayer) {
193
+ this.api.video.style.display = 'none';
194
+ }
195
+
196
+ // Create container
197
+ this.twitchContainer = document.createElement('div');
198
+ this.twitchContainer.id = 'twitch-player-' + Date.now();
199
+ this.twitchContainer.style.cssText = `
200
+ position: absolute;
201
+ top: 0;
202
+ left: 0;
203
+ width: 100%;
204
+ height: 100%;
205
+ z-index: 100;
206
+ `;
207
+
208
+ this.api.container.appendChild(this.twitchContainer);
209
+
210
+ // Configure options
211
+ const playerOptions = {
212
+ width: this.options.width,
213
+ height: this.options.height,
214
+ parent: this.options.parent,
215
+ autoplay: this.options.autoplay,
216
+ muted: this.options.muted,
217
+ allowfullscreen: this.options.allowfullscreen
218
+ };
219
+
220
+ // Add source
221
+ if (this.options.channel) {
222
+ playerOptions.channel = this.options.channel;
223
+ this.isLive = true;
224
+ } else if (this.options.video) {
225
+ playerOptions.video = this.options.video;
226
+ playerOptions.time = this.options.time;
227
+ this.isLive = false;
228
+ } else if (this.options.collection) {
229
+ playerOptions.collection = this.options.collection;
230
+ this.isLive = false;
231
+ }
232
+
233
+ // Create player
234
+ this.twitchPlayer = new Twitch.Player(this.twitchContainer.id, playerOptions);
235
+
236
+ // Setup event listeners
237
+ this.setupEventListeners();
238
+
239
+ this.api.debug('Twitch player created');
240
+ this.api.triggerEvent('twitchplugin:playerready', {
241
+ channel: this.options.channel,
242
+ video: this.options.video,
243
+ isLive: this.isLive
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Setup event listeners
249
+ */
250
+ setupEventListeners() {
251
+ if (!this.twitchPlayer) return;
252
+
253
+ // Player ready
254
+ this.twitchPlayer.addEventListener(Twitch.Player.READY, () => {
255
+ this.isPlayerReady = true;
256
+ this.api.debug('Player ready');
257
+ this.api.triggerEvent('twitchplugin:ready', {});
258
+ });
259
+
260
+ // Playing
261
+ this.twitchPlayer.addEventListener(Twitch.Player.PLAYING, () => {
262
+ this.api.debug('Playing');
263
+ this.api.triggerEvent('play', {});
264
+ this.api.triggerEvent('playing', {});
265
+ });
266
+
267
+ // Pause
268
+ this.twitchPlayer.addEventListener(Twitch.Player.PAUSE, () => {
269
+ this.api.debug('Paused');
270
+ this.api.triggerEvent('pause', {});
271
+ });
272
+
273
+ // Ended
274
+ this.twitchPlayer.addEventListener(Twitch.Player.ENDED, () => {
275
+ this.api.debug('Ended');
276
+ this.api.triggerEvent('ended', {});
277
+ });
278
+
279
+ // Online (stream went live)
280
+ this.twitchPlayer.addEventListener(Twitch.Player.ONLINE, () => {
281
+ this.api.debug('Stream online');
282
+ this.api.triggerEvent('twitchplugin:online', {});
283
+ });
284
+
285
+ // Offline (stream went offline)
286
+ this.twitchPlayer.addEventListener(Twitch.Player.OFFLINE, () => {
287
+ this.api.debug('Stream offline');
288
+ this.api.triggerEvent('twitchplugin:offline', {});
289
+ });
290
+
291
+ // Playback blocked (autoplay restrictions)
292
+ this.twitchPlayer.addEventListener(Twitch.Player.PLAYBACK_BLOCKED, () => {
293
+ this.api.debug('Playback blocked - user interaction required');
294
+ this.api.triggerEvent('twitchplugin:playbackblocked', {});
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Add custom methods to player instance
300
+ */
301
+ addCustomMethods() {
302
+ // Load channel
303
+ this.api.player.loadTwitchChannel = (channel) => {
304
+ return this.loadChannel(channel);
305
+ };
306
+
307
+ // Load video
308
+ this.api.player.loadTwitchVideo = (videoId, timestamp) => {
309
+ return this.loadVideo(videoId, timestamp);
310
+ };
311
+
312
+ // Get Twitch player instance
313
+ this.api.player.getTwitchPlayer = () => {
314
+ return this.twitchPlayer;
315
+ };
316
+
317
+ // Check if live
318
+ this.api.player.isTwitchLive = () => {
319
+ return this.isLive;
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Play
325
+ */
326
+ play() {
327
+ if (!this.twitchPlayer) {
328
+ return Promise.reject('Player not initialized');
329
+ }
330
+ this.twitchPlayer.play();
331
+ return Promise.resolve();
332
+ }
333
+
334
+ /**
335
+ * Pause
336
+ */
337
+ pause() {
338
+ if (!this.twitchPlayer) {
339
+ return Promise.reject('Player not initialized');
340
+ }
341
+ this.twitchPlayer.pause();
342
+ return Promise.resolve();
343
+ }
344
+
345
+ /**
346
+ * Seek (VODs only)
347
+ */
348
+ seek(seconds) {
349
+ if (!this.twitchPlayer) {
350
+ return Promise.reject('Player not initialized');
351
+ }
352
+ if (this.isLive) {
353
+ console.warn('🎮 Twitch Plugin: Cannot seek in live streams');
354
+ return Promise.reject('Cannot seek in live streams');
355
+ }
356
+ this.twitchPlayer.seek(seconds);
357
+ return Promise.resolve(seconds);
358
+ }
359
+
360
+ /**
361
+ * Get current time
362
+ */
363
+ getCurrentTime() {
364
+ if (!this.twitchPlayer) {
365
+ return Promise.reject('Player not initialized');
366
+ }
367
+ return Promise.resolve(this.twitchPlayer.getCurrentTime());
368
+ }
369
+
370
+ /**
371
+ * Get duration (VODs only)
372
+ */
373
+ getDuration() {
374
+ if (!this.twitchPlayer) {
375
+ return Promise.reject('Player not initialized');
376
+ }
377
+ if (this.isLive) {
378
+ return Promise.resolve(0);
379
+ }
380
+ return Promise.resolve(this.twitchPlayer.getDuration());
381
+ }
382
+
383
+ /**
384
+ * Set volume
385
+ */
386
+ setVolume(volume) {
387
+ if (!this.twitchPlayer) {
388
+ return Promise.reject('Player not initialized');
389
+ }
390
+ this.twitchPlayer.setVolume(volume);
391
+ return Promise.resolve(volume);
392
+ }
393
+
394
+ /**
395
+ * Get volume
396
+ */
397
+ getVolume() {
398
+ if (!this.twitchPlayer) {
399
+ return Promise.reject('Player not initialized');
400
+ }
401
+ return Promise.resolve(this.twitchPlayer.getVolume());
402
+ }
403
+
404
+ /**
405
+ * Mute
406
+ */
407
+ setMuted(muted) {
408
+ if (!this.twitchPlayer) {
409
+ return Promise.reject('Player not initialized');
410
+ }
411
+ this.twitchPlayer.setMuted(muted);
412
+ return Promise.resolve(muted);
413
+ }
414
+
415
+ /**
416
+ * Get muted state
417
+ */
418
+ getMuted() {
419
+ if (!this.twitchPlayer) {
420
+ return Promise.reject('Player not initialized');
421
+ }
422
+ return Promise.resolve(this.twitchPlayer.getMuted());
423
+ }
424
+
425
+ /**
426
+ * Get quality
427
+ */
428
+ getQuality() {
429
+ if (!this.twitchPlayer) {
430
+ return Promise.reject('Player not initialized');
431
+ }
432
+ return Promise.resolve(this.twitchPlayer.getQuality());
433
+ }
434
+
435
+ /**
436
+ * Set quality
437
+ */
438
+ setQuality(quality) {
439
+ if (!this.twitchPlayer) {
440
+ return Promise.reject('Player not initialized');
441
+ }
442
+ this.twitchPlayer.setQuality(quality);
443
+ return Promise.resolve(quality);
444
+ }
445
+
446
+ /**
447
+ * Get available qualities
448
+ */
449
+ getQualities() {
450
+ if (!this.twitchPlayer) {
451
+ return Promise.reject('Player not initialized');
452
+ }
453
+ return Promise.resolve(this.twitchPlayer.getPlaybackStats().qualitiesAvailable);
454
+ }
455
+
456
+ /**
457
+ * Get channel name
458
+ */
459
+ getChannel() {
460
+ if (!this.twitchPlayer) {
461
+ return Promise.reject('Player not initialized');
462
+ }
463
+ return Promise.resolve(this.twitchPlayer.getChannel());
464
+ }
465
+
466
+ /**
467
+ * Get video ID
468
+ */
469
+ getVideo() {
470
+ if (!this.twitchPlayer) {
471
+ return Promise.reject('Player not initialized');
472
+ }
473
+ return Promise.resolve(this.twitchPlayer.getVideo());
474
+ }
475
+
476
+ /**
477
+ * Get playback stats
478
+ */
479
+ getPlaybackStats() {
480
+ if (!this.twitchPlayer) {
481
+ return Promise.reject('Player not initialized');
482
+ }
483
+ return Promise.resolve(this.twitchPlayer.getPlaybackStats());
484
+ }
485
+
486
+ /**
487
+ * Check if paused
488
+ */
489
+ isPaused() {
490
+ if (!this.twitchPlayer) {
491
+ return Promise.reject('Player not initialized');
492
+ }
493
+ return Promise.resolve(this.twitchPlayer.isPaused());
494
+ }
495
+
496
+ /**
497
+ * Load channel (live stream)
498
+ */
499
+ loadChannel(channel) {
500
+ if (!this.twitchPlayer) {
501
+ return Promise.reject('Player not initialized');
502
+ }
503
+ this.options.channel = channel;
504
+ this.options.video = null;
505
+ this.isLive = true;
506
+ this.twitchPlayer.setChannel(channel);
507
+ this.api.triggerEvent('twitchplugin:channelloaded', { channel });
508
+ return Promise.resolve(channel);
509
+ }
510
+
511
+ /**
512
+ * Load video (VOD)
513
+ */
514
+ loadVideo(videoId, timestamp = '0h0m0s') {
515
+ if (!this.twitchPlayer) {
516
+ return Promise.reject('Player not initialized');
517
+ }
518
+ this.options.video = videoId;
519
+ this.options.channel = null;
520
+ this.isLive = false;
521
+ this.twitchPlayer.setVideo(videoId, timestamp);
522
+ this.api.triggerEvent('twitchplugin:videoloaded', { video: videoId, timestamp });
523
+ return Promise.resolve(videoId);
524
+ }
525
+
526
+ /**
527
+ * Load collection
528
+ */
529
+ loadCollection(collectionId, videoId) {
530
+ if (!this.twitchPlayer) {
531
+ return Promise.reject('Player not initialized');
532
+ }
533
+ this.options.collection = collectionId;
534
+ this.isLive = false;
535
+ this.twitchPlayer.setCollection(collectionId, videoId);
536
+ this.api.triggerEvent('twitchplugin:collectionloaded', { collection: collectionId, video: videoId });
537
+ return Promise.resolve(collectionId);
538
+ }
539
+
540
+ /**
541
+ * Dispose plugin
542
+ */
543
+ dispose() {
544
+ this.api.debug('Disposing plugin');
545
+
546
+ if (this.twitchContainer) {
547
+ this.twitchContainer.remove();
548
+ this.twitchContainer = null;
549
+ }
550
+
551
+ this.twitchPlayer = null;
552
+
553
+ // Restore native player
554
+ if (this.api.video && this.options.replaceNativePlayer) {
555
+ this.api.video.style.display = '';
556
+ }
557
+
558
+ this.api.debug('Plugin disposed');
559
+ }
560
+ }
561
+
562
+ // Register plugin globally
563
+ if (typeof window.registerMYETVPlugin === 'function') {
564
+ window.registerMYETVPlugin('twitch', TwitchPlugin);
565
+ } else {
566
+ console.error('🎮 MYETV Player plugin system not found');
567
+ }
568
+
569
+ })();
@@ -0,0 +1 @@
1
+