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
package/plugins/README.md CHANGED
@@ -1 +1,1017 @@
1
+ # MYETV Video Player - Plugin Development Guide
2
+ Complete guide for developing custom plugins for MYETV Video Player.
1
3
 
4
+ ---
5
+
6
+ ## Table of Contents
7
+
8
+ - [Introduction](#introduction)
9
+ - [Plugin Architecture](#plugin-architecture)
10
+ - [Creating a Plugin from Scratch](#creating-a-plugin-from-scratch)
11
+ - [API Reference](#api-reference)
12
+ - [HTML Integration](#html-integration)
13
+ - [Advanced Examples](#advanced-examples)
14
+ - [Best Practices](#best-practices)
15
+ - [Debugging](#debugging)
16
+ - [FAQ](#faq)
17
+
18
+ ---
19
+
20
+ ## Introduction
21
+
22
+ MYETV Video Player features a powerful and extensible plugin system that allows developers to add custom functionality without modifying the core player code.
23
+
24
+ ### Why Use Plugins?
25
+
26
+ - **Modularity**: Keep your code organized and maintainable
27
+ - **Reusability**: Share plugins across multiple projects
28
+ - **Non-invasive**: No need to modify core player files
29
+ - **Event-driven**: React to player events and lifecycle hooks
30
+
31
+ ---
32
+
33
+ ## Plugin Architecture
34
+
35
+ ### How Plugins Work
36
+
37
+ 1. **Registration**: Plugins are registered globally using `window.registerMYETVPlugin()`
38
+ 2. **Initialization**: The player initializes plugins when created or via `usePlugin()`
39
+ 3. **Lifecycle**: Plugins can hook into player events and lifecycle stages
40
+ 4. **Disposal**: Plugins are properly cleaned up when removed or player is disposed
41
+
42
+ ### Plugin Structure
43
+
44
+ A plugin can be:
45
+ - A **Constructor Function** (class)
46
+ - A **Factory Function**
47
+ - An **Object with a `create()` method**
48
+
49
+ ---
50
+
51
+ ## Creating a Plugin from Scratch
52
+
53
+ ### Step 1: Basic Template
54
+
55
+ Create a new JavaScript file (e.g., `myetv-plugin-example.js`):
56
+
57
+ ```javascript
58
+ /**
59
+ * MYETV Video Player - Example Plugin
60
+ * Description: This plugin demonstrates basic plugin functionality
61
+ * Author: Your Name
62
+ * Version: 1.0.0
63
+ */
64
+
65
+ (function(window) {
66
+ 'use strict';
67
+
68
+ // Plugin Constructor
69
+ class ExamplePlugin {
70
+ constructor(player, options) {
71
+ // Store player instance and options
72
+ this.player = player;
73
+ this.options = Object.assign({
74
+ // Default options
75
+ enabled: true,
76
+ customMessage: 'Hello from Example Plugin!'
77
+ }, options);
78
+
79
+ // Plugin state
80
+ this.isActive = false;
81
+
82
+ // Log initialization
83
+ if (this.player.options.debug) {
84
+ console.log('🔌 Example Plugin initialized with options:', this.options);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Setup method - called automatically after plugin initialization
90
+ * Use this to bind events, create UI elements, etc.
91
+ */
92
+ setup() {
93
+ if (!this.options.enabled) {
94
+ return;
95
+ }
96
+
97
+ // Bind player events
98
+ this.bindEvents();
99
+
100
+ // Create custom UI elements
101
+ this.createUI();
102
+
103
+ // Mark plugin as active
104
+ this.isActive = true;
105
+
106
+ if (this.player.options.debug) {
107
+ console.log('🔌 Example Plugin setup completed');
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Bind player events
113
+ */
114
+ bindEvents() {
115
+ // Listen to play event
116
+ this.player.addEventListener('played', (data) => {
117
+ console.log('Video started playing', data);
118
+ });
119
+
120
+ // Listen to pause event
121
+ this.player.addEventListener('paused', (data) => {
122
+ console.log('Video paused', data);
123
+ });
124
+
125
+ // Listen to timeupdate event
126
+ this.player.addEventListener('timeupdate', (data) => {
127
+ // This event fires frequently - use with caution
128
+ // console.log('Time update', data);
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Create custom UI elements
134
+ */
135
+ createUI() {
136
+ // Add a custom button to the player controls
137
+ const button = this.player.addPluginControlButton({
138
+ id: 'example-plugin-btn',
139
+ icon: '🎯',
140
+ tooltip: 'Example Plugin Action',
141
+ position: 'right', // 'left' or 'right'
142
+ onClick: (e, player) => {
143
+ alert(this.options.customMessage);
144
+ console.log('Example plugin button clicked!');
145
+ },
146
+ className: 'example-plugin-button'
147
+ });
148
+
149
+ if (button && this.player.options.debug) {
150
+ console.log('🔌 Example Plugin: Custom button created');
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Public method - can be called from outside
156
+ */
157
+ doSomething() {
158
+ console.log('Example Plugin: doSomething() called');
159
+ alert(this.options.customMessage);
160
+ }
161
+
162
+ /**
163
+ * Get plugin status
164
+ */
165
+ getStatus() {
166
+ return {
167
+ isActive: this.isActive,
168
+ options: this.options
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Dispose method - cleanup when plugin is removed
174
+ */
175
+ dispose() {
176
+ // Remove custom UI elements
177
+ this.player.removePluginControlButton('example-plugin-btn');
178
+
179
+ // Cleanup any timers, event listeners, etc.
180
+ this.isActive = false;
181
+
182
+ if (this.player.options.debug) {
183
+ console.log('🔌 Example Plugin disposed');
184
+ }
185
+ }
186
+ }
187
+
188
+ // Register the plugin globally
189
+ if (typeof window.registerMYETVPlugin === 'function') {
190
+ window.registerMYETVPlugin('examplePlugin', ExamplePlugin);
191
+ } else {
192
+ console.error('🔌 MYETV Player plugin system not found. Make sure to load the player first.');
193
+ }
194
+
195
+ })(window);
196
+ ```
197
+
198
+ ---
199
+
200
+ ## API Reference
201
+
202
+ ### Player Instance Methods
203
+
204
+ The `player` instance passed to your plugin provides access to:
205
+
206
+ #### Playback Control
207
+
208
+ ```javascript
209
+ player.play() // Play video
210
+ player.pause() // Pause video
211
+ player.togglePlayPause() // Toggle play/pause
212
+ player.getCurrentTime() // Get current time in seconds
213
+ player.setCurrentTime(time) // Set current time
214
+ player.getDuration() // Get video duration
215
+ ```
216
+
217
+ #### Volume Control
218
+
219
+ ```javascript
220
+ player.getVolume() // Get volume (0-1)
221
+ player.setVolume(volume) // Set volume (0-1)
222
+ player.isMuted() // Check if muted
223
+ player.setMuted(muted) // Mute/unmute
224
+ ```
225
+
226
+ #### Quality Control
227
+
228
+ ```javascript
229
+ player.getQualities() // Get available qualities
230
+ player.setQuality(quality) // Set quality
231
+ player.getSelectedQuality() // Get selected quality
232
+ player.getCurrentPlayingQuality() // Get actual playing quality
233
+ ```
234
+
235
+ #### Event System
236
+
237
+ ```javascript
238
+ player.addEventListener(eventType, callback)
239
+ player.removeEventListener(eventType, callback)
240
+ player.triggerEvent(eventType, data)
241
+ ```
242
+
243
+ #### UI Control
244
+
245
+ ```javascript
246
+ player.addPluginControlButton(config)
247
+ player.removePluginControlButton(buttonId)
248
+ ```
249
+
250
+ ### Available Events
251
+
252
+ | Event | Description | Data |
253
+ |-------|-------------|------|
254
+ | `played` | Video started playing | `{currentTime, duration}` |
255
+ | `paused` | Video paused | `{currentTime, duration}` |
256
+ | `timeupdate` | Time position updated | `{currentTime, duration, progress}` |
257
+ | `volumechange` | Volume changed | `{volume, muted}` |
258
+ | `qualitychange` | Quality changed | `{quality, previousQuality}` |
259
+ | `speedchange` | Playback speed changed | `{speed, previousSpeed}` |
260
+ | `ended` | Video ended | `{currentTime, duration}` |
261
+ | `fullscreenchange` | Fullscreen toggled | `{isFullscreen}` |
262
+ | `pipchange` | Picture-in-Picture toggled | `{isPiP}` |
263
+ | `subtitlechange` | Subtitle changed | `{track, enabled}` |
264
+
265
+ ---
266
+
267
+ ## HTML Integration
268
+
269
+ ### Method 1: Load Plugin Before Player Initialization
270
+
271
+ ```html
272
+ <!DOCTYPE html>
273
+ <html lang="en">
274
+ <head>
275
+ <meta charset="UTF-8">
276
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
277
+ <title>MYETV Player with Plugin</title>
278
+
279
+ <!-- Player CSS -->
280
+ <link rel="stylesheet" href="dist/myetv-player.css">
281
+ </head>
282
+ <body>
283
+ <!-- Video Element -->
284
+ <video id="myVideo" class="video-player">
285
+ <source src="video-720p.mp4" data-quality="720p" type="video/mp4">
286
+ <source src="video-480p.mp4" data-quality="480p" type="video/mp4">
287
+ </video>
288
+
289
+ <!-- Load Player Core -->
290
+ <script src="dist/myetv-player.js"></script>
291
+
292
+ <!-- Load Your Plugin -->
293
+ <script src="plugins/myetv-plugin-example.js"></script>
294
+
295
+ <!-- Initialize Player with Plugin -->
296
+ <script>
297
+ const player = new MYETVPlayer('myVideo', {
298
+ debug: true,
299
+ // Load plugin during initialization
300
+ plugins: {
301
+ examplePlugin: {
302
+ enabled: true,
303
+ customMessage: 'Hello from my custom plugin!'
304
+ }
305
+ }
306
+ });
307
+ </script>
308
+ </body>
309
+ </html>
310
+ ```
311
+
312
+ ---
313
+
314
+ ### Method 2: Load Plugin After Player Initialization
315
+
316
+ ```html
317
+ <!DOCTYPE html>
318
+ <html lang="en">
319
+ <head>
320
+ <meta charset="UTF-8">
321
+ <title>MYETV Player - Dynamic Plugin Loading</title>
322
+ <link rel="stylesheet" href="dist/myetv-player.css">
323
+ </head>
324
+ <body>
325
+ <video id="myVideo" class="video-player">
326
+ <source src="video.mp4" type="video/mp4">
327
+ </video>
328
+
329
+ <script src="dist/myetv-player.js"></script>
330
+ <script src="plugins/myetv-plugin-example.js"></script>
331
+
332
+ <script>
333
+ // Initialize player first
334
+ const player = new MYETVPlayer('myVideo', {
335
+ debug: true
336
+ });
337
+
338
+ // Load plugin dynamically later
339
+ player.usePlugin('examplePlugin', {
340
+ enabled: true,
341
+ customMessage: 'Dynamically loaded plugin!'
342
+ });
343
+
344
+ // Access plugin instance
345
+ const pluginInstance = player.getPlugin('examplePlugin');
346
+
347
+ // Call plugin methods
348
+ if (pluginInstance) {
349
+ pluginInstance.doSomething();
350
+ }
351
+ </script>
352
+ </body>
353
+ </html>
354
+ ```
355
+
356
+ ---
357
+
358
+ ### Method 3: Multiple Plugins
359
+
360
+ ```html
361
+ <!DOCTYPE html>
362
+ <html lang="en">
363
+ <head>
364
+ <meta charset="UTF-8">
365
+ <title>MYETV Player - Multiple Plugins</title>
366
+ <link rel="stylesheet" href="dist/myetv-player.css">
367
+ </head>
368
+ <body>
369
+ <video id="myVideo" class="video-player">
370
+ <source src="video.mp4" type="video/mp4">
371
+ </video>
372
+
373
+ <script src="dist/myetv-player.js"></script>
374
+
375
+ <!-- Load multiple plugins -->
376
+ <script src="plugins/myetv-plugin-example.js"></script>
377
+ <script src="plugins/myetv-plugin-analytics.js"></script>
378
+ <script src="plugins/myetv-plugin-watermark.js"></script>
379
+
380
+ <script>
381
+ const player = new MYETVPlayer('myVideo', {
382
+ debug: true,
383
+ plugins: {
384
+ // Load multiple plugins at once
385
+ examplePlugin: {
386
+ enabled: true,
387
+ customMessage: 'Plugin 1 loaded!'
388
+ },
389
+ analyticsPlugin: {
390
+ trackingId: 'UA-XXXXX-Y',
391
+ sendEvents: true
392
+ },
393
+ watermarkPlugin: {
394
+ imageUrl: 'logo.png',
395
+ position: 'top-right'
396
+ }
397
+ }
398
+ });
399
+
400
+ // Check which plugins are loaded
401
+ console.log('Active plugins:', player.getActivePlugins());
402
+ </script>
403
+ </body>
404
+ </html>
405
+ ```
406
+
407
+ ---
408
+
409
+ ### Method 4: Conditional Plugin Loading
410
+
411
+ ```html
412
+ <script>
413
+ const player = new MYETVPlayer('myVideo', {
414
+ debug: true
415
+ });
416
+
417
+ // Load plugin based on condition
418
+ if (window.innerWidth < 768) {
419
+ // Load mobile-specific plugin
420
+ player.usePlugin('mobilePlugin', {
421
+ enableTouchGestures: true
422
+ });
423
+ } else {
424
+ // Load desktop-specific plugin
425
+ player.usePlugin('desktopPlugin', {
426
+ enableKeyboardShortcuts: true
427
+ });
428
+ }
429
+
430
+ // Load plugin asynchronously
431
+ async function loadPluginAsync() {
432
+ try {
433
+ // Dynamically import plugin
434
+ await import('./plugins/myetv-plugin-async.js');
435
+
436
+ // Use plugin after loading
437
+ player.usePlugin('asyncPlugin', {
438
+ option1: 'value1'
439
+ });
440
+
441
+ console.log('Async plugin loaded successfully');
442
+ } catch (error) {
443
+ console.error('Failed to load async plugin:', error);
444
+ }
445
+ }
446
+
447
+ loadPluginAsync();
448
+ </script>
449
+ ```
450
+
451
+ ---
452
+
453
+ ## Advanced Examples
454
+
455
+ ### Example 1: Analytics Plugin
456
+
457
+ ```javascript
458
+ /**
459
+ * Analytics Plugin - Track video viewing analytics
460
+ */
461
+ (function(window) {
462
+ 'use strict';
463
+
464
+ class AnalyticsPlugin {
465
+ constructor(player, options) {
466
+ this.player = player;
467
+ this.options = Object.assign({
468
+ trackingId: '',
469
+ sendEvents: true,
470
+ trackMilestones: true
471
+ }, options);
472
+
473
+ this.analytics = {
474
+ sessionId: this.generateSessionId(),
475
+ startTime: null,
476
+ endTime: null,
477
+ totalWatchTime: 0,
478
+ pauseCount: 0,
479
+ seekCount: 0,
480
+ qualityChanges: 0,
481
+ milestones: []
482
+ };
483
+ }
484
+
485
+ setup() {
486
+ this.analytics.startTime = new Date();
487
+ this.bindAnalyticsEvents();
488
+ console.log('Analytics Plugin: Tracking started', this.analytics.sessionId);
489
+ }
490
+
491
+ bindAnalyticsEvents() {
492
+ // Track play
493
+ this.player.addEventListener('played', () => {
494
+ this.sendEvent('video_play');
495
+ });
496
+
497
+ // Track pause
498
+ this.player.addEventListener('paused', () => {
499
+ this.analytics.pauseCount++;
500
+ this.sendEvent('video_pause', { pauseCount: this.analytics.pauseCount });
501
+ });
502
+
503
+ // Track completion
504
+ this.player.addEventListener('ended', () => {
505
+ this.analytics.endTime = new Date();
506
+ this.sendEvent('video_complete', this.getAnalyticsSummary());
507
+ });
508
+
509
+ // Track quality changes
510
+ this.player.addEventListener('qualitychange', (data) => {
511
+ this.analytics.qualityChanges++;
512
+ this.sendEvent('quality_change', data);
513
+ });
514
+
515
+ // Track watch time
516
+ this.player.addEventListener('timeupdate', (data) => {
517
+ this.analytics.totalWatchTime = data.currentTime;
518
+
519
+ // Track milestones
520
+ if (this.options.trackMilestones) {
521
+ this.checkMilestones(data.progress);
522
+ }
523
+ });
524
+ }
525
+
526
+ checkMilestones(progress) {
527
+ const milestones = [25, 50, 75, 90];
528
+
529
+ milestones.forEach(milestone => {
530
+ if (progress >= milestone && !this.analytics.milestones.includes(milestone)) {
531
+ this.analytics.milestones.push(milestone);
532
+ this.sendEvent('milestone_reached', { milestone: milestone });
533
+ }
534
+ });
535
+ }
536
+
537
+ sendEvent(eventName, data = {}) {
538
+ if (!this.options.sendEvents) return;
539
+
540
+ const eventData = {
541
+ sessionId: this.analytics.sessionId,
542
+ trackingId: this.options.trackingId,
543
+ event: eventName,
544
+ timestamp: new Date().toISOString(),
545
+ videoTitle: this.player.getVideoTitle(),
546
+ ...data
547
+ };
548
+
549
+ // Send to your analytics backend
550
+ console.log('Analytics Event:', eventData);
551
+
552
+ // Example: Send to Google Analytics
553
+ if (window.gtag) {
554
+ window.gtag('event', eventName, eventData);
555
+ }
556
+ }
557
+
558
+ getAnalyticsSummary() {
559
+ return {
560
+ ...this.analytics,
561
+ duration: this.analytics.endTime - this.analytics.startTime
562
+ };
563
+ }
564
+
565
+ generateSessionId() {
566
+ return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
567
+ }
568
+
569
+ dispose() {
570
+ console.log('Analytics Plugin: Final summary', this.getAnalyticsSummary());
571
+ }
572
+ }
573
+
574
+ window.registerMYETVPlugin('analyticsPlugin', AnalyticsPlugin);
575
+
576
+ })(window);
577
+ ```
578
+
579
+ ---
580
+
581
+ ### Example 2: Watermark Plugin
582
+
583
+ ```javascript
584
+ /**
585
+ * Custom Watermark Plugin
586
+ */
587
+ (function(window) {
588
+ 'use strict';
589
+
590
+ class WatermarkPlugin {
591
+ constructor(player, options) {
592
+ this.player = player;
593
+ this.options = Object.assign({
594
+ imageUrl: '',
595
+ position: 'top-right', // top-left, top-right, bottom-left, bottom-right
596
+ opacity: 0.7,
597
+ size: '80px',
598
+ link: '',
599
+ fadeOnControls: true
600
+ }, options);
601
+
602
+ this.watermarkElement = null;
603
+ }
604
+
605
+ setup() {
606
+ if (!this.options.imageUrl) {
607
+ console.warn('🔌 Watermark Plugin: No image URL provided');
608
+ return;
609
+ }
610
+
611
+ this.createWatermark();
612
+
613
+ if (this.options.fadeOnControls) {
614
+ this.bindControlsEvents();
615
+ }
616
+ }
617
+
618
+ createWatermark() {
619
+ const watermark = document.createElement('div');
620
+ watermark.className = 'custom-watermark';
621
+ watermark.style.cssText = `
622
+ position: absolute;
623
+ z-index: 10;
624
+ opacity: ${this.options.opacity};
625
+ transition: opacity 0.3s;
626
+ pointer-events: ${this.options.link ? 'auto' : 'none'};
627
+ `;
628
+
629
+ // Position
630
+ const positions = {
631
+ 'top-left': 'top: 10px; left: 10px;',
632
+ 'top-right': 'top: 10px; right: 10px;',
633
+ 'bottom-left': 'bottom: 60px; left: 10px;',
634
+ 'bottom-right': 'bottom: 60px; right: 10px;'
635
+ };
636
+ watermark.style.cssText += positions[this.options.position] || positions['top-right'];
637
+
638
+ // Create image
639
+ const img = document.createElement('img');
640
+ img.src = this.options.imageUrl;
641
+ img.style.cssText = `
642
+ width: ${this.options.size};
643
+ height: auto;
644
+ display: block;
645
+ `;
646
+
647
+ // Add link if provided
648
+ if (this.options.link) {
649
+ const link = document.createElement('a');
650
+ link.href = this.options.link;
651
+ link.target = '_blank';
652
+ link.rel = 'noopener noreferrer';
653
+ link.appendChild(img);
654
+ watermark.appendChild(link);
655
+ } else {
656
+ watermark.appendChild(img);
657
+ }
658
+
659
+ // Add to player container
660
+ this.player.container.appendChild(watermark);
661
+ this.watermarkElement = watermark;
662
+
663
+ console.log('🔌 Watermark Plugin: Watermark created');
664
+ }
665
+
666
+ bindControlsEvents() {
667
+ // Fade watermark when controls are shown
668
+ this.player.addEventListener('controlsshown', () => {
669
+ if (this.watermarkElement) {
670
+ this.watermarkElement.style.opacity = '0.3';
671
+ }
672
+ });
673
+
674
+ this.player.addEventListener('controlshidden', () => {
675
+ if (this.watermarkElement) {
676
+ this.watermarkElement.style.opacity = this.options.opacity;
677
+ }
678
+ });
679
+ }
680
+
681
+ dispose() {
682
+ if (this.watermarkElement) {
683
+ this.watermarkElement.remove();
684
+ this.watermarkElement = null;
685
+ }
686
+ console.log('🔌 Watermark Plugin disposed');
687
+ }
688
+ }
689
+
690
+ window.registerMYETVPlugin('watermarkPlugin', WatermarkPlugin);
691
+
692
+ })(window);
693
+ ```
694
+
695
+ ---
696
+
697
+ ### Example 3: Keyboard Shortcuts Plugin
698
+
699
+ ```javascript
700
+ /**
701
+ * Enhanced Keyboard Shortcuts Plugin
702
+ */
703
+ (function(window) {
704
+ 'use strict';
705
+
706
+ class KeyboardShortcutsPlugin {
707
+ constructor(player, options) {
708
+ this.player = player;
709
+ this.options = Object.assign({
710
+ enabled: true,
711
+ shortcuts: {
712
+ 'Space': 'togglePlayPause',
713
+ 'ArrowLeft': 'seekBackward',
714
+ 'ArrowRight': 'seekForward',
715
+ 'ArrowUp': 'volumeUp',
716
+ 'ArrowDown': 'volumeDown',
717
+ 'KeyM': 'toggleMute',
718
+ 'KeyF': 'toggleFullscreen',
719
+ 'KeyP': 'togglePiP',
720
+ 'KeyS': 'toggleSubtitles',
721
+ 'Digit0': 'seekToStart',
722
+ 'Digit1': 'seekToPercent10',
723
+ 'Digit2': 'seekToPercent20',
724
+ 'Digit3': 'seekToPercent30',
725
+ 'Digit4': 'seekToPercent40',
726
+ 'Digit5': 'seekToPercent50',
727
+ 'Digit6': 'seekToPercent60',
728
+ 'Digit7': 'seekToPercent70',
729
+ 'Digit8': 'seekToPercent80',
730
+ 'Digit9': 'seekToPercent90'
731
+ },
732
+ seekStep: 10, // seconds
733
+ volumeStep: 0.1 // 0-1
734
+ }, options);
735
+
736
+ this.boundKeyHandler = null;
737
+ }
738
+
739
+ setup() {
740
+ if (!this.options.enabled) return;
741
+
742
+ this.boundKeyHandler = this.handleKeyPress.bind(this);
743
+ document.addEventListener('keydown', this.boundKeyHandler);
744
+
745
+ console.log('Keyboard Shortcuts Plugin: Enabled');
746
+ }
747
+
748
+ handleKeyPress(e) {
749
+ // Ignore if typing in input
750
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
751
+ return;
752
+ }
753
+
754
+ const action = this.options.shortcuts[e.code];
755
+ if (!action) return;
756
+
757
+ e.preventDefault();
758
+
759
+ // Execute action
760
+ this.executeAction(action);
761
+ }
762
+
763
+ executeAction(action) {
764
+ const actions = {
765
+ togglePlayPause: () => this.player.togglePlayPause(),
766
+ seekBackward: () => this.player.setCurrentTime(this.player.getCurrentTime() - this.options.seekStep),
767
+ seekForward: () => this.player.setCurrentTime(this.player.getCurrentTime() + this.options.seekStep),
768
+ volumeUp: () => this.player.setVolume(Math.min(1, this.player.getVolume() + this.options.volumeStep)),
769
+ volumeDown: () => this.player.setVolume(Math.max(0, this.player.getVolume() - this.options.volumeStep)),
770
+ toggleMute: () => this.player.setMuted(!this.player.isMuted()),
771
+ toggleFullscreen: () => this.player.toggleFullscreen(),
772
+ togglePiP: () => this.player.togglePictureInPicture(),
773
+ toggleSubtitles: () => this.player.toggleSubtitles(),
774
+ seekToStart: () => this.player.setCurrentTime(0),
775
+ seekToPercent10: () => this.seekToPercent(10),
776
+ seekToPercent20: () => this.seekToPercent(20),
777
+ seekToPercent30: () => this.seekToPercent(30),
778
+ seekToPercent40: () => this.seekToPercent(40),
779
+ seekToPercent50: () => this.seekToPercent(50),
780
+ seekToPercent60: () => this.seekToPercent(60),
781
+ seekToPercent70: () => this.seekToPercent(70),
782
+ seekToPercent80: () => this.seekToPercent(80),
783
+ seekToPercent90: () => this.seekToPercent(90)
784
+ };
785
+
786
+ if (actions[action]) {
787
+ actions[action]();
788
+ }
789
+ }
790
+
791
+ seekToPercent(percent) {
792
+ const duration = this.player.getDuration();
793
+ if (duration > 0) {
794
+ this.player.setCurrentTime((duration * percent) / 100);
795
+ }
796
+ }
797
+
798
+ dispose() {
799
+ if (this.boundKeyHandler) {
800
+ document.removeEventListener('keydown', this.boundKeyHandler);
801
+ this.boundKeyHandler = null;
802
+ }
803
+ console.log('Keyboard Shortcuts Plugin disposed');
804
+ }
805
+ }
806
+
807
+ window.registerMYETVPlugin('keyboardShortcutsPlugin', KeyboardShortcutsPlugin);
808
+
809
+ })(window);
810
+ ```
811
+
812
+ ---
813
+
814
+ ## Best Practices
815
+
816
+ ### 1. Always Check Debug Mode
817
+
818
+ ```javascript
819
+ if (this.player.options.debug) {
820
+ console.log('Plugin debug message');
821
+ }
822
+ ```
823
+
824
+ ### 2. Handle Errors Gracefully
825
+
826
+ ```javascript
827
+ setup() {
828
+ try {
829
+ this.createUI();
830
+ } catch (error) {
831
+ console.error('Plugin setup failed:', error);
832
+ }
833
+ }
834
+ ```
835
+
836
+ ### 3. Clean Up Resources
837
+
838
+ ```javascript
839
+ dispose() {
840
+ // Remove event listeners
841
+ // Clear timers
842
+ // Remove DOM elements
843
+ // Reset state
844
+ }
845
+ ```
846
+
847
+ ### 4. Provide Default Options
848
+
849
+ ```javascript
850
+ this.options = Object.assign({
851
+ enabled: true,
852
+ option1: 'default1',
853
+ option2: 'default2'
854
+ }, options);
855
+ ```
856
+
857
+ ### 5. Use Namespaced CSS Classes
858
+
859
+ ```javascript
860
+ button.className = 'myplugin-button'; // Not just 'button'
861
+ ```
862
+
863
+ ### 6. Document Your Plugin
864
+
865
+ ```javascript
866
+ /**
867
+ * MyPlugin - Description
868
+ * @param {Object} player - Player instance
869
+ * @param {Object} options - Plugin options
870
+ * @param {Boolean} options.enabled - Enable/disable plugin
871
+ * @param {String} options.customOption - Custom option description
872
+ */
873
+ ```
874
+
875
+ ---
876
+
877
+ ## Debugging
878
+
879
+ ### Enable Debug Mode
880
+
881
+ ```javascript
882
+ const player = new MYETVPlayer('myVideo', {
883
+ debug: true // Enable debug logging
884
+ });
885
+ ```
886
+
887
+ ### Check Plugin Status
888
+
889
+ ```javascript
890
+ // Check if plugin is loaded
891
+ if (player.hasPlugin('myPlugin')) {
892
+ console.log('Plugin is loaded');
893
+ }
894
+
895
+ // Get plugin instance
896
+ const plugin = player.getPlugin('myPlugin');
897
+ console.log('Plugin instance:', plugin);
898
+
899
+ // Get all active plugins
900
+ console.log('Active plugins:', player.getActivePlugins());
901
+ ```
902
+
903
+ ### Console Debugging
904
+
905
+ ```javascript
906
+ // In your plugin
907
+ setup() {
908
+ console.log('[MyPlugin] Setup started');
909
+ console.log('[MyPlugin] Options:', this.options);
910
+ console.log('[MyPlugin] Player state:', this.player.getPlayerState());
911
+ }
912
+ ```
913
+
914
+ ### Testing Plugin Events
915
+
916
+ ```javascript
917
+ const player = new MYETVPlayer('myVideo', { debug: true });
918
+
919
+ // Listen to plugin setup events
920
+ player.addEventListener('pluginsetup', (data) => {
921
+ console.log('Plugin setup:', data);
922
+ });
923
+
924
+ player.addEventListener('pluginsetup:myPlugin', (data) => {
925
+ console.log('My specific plugin setup:', data);
926
+ });
927
+ ```
928
+
929
+ ---
930
+
931
+ ## FAQ
932
+
933
+ ### Q: Can I create a plugin without modifying core files?
934
+
935
+ **A:** Yes! That's the whole point. Plugins are completely separate from the core player.
936
+
937
+ ### Q: How do I pass data between plugins?
938
+
939
+ **A:** Use the player's event system or store data in `player.data` object.
940
+
941
+ ```javascript
942
+ // Plugin A
943
+ this.player.data = this.player.data || {};
944
+ this.player.data.sharedValue = 'some data';
945
+
946
+ // Plugin B
947
+ const sharedValue = this.player.data?.sharedValue;
948
+ ```
949
+
950
+ ### Q: Can I override core player methods?
951
+
952
+ **A:** Not recommended, but technically possible. Use hooks and events instead.
953
+
954
+ ### Q: How do I distribute my plugin?
955
+
956
+ **A:** Publish it as a standalone JavaScript file. Users can include it via `<script>` tag or module import.
957
+
958
+ ### Q: Can plugins work with multiple player instances?
959
+
960
+ **A:** Yes! Each player instance gets its own plugin instance.
961
+
962
+ ```javascript
963
+ const player1 = new MYETVPlayer('video1');
964
+ const player2 = new MYETVPlayer('video2');
965
+
966
+ player1.usePlugin('myPlugin', { option: 'value1' });
967
+ player2.usePlugin('myPlugin', { option: 'value2' });
968
+ ```
969
+
970
+ ### Q: How do I make my plugin configurable?
971
+
972
+ **A:** Accept options in the constructor and provide defaults.
973
+
974
+ ```javascript
975
+ constructor(player, options) {
976
+ this.options = Object.assign({
977
+ // Defaults
978
+ setting1: 'default',
979
+ setting2: true
980
+ }, options);
981
+ }
982
+ ```
983
+
984
+ ### Q: Can I use async/await in plugins?
985
+
986
+ **A:** Yes! Plugins fully support modern JavaScript features.
987
+
988
+ ```javascript
989
+ async setup() {
990
+ const data = await fetch('/api/plugin-data');
991
+ this.data = await data.json();
992
+ }
993
+ ```
994
+
995
+ ---
996
+
997
+ ## 📝 License
998
+
999
+ MIT License - See main project for details.
1000
+
1001
+ ---
1002
+
1003
+ ## Contributing
1004
+
1005
+ Contributions are welcome! Please submit pull requests or open issues on GitHub.
1006
+
1007
+ ---
1008
+
1009
+ ## Support
1010
+
1011
+ - **GitHub**: [MYETV Video Player Open Source]([https://github.com/yourusername/myetv-player](https://github.com/OskarCosimo/myetv-video-player-opensource/))
1012
+ - **Website**: [https://www.myetv.tv](https://www.myetv.tv)
1013
+ - **Author**: [https://oskarcosimo.com](https://oskarcosimo.com)
1014
+
1015
+ ---
1016
+
1017
+ **Happy Plugin Development!**