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,1068 @@
1
+ # MYETV Player - Cloudflare Stream Plugin
2
+ Official Cloudflare Stream integration plugin for MYETV Video Player. Embed videos from Cloudflare Stream with full API control, live streaming, and enterprise features.
3
+
4
+ ---
5
+
6
+ ## Table of Contents
7
+
8
+ - [Features](#features)
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Configuration Options](#configuration-options)
12
+ - [Public vs Private Videos](#public-vs-private-videos)
13
+ - [Usage Methods](#usage-methods)
14
+ - [API Methods](#api-methods)
15
+ - [Events](#events)
16
+ - [Player Customization](#player-customization)
17
+ - [Examples](#examples)
18
+ - [FAQ](#faq)
19
+ - [Troubleshooting](#troubleshooting)
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ - **Cloudflare Stream Integration**: Full support for Cloudflare Stream videos
26
+ - **Private Videos**: Support for signed URLs and private video access
27
+ - **Live Streaming**: Real-time live stream playback
28
+ - **Player Customization**: Custom colors, poster images, and branding
29
+ - **Auto-Detection**: Automatically detects Cloudflare Stream URLs
30
+ - **Complete API**: Full control over playback, volume, seeking
31
+ - **Analytics Ready**: Works with Cloudflare Analytics
32
+ - **Ad Support**: VAST ad tag integration
33
+ - **Easy Integration**: Seamless MYETV Player integration
34
+ - **Responsive**: Works perfectly on all devices
35
+ - **Global CDN**: Powered by Cloudflare's global network
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ### Method 1: Direct Script Include
42
+
43
+ ```html
44
+ <!-- Load MYETV Player Core -->
45
+ <script src="dist/myetv-player.js"></script>
46
+
47
+ <!-- Load Cloudflare Stream Plugin -->
48
+ <script src="plugins/myetv-player-cloudflare-plugin.js"></script>
49
+ ```
50
+
51
+ ### Method 2: Module Import
52
+
53
+ ```javascript
54
+ import MYETVPlayer from './myetv-player.js';
55
+ import './plugins/myetv-player-cloudflare-plugin.js';
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Quick Start
61
+
62
+ ### Basic Example
63
+
64
+ ```html
65
+ <!DOCTYPE html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="UTF-8">
69
+ <title>MYETV Player - Cloudflare Stream</title>
70
+ <link rel="stylesheet" href="dist/myetv-player.css">
71
+ </head>
72
+ <body>
73
+ <!-- Video Element -->
74
+ <video id="myVideo" class="video-player"></video>
75
+
76
+ <script src="dist/myetv-player.js"></script>
77
+ <script src="plugins/myetv-player-cloudflare-plugin.js"></script>
78
+
79
+ <script>
80
+ // Initialize player with Cloudflare Stream
81
+ const player = new MYETVPlayer('myVideo', {
82
+ debug: true,
83
+ plugins: {
84
+ cloudflare: {
85
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81', // Your video ID
86
+ autoplay: false,
87
+ controls: true
88
+ }
89
+ }
90
+ });
91
+ </script>
92
+ </body>
93
+ </html>
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Configuration Options
99
+
100
+ ```javascript
101
+ const player = new MYETVPlayer('myVideo', {
102
+ plugins: {
103
+ cloudflare: {
104
+ // ========== Video Source (choose one) ==========
105
+ // Option 1: Video ID (most common)
106
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
107
+
108
+ // Option 2: Full video URL
109
+ videoUrl: 'https://iframe.videodelivery.net/5d5bc37ffcf54c9b82e996823bffbb81',
110
+
111
+ // Option 3: Signed URL (for private videos)
112
+ signedUrl: 'https://iframe.videodelivery.net/5d5bc37f...?token=xyz',
113
+
114
+ // ========== Account Settings ==========
115
+ // Your Cloudflare customer code (optional for most cases)
116
+ customerCode: 'abc123def',
117
+
118
+ // ========== Playback Options ==========
119
+ autoplay: false, // Auto-play on load
120
+ muted: false, // Start muted
121
+ loop: false, // Loop playback
122
+ preload: 'metadata', // 'none', 'metadata', 'auto'
123
+ controls: true, // Show player controls
124
+ startTime: 0, // Start position in seconds
125
+
126
+ // ========== Player Customization ==========
127
+ poster: 'https://example.com/poster.jpg', // Custom poster image
128
+ primaryColor: '#ff6600', // Player accent color
129
+ letterboxColor: 'black', // Letterbox background
130
+
131
+ // ========== Subtitles/Captions ==========
132
+ defaultTextTrack: 'en', // Default subtitle language
133
+
134
+ // ========== Advanced Features ==========
135
+ adUrl: 'https://example.com/vast.xml', // VAST ad tag URL
136
+
137
+ // ========== Plugin Options ==========
138
+ debug: false, // Enable debug logging
139
+ replaceNativePlayer: true, // Replace native video element
140
+ autoLoadFromData: true, // Auto-detect from data attributes
141
+ responsive: true // Responsive sizing
142
+ }
143
+ }
144
+ });
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Public vs Private Videos
150
+
151
+ ### Public Videos
152
+
153
+ Public videos can be embedded anywhere without restrictions.
154
+
155
+ ```javascript
156
+ plugins: {
157
+ cloudflare: {
158
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81'
159
+ // That's it! No authentication needed
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ### Private Videos
167
+
168
+ Private videos require a **signed URL** with a time-limited token.
169
+
170
+ #### How to Generate Signed URLs:
171
+
172
+ 1. **In Cloudflare Dashboard:**
173
+ - Go to Stream → Your Video
174
+ - Set "Require signed URLs" to ON
175
+ - Generate a signing key
176
+
177
+ 2. **Generate Token (Server-Side):**
178
+
179
+ ```javascript
180
+ // Node.js example
181
+ const crypto = require('crypto');
182
+
183
+ function generateSignedUrl(videoId, signingKey, expiresIn = 3600) {
184
+ const expires = Math.floor(Date.now() / 1000) + expiresIn;
185
+ const toSign = `${videoId}${expires}`;
186
+ const signature = crypto
187
+ .createHmac('sha256', signingKey)
188
+ .update(toSign)
189
+ .digest('hex');
190
+
191
+ return `https://iframe.videodelivery.net/${videoId}?token=${signature}&expires=${expires}`;
192
+ }
193
+
194
+ const signedUrl = generateSignedUrl(
195
+ '5d5bc37ffcf54c9b82e996823bffbb81',
196
+ 'YOUR_SIGNING_KEY',
197
+ 3600 // 1 hour
198
+ );
199
+ ```
200
+
201
+ 3. **Use in Plugin:**
202
+
203
+ ```javascript
204
+ plugins: {
205
+ cloudflare: {
206
+ signedUrl: signedUrl // Pass the signed URL
207
+ }
208
+ }
209
+ ```
210
+
211
+ **Important Notes:**
212
+ - Generate signed URLs **server-side** only
213
+ - Never expose your signing key in client code
214
+ - Set appropriate expiration times
215
+ - Tokens expire automatically for security
216
+
217
+ ---
218
+
219
+ ## Usage Methods
220
+
221
+ ### Method 1: Using Video ID
222
+
223
+ ```html
224
+ <video id="myVideo" class="video-player"></video>
225
+
226
+ <script>
227
+ const player = new MYETVPlayer('myVideo', {
228
+ plugins: {
229
+ cloudflare: {
230
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81'
231
+ }
232
+ }
233
+ });
234
+ </script>
235
+ ```
236
+
237
+ ---
238
+
239
+ ### Method 2: Using Data Attributes
240
+
241
+ ```html
242
+ <video id="myVideo" class="video-player"
243
+ data-cloudflare-video-id="5d5bc37ffcf54c9b82e996823bffbb81"
244
+ data-video-type="cloudflare">
245
+ </video>
246
+
247
+ <script>
248
+ const player = new MYETVPlayer('myVideo', {
249
+ plugins: {
250
+ cloudflare: {
251
+ autoLoadFromData: true
252
+ }
253
+ }
254
+ });
255
+ </script>
256
+ ```
257
+
258
+ ---
259
+
260
+ ### Method 3: Using Cloudflare URL
261
+
262
+ ```html
263
+ <video id="myVideo" class="video-player"
264
+ src="https://iframe.videodelivery.net/5d5bc37ffcf54c9b82e996823bffbb81">
265
+ </video>
266
+
267
+ <script>
268
+ const player = new MYETVPlayer('myVideo', {
269
+ plugins: {
270
+ cloudflare: {}
271
+ }
272
+ });
273
+ </script>
274
+ ```
275
+
276
+ **Supported URL Formats:**
277
+ - `https://iframe.videodelivery.net/VIDEO_ID`
278
+ - `https://customer-CODE.cloudflarestream.com/VIDEO_ID/iframe`
279
+ - `https://videodelivery.net/VIDEO_ID`
280
+
281
+ ---
282
+
283
+ ### Method 4: Load Dynamically
284
+
285
+ ```html
286
+ <video id="myVideo" class="video-player"></video>
287
+
288
+ <script>
289
+ const player = new MYETVPlayer('myVideo', {
290
+ plugins: { cloudflare: {} }
291
+ });
292
+
293
+ const cfPlugin = player.getPlugin('cloudflare');
294
+
295
+ // Load video after initialization
296
+ cfPlugin.loadVideo('5d5bc37ffcf54c9b82e996823bffbb81');
297
+ </script>
298
+ ```
299
+
300
+ ---
301
+
302
+ ## API Methods
303
+
304
+ Get the plugin instance:
305
+ ```javascript
306
+ const cfPlugin = player.getPlugin('cloudflare');
307
+ ```
308
+
309
+ ### Playback Control
310
+
311
+ #### `play()`
312
+ Play the video.
313
+
314
+ ```javascript
315
+ cfPlugin.play().then(() => {
316
+ console.log('Playing');
317
+ });
318
+ ```
319
+
320
+ **Returns:** Promise
321
+
322
+ ---
323
+
324
+ #### `pause()`
325
+ Pause the video.
326
+
327
+ ```javascript
328
+ cfPlugin.pause().then(() => {
329
+ console.log('Paused');
330
+ });
331
+ ```
332
+
333
+ **Returns:** Promise
334
+
335
+ ---
336
+
337
+ #### `seek(seconds)`
338
+ Seek to position.
339
+
340
+ ```javascript
341
+ cfPlugin.seek(60).then(() => {
342
+ console.log('Seeked to 1 minute');
343
+ });
344
+ ```
345
+
346
+ **Parameters:**
347
+ - `seconds` (Number): Position in seconds
348
+
349
+ **Returns:** Promise
350
+
351
+ ---
352
+
353
+ #### `getCurrentTime()`
354
+ Get current playback position.
355
+
356
+ ```javascript
357
+ cfPlugin.getCurrentTime().then(time => {
358
+ console.log('Current time:', time);
359
+ });
360
+ ```
361
+
362
+ **Returns:** Promise<Number>
363
+
364
+ ---
365
+
366
+ #### `getDuration()`
367
+ Get video duration.
368
+
369
+ ```javascript
370
+ cfPlugin.getDuration().then(duration => {
371
+ console.log('Duration:', duration);
372
+ });
373
+ ```
374
+
375
+ **Returns:** Promise<Number>
376
+
377
+ ---
378
+
379
+ #### `getPaused()`
380
+ Check if video is paused.
381
+
382
+ ```javascript
383
+ cfPlugin.getPaused().then(paused => {
384
+ console.log('Is paused:', paused);
385
+ });
386
+ ```
387
+
388
+ **Returns:** Promise<Boolean>
389
+
390
+ ---
391
+
392
+ ### Volume Control
393
+
394
+ #### `setVolume(volume)`
395
+ Set volume level.
396
+
397
+ ```javascript
398
+ cfPlugin.setVolume(0.5).then(() => {
399
+ console.log('Volume set to 50%');
400
+ });
401
+ ```
402
+
403
+ **Parameters:**
404
+ - `volume` (Number): Volume level (0-1)
405
+
406
+ **Returns:** Promise
407
+
408
+ ---
409
+
410
+ #### `getVolume()`
411
+ Get current volume.
412
+
413
+ ```javascript
414
+ cfPlugin.getVolume().then(volume => {
415
+ console.log('Current volume:', volume);
416
+ });
417
+ ```
418
+
419
+ **Returns:** Promise<Number>
420
+
421
+ ---
422
+
423
+ #### `mute()`
424
+ Mute the video.
425
+
426
+ ```javascript
427
+ cfPlugin.mute().then(() => {
428
+ console.log('Muted');
429
+ });
430
+ ```
431
+
432
+ **Returns:** Promise
433
+
434
+ ---
435
+
436
+ #### `unmute()`
437
+ Unmute the video.
438
+
439
+ ```javascript
440
+ cfPlugin.unmute().then(() => {
441
+ console.log('Unmuted');
442
+ });
443
+ ```
444
+
445
+ **Returns:** Promise
446
+
447
+ ---
448
+
449
+ #### `getMuted()`
450
+ Get muted state.
451
+
452
+ ```javascript
453
+ cfPlugin.getMuted().then(muted => {
454
+ console.log('Is muted:', muted);
455
+ });
456
+ ```
457
+
458
+ **Returns:** Promise<Boolean>
459
+
460
+ ---
461
+
462
+ ### Playback Speed
463
+
464
+ #### `setPlaybackRate(rate)`
465
+ Set playback speed.
466
+
467
+ ```javascript
468
+ cfPlugin.setPlaybackRate(1.5).then(() => {
469
+ console.log('Speed set to 1.5x');
470
+ });
471
+ ```
472
+
473
+ **Parameters:**
474
+ - `rate` (Number): Playback rate (0.25 - 2.0)
475
+
476
+ **Returns:** Promise
477
+
478
+ ---
479
+
480
+ ### Video Management
481
+
482
+ #### `loadVideo(videoId, customerCode)`
483
+ Load a new video.
484
+
485
+ ```javascript
486
+ cfPlugin.loadVideo('5d5bc37ffcf54c9b82e996823bffbb81').then(() => {
487
+ console.log('New video loaded');
488
+ });
489
+
490
+ // With customer code
491
+ cfPlugin.loadVideo('5d5bc37f...', 'abc123').then(() => {
492
+ console.log('Custom domain video loaded');
493
+ });
494
+ ```
495
+
496
+ **Parameters:**
497
+ - `videoId` (String): Cloudflare Stream video ID
498
+ - `customerCode` (String): Optional customer code
499
+
500
+ **Returns:** Promise
501
+
502
+ ---
503
+
504
+ ## 📡 Events
505
+
506
+ ### Playback Events
507
+
508
+ #### `play`
509
+ Video started playing.
510
+
511
+ ```javascript
512
+ player.addEventListener('play', () => {
513
+ console.log('Video playing');
514
+ });
515
+ ```
516
+
517
+ ---
518
+
519
+ #### `playing`
520
+ Video is actively playing.
521
+
522
+ ```javascript
523
+ player.addEventListener('playing', () => {
524
+ console.log('Playing');
525
+ });
526
+ ```
527
+
528
+ ---
529
+
530
+ #### `pause`
531
+ Video paused.
532
+
533
+ ```javascript
534
+ player.addEventListener('pause', () => {
535
+ console.log('Paused');
536
+ });
537
+ ```
538
+
539
+ ---
540
+
541
+ #### `ended`
542
+ Video ended.
543
+
544
+ ```javascript
545
+ player.addEventListener('ended', () => {
546
+ console.log('Video ended');
547
+ });
548
+ ```
549
+
550
+ ---
551
+
552
+ #### `timeupdate`
553
+ Playback position changed.
554
+
555
+ ```javascript
556
+ player.addEventListener('timeupdate', (data) => {
557
+ console.log('Current time:', data.currentTime);
558
+ console.log('Duration:', data.duration);
559
+ });
560
+ ```
561
+
562
+ ---
563
+
564
+ ### Volume Events
565
+
566
+ #### `volumechange`
567
+ Volume changed.
568
+
569
+ ```javascript
570
+ player.addEventListener('volumechange', (data) => {
571
+ console.log('Volume:', data.volume);
572
+ console.log('Muted:', data.muted);
573
+ });
574
+ ```
575
+
576
+ ---
577
+
578
+ ### Plugin Events
579
+
580
+ #### `cloudflare:playerready`
581
+ Player created and ready.
582
+
583
+ ```javascript
584
+ player.addEventListener('cloudflare:playerready', (data) => {
585
+ console.log('Player ready for video:', data.videoId);
586
+ });
587
+ ```
588
+
589
+ ---
590
+
591
+ #### `cloudflare:ready`
592
+ Video loaded and ready to play.
593
+
594
+ ```javascript
595
+ player.addEventListener('cloudflare:ready', () => {
596
+ console.log('Video ready');
597
+ });
598
+ ```
599
+
600
+ ---
601
+
602
+ #### `cloudflare:videoloaded`
603
+ New video loaded.
604
+
605
+ ```javascript
606
+ player.addEventListener('cloudflare:videoloaded', (data) => {
607
+ console.log('Video loaded:', data.videoId);
608
+ });
609
+ ```
610
+
611
+ ---
612
+
613
+ #### `cloudflare:error`
614
+ Player error occurred.
615
+
616
+ ```javascript
617
+ player.addEventListener('cloudflare:error', (error) => {
618
+ console.error('Cloudflare error:', error);
619
+ });
620
+ ```
621
+
622
+ ---
623
+
624
+ #### `loadedmetadata`
625
+ Video metadata loaded.
626
+
627
+ ```javascript
628
+ player.addEventListener('loadedmetadata', (data) => {
629
+ console.log('Metadata loaded:', data);
630
+ });
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Player Customization
636
+
637
+ ### Custom Colors
638
+
639
+ Match the player to your brand:
640
+
641
+ ```javascript
642
+ plugins: {
643
+ cloudflare: {
644
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
645
+ primaryColor: '#ff6600', // Accent color (controls, progress)
646
+ letterboxColor: '#000000' // Letterbox background
647
+ }
648
+ }
649
+ ```
650
+
651
+ ---
652
+
653
+ ### Custom Poster Image
654
+
655
+ Set a custom thumbnail:
656
+
657
+ ```javascript
658
+ plugins: {
659
+ cloudflare: {
660
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
661
+ poster: 'https://example.com/custom-poster.jpg'
662
+ }
663
+ }
664
+ ```
665
+
666
+ ---
667
+
668
+ ### Hide Controls
669
+
670
+ Create a custom UI:
671
+
672
+ ```javascript
673
+ plugins: {
674
+ cloudflare: {
675
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
676
+ controls: false // Hide default controls
677
+ }
678
+ }
679
+ ```
680
+
681
+ ---
682
+
683
+ ### Autoplay with Mute
684
+
685
+ For autoplay on mobile:
686
+
687
+ ```javascript
688
+ plugins: {
689
+ cloudflare: {
690
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
691
+ autoplay: true,
692
+ muted: true // Required for autoplay
693
+ }
694
+ }
695
+ ```
696
+
697
+ ---
698
+
699
+ ## Examples
700
+
701
+ ### Example 1: Video Gallery
702
+
703
+ ```html
704
+ <video id="myVideo" class="video-player"></video>
705
+
706
+ <div id="video-gallery">
707
+ <button onclick="loadCFVideo('video-id-1')">Video 1</button>
708
+ <button onclick="loadCFVideo('video-id-2')">Video 2</button>
709
+ <button onclick="loadCFVideo('video-id-3')">Video 3</button>
710
+ </div>
711
+
712
+ <script>
713
+ const player = new MYETVPlayer('myVideo', {
714
+ plugins: { cloudflare: {} }
715
+ });
716
+
717
+ const cfPlugin = player.getPlugin('cloudflare');
718
+
719
+ function loadCFVideo(videoId) {
720
+ cfPlugin.loadVideo(videoId).then(() => {
721
+ console.log('Loaded:', videoId);
722
+ });
723
+ }
724
+ </script>
725
+ ```
726
+
727
+ ---
728
+
729
+ ### Example 2: Custom Controls
730
+
731
+ ```html
732
+ <video id="myVideo" class="video-player"></video>
733
+
734
+ <div id="custom-controls">
735
+ <button id="playBtn">Play</button>
736
+ <button id="pauseBtn">Pause</button>
737
+ <input type="range" id="seekBar" min="0" max="100" value="0">
738
+ <input type="range" id="volumeSlider" min="0" max="100" value="100">
739
+ <span id="time">0:00 / 0:00</span>
740
+ </div>
741
+
742
+ <script>
743
+ const player = new MYETVPlayer('myVideo', {
744
+ plugins: {
745
+ cloudflare: {
746
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
747
+ controls: false // Use custom controls
748
+ }
749
+ }
750
+ });
751
+
752
+ const cfPlugin = player.getPlugin('cloudflare');
753
+
754
+ // Play button
755
+ document.getElementById('playBtn').onclick = () => {
756
+ cfPlugin.play();
757
+ };
758
+
759
+ // Pause button
760
+ document.getElementById('pauseBtn').onclick = () => {
761
+ cfPlugin.pause();
762
+ };
763
+
764
+ // Seek bar
765
+ document.getElementById('seekBar').oninput = (e) => {
766
+ cfPlugin.getDuration().then(duration => {
767
+ const seekTo = (e.target.value / 100) * duration;
768
+ cfPlugin.seek(seekTo);
769
+ });
770
+ };
771
+
772
+ // Volume slider
773
+ document.getElementById('volumeSlider').oninput = (e) => {
774
+ const volume = e.target.value / 100;
775
+ cfPlugin.setVolume(volume);
776
+ };
777
+
778
+ // Update time display
779
+ player.addEventListener('timeupdate', (data) => {
780
+ const seekBar = document.getElementById('seekBar');
781
+ seekBar.value = (data.currentTime / data.duration) * 100;
782
+
783
+ document.getElementById('time').textContent =
784
+ `${formatTime(data.currentTime)} / ${formatTime(data.duration)}`;
785
+ });
786
+
787
+ function formatTime(seconds) {
788
+ const mins = Math.floor(seconds / 60);
789
+ const secs = Math.floor(seconds % 60);
790
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
791
+ }
792
+ </script>
793
+ ```
794
+
795
+ ---
796
+
797
+ ### Example 3: Private Video with Signed URL
798
+
799
+ ```html
800
+ <video id="myVideo" class="video-player"></video>
801
+
802
+ <script>
803
+ // Fetch signed URL from your server
804
+ fetch('/api/get-signed-url?videoId=5d5bc37f...')
805
+ .then(res => res.json())
806
+ .then(data => {
807
+ const player = new MYETVPlayer('myVideo', {
808
+ plugins: {
809
+ cloudflare: {
810
+ signedUrl: data.signedUrl // Use signed URL
811
+ }
812
+ }
813
+ });
814
+ });
815
+ </script>
816
+ ```
817
+
818
+ **Server-side (Node.js):**
819
+ ```javascript
820
+ app.get('/api/get-signed-url', (req, res) => {
821
+ const videoId = req.query.videoId;
822
+ const signedUrl = generateSignedUrl(videoId, process.env.CF_SIGNING_KEY);
823
+ res.json({ signedUrl });
824
+ });
825
+ ```
826
+
827
+ ---
828
+
829
+ ### Example 4: Branded Player
830
+
831
+ ```html
832
+ <video id="myVideo" class="video-player"></video>
833
+
834
+ <script>
835
+ const player = new MYETVPlayer('myVideo', {
836
+ plugins: {
837
+ cloudflare: {
838
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
839
+ primaryColor: '#ff6600',
840
+ poster: 'https://mybrand.com/poster.jpg',
841
+ autoplay: false,
842
+ loop: false
843
+ }
844
+ }
845
+ });
846
+ </script>
847
+ ```
848
+
849
+ ---
850
+
851
+ ### Example 5: Video with Ads (VAST)
852
+
853
+ ```html
854
+ <video id="myVideo" class="video-player"></video>
855
+
856
+ <script>
857
+ const player = new MYETVPlayer('myVideo', {
858
+ plugins: {
859
+ cloudflare: {
860
+ videoId: '5d5bc37ffcf54c9b82e996823bffbb81',
861
+ adUrl: 'https://example.com/vast-ad-tag.xml' // VAST ad URL
862
+ }
863
+ }
864
+ });
865
+ </script>
866
+ ```
867
+
868
+ ---
869
+
870
+ ### Example 6: Live Stream
871
+
872
+ ```html
873
+ <video id="myVideo" class="video-player"></video>
874
+
875
+ <div id="live-indicator" style="display: none;">
876
+ 🔴 LIVE
877
+ </div>
878
+
879
+ <script>
880
+ const player = new MYETVPlayer('myVideo', {
881
+ plugins: {
882
+ cloudflare: {
883
+ videoId: 'live-stream-id-abc123',
884
+ autoplay: true,
885
+ muted: true
886
+ }
887
+ }
888
+ });
889
+
890
+ // Show live indicator when streaming
891
+ player.addEventListener('cloudflare:ready', () => {
892
+ document.getElementById('live-indicator').style.display = 'block';
893
+ });
894
+ </script>
895
+ ```
896
+
897
+ ---
898
+
899
+ ## FAQ
900
+
901
+ ### Q: Do I need a Cloudflare account?
902
+
903
+ **A:** Yes, you need a Cloudflare Stream account to upload and host videos. Videos are stored in your Cloudflare Stream account.
904
+
905
+ ---
906
+
907
+ ### Q: How do I get a video ID?
908
+
909
+ **A:**
910
+ 1. Upload video to Cloudflare Stream
911
+ 2. In your dashboard, go to Stream
912
+ 3. Click on your video
913
+ 4. Copy the Video ID (shown in the URL or video details)
914
+
915
+ ---
916
+
917
+ ### Q: Can I embed videos on any domain?
918
+
919
+ **A:** Yes! Unlike some platforms, Cloudflare Stream works on any domain without domain restrictions.
920
+
921
+ ---
922
+
923
+ ### Q: What's the difference between videoId and signedUrl?
924
+
925
+ **A:**
926
+ - `videoId`: For public videos, just use the video ID
927
+ - `signedUrl`: For private videos, use a time-limited signed URL with a token
928
+
929
+ ---
930
+
931
+ ### Q: How do I make a video private?
932
+
933
+ **A:**
934
+ 1. In Cloudflare dashboard, go to your video
935
+ 2. Enable "Require signed URLs"
936
+ 3. Generate a signing key
937
+ 4. Use that key to create signed URLs server-side
938
+
939
+ ---
940
+
941
+ ### Q: Does this support live streaming?
942
+
943
+ **A:** Yes! Cloudflare Stream supports both VODs and live streaming. Just use your live stream ID as the `videoId`.
944
+
945
+ ---
946
+
947
+ ### Q: Can I customize the player appearance?
948
+
949
+ **A:** Yes! You can customize:
950
+ - Primary color (controls, progress bar)
951
+ - Letterbox color (background)
952
+ - Poster image
953
+ - Show/hide controls
954
+
955
+ ---
956
+
957
+ ### Q: How do I track analytics?
958
+
959
+ **A:** Cloudflare Stream provides built-in analytics in your dashboard. No additional setup needed in the player.
960
+
961
+ ---
962
+
963
+ ## Troubleshooting
964
+
965
+ ### Issue: Video not loading
966
+
967
+ **Possible causes:**
968
+ 1. Invalid video ID
969
+ 2. Video not uploaded/ready in Cloudflare
970
+ 3. Video requires signed URL but none provided
971
+ 4. Network/firewall blocking Cloudflare
972
+
973
+ **Solution:**
974
+ - Verify video ID in Cloudflare dashboard
975
+ - Check if video processing is complete
976
+ - For private videos, use signed URLs
977
+ - Check browser console for errors
978
+
979
+ ---
980
+
981
+ ### Issue: "This video requires a valid token"
982
+
983
+ **Solution:**
984
+ Your video requires a signed URL:
985
+ ```javascript
986
+ plugins: {
987
+ cloudflare: {
988
+ signedUrl: 'YOUR_SIGNED_URL' // Not just videoId
989
+ }
990
+ }
991
+ ```
992
+
993
+ ---
994
+
995
+ ### Issue: Player not responsive
996
+
997
+ **Solution:**
998
+ Ensure container has proper sizing:
999
+ ```css
1000
+ .video-player {
1001
+ width: 100%;
1002
+ height: 100%;
1003
+ max-width: 100%;
1004
+ }
1005
+ ```
1006
+
1007
+ ---
1008
+
1009
+ ### Issue: Autoplay not working
1010
+
1011
+ **Solution:**
1012
+ Browsers require muted videos for autoplay:
1013
+ ```javascript
1014
+ plugins: {
1015
+ cloudflare: {
1016
+ videoId: 'YOUR_VIDEO_ID',
1017
+ autoplay: true,
1018
+ muted: true // Required!
1019
+ }
1020
+ }
1021
+ ```
1022
+
1023
+ ---
1024
+
1025
+ ### Debug Mode
1026
+
1027
+ Enable detailed logging:
1028
+
1029
+ ```javascript
1030
+ const player = new MYETVPlayer('myVideo', {
1031
+ debug: true,
1032
+ plugins: {
1033
+ cloudflare: {
1034
+ videoId: 'YOUR_VIDEO_ID',
1035
+ debug: true
1036
+ }
1037
+ }
1038
+ });
1039
+ ```
1040
+
1041
+ Debug messages appear with `Cloudflare Stream:` prefix.
1042
+
1043
+ ---
1044
+
1045
+ ## Resources
1046
+
1047
+ - **MYETV Player**: [https://www.myetv.tv](https://www.myetv.tv)
1048
+ - **Cloudflare Stream**: [https://cloudflare.com/products/cloudflare-stream/](https://cloudflare.com/products/cloudflare-stream/)
1049
+ - **Cloudflare Stream Docs**: [https://developers.cloudflare.com/stream/](https://developers.cloudflare.com/stream/)
1050
+ - **Stream Player API**: [https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/using-the-player-api/](https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/using-the-player-api/)
1051
+ - **GitHub**: [MYETV Video Player Open Source](https://github.com/OskarCosimo/myetv-video-player-opensource)
1052
+ - **Author**: [https://oskarcosimo.com](https://oskarcosimo.com)
1053
+
1054
+ ---
1055
+
1056
+ ## License
1057
+
1058
+ MIT License - See main project for details.
1059
+
1060
+ ---
1061
+
1062
+ ## Contributing
1063
+
1064
+ Contributions are welcome! Please submit pull requests or open issues on GitHub.
1065
+
1066
+ ---
1067
+
1068
+ **Enjoy enterprise-grade video delivery!**