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,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!**
|