myetv-player 1.0.0

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 (50) hide show
  1. package/.github/workflows/npm-publish.yml +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +866 -0
  4. package/build.js +189 -0
  5. package/css/README.md +1 -0
  6. package/css/myetv-player.css +13702 -0
  7. package/css/myetv-player.min.css +1 -0
  8. package/dist/README.md +1 -0
  9. package/dist/myetv-player.js +6408 -0
  10. package/dist/myetv-player.min.js +6183 -0
  11. package/package.json +27 -0
  12. package/plugins/README.md +1 -0
  13. package/plugins/google-analytics/README.md +1 -0
  14. package/plugins/google-analytics/myetv-player-g-analytics-plugin.js +548 -0
  15. package/plugins/youtube/README.md +1 -0
  16. package/plugins/youtube/myetv-player-youtube-plugin.js +418 -0
  17. package/scss/README.md +1 -0
  18. package/scss/_audio-player.scss +21 -0
  19. package/scss/_base.scss +131 -0
  20. package/scss/_controls.scss +30 -0
  21. package/scss/_loading.scss +111 -0
  22. package/scss/_menus.scss +4070 -0
  23. package/scss/_mixins.scss +112 -0
  24. package/scss/_poster.scss +8 -0
  25. package/scss/_progress-bar.scss +2203 -0
  26. package/scss/_resolution.scss +68 -0
  27. package/scss/_responsive.scss +1532 -0
  28. package/scss/_themes.scss +30 -0
  29. package/scss/_title-overlay.scss +2262 -0
  30. package/scss/_tooltips.scss +7 -0
  31. package/scss/_variables.scss +49 -0
  32. package/scss/_video.scss +2401 -0
  33. package/scss/_volume.scss +1981 -0
  34. package/scss/_watermark.scss +8 -0
  35. package/scss/myetv-player.scss +51 -0
  36. package/scss/package.json +16 -0
  37. package/src/README.md +1 -0
  38. package/src/chapters.js +521 -0
  39. package/src/controls.js +1005 -0
  40. package/src/core.js +1650 -0
  41. package/src/events.js +330 -0
  42. package/src/fullscreen.js +82 -0
  43. package/src/i18n.js +348 -0
  44. package/src/playlist.js +177 -0
  45. package/src/plugins.js +384 -0
  46. package/src/quality.js +921 -0
  47. package/src/streaming.js +346 -0
  48. package/src/subtitles.js +426 -0
  49. package/src/utils.js +51 -0
  50. package/src/watermark.js +195 -0
@@ -0,0 +1,418 @@
1
+ /**
2
+ * MYETV Player - YouTube Plugin (Enhanced)
3
+ * File: myetv-player-youtube-plugin.js
4
+ */
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ class YouTubePlugin {
10
+ constructor(player, options = {}) {
11
+ this.player = player;
12
+ this.options = {
13
+ videoId: options.videoId || null, // Direct video ID
14
+ apiKey: options.apiKey || null,
15
+ autoplay: options.autoplay !== undefined ? options.autoplay : false,
16
+ showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
17
+ autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
18
+ quality: options.quality || 'default', // 'default', 'hd720', 'hd1080'
19
+ ...options
20
+ };
21
+
22
+ this.ytPlayer = null;
23
+ this.isYouTubeReady = false;
24
+ this.videoId = this.options.videoId;
25
+
26
+ // Get plugin API
27
+ this.api = player.getPluginAPI();
28
+ }
29
+
30
+ /**
31
+ * Setup plugin (called automatically after instantiation)
32
+ */
33
+ setup() {
34
+ this.api.debug('YouTube plugin setup started');
35
+
36
+ // Load YouTube IFrame API
37
+ this.loadYouTubeAPI();
38
+
39
+ // Add custom methods to player
40
+ this.addPlayerMethods();
41
+
42
+ // Register hooks
43
+ this.registerHooks();
44
+
45
+ // Auto-load video ID from various sources
46
+ if (this.options.autoLoadFromData) {
47
+ this.autoDetectVideoId();
48
+ }
49
+
50
+ // If video ID is provided in options, load it immediately
51
+ if (this.videoId) {
52
+ this.waitForAPIThenLoad();
53
+ }
54
+
55
+ this.api.debug('YouTube plugin setup completed');
56
+ }
57
+
58
+ /**
59
+ * Add custom methods to player instance
60
+ */
61
+ addPlayerMethods() {
62
+ // Load YouTube video by ID
63
+ this.player.loadYouTubeVideo = (videoId, options = {}) => {
64
+ return this.loadVideo(videoId, options);
65
+ };
66
+
67
+ // Get current YouTube video ID
68
+ this.player.getYouTubeVideoId = () => {
69
+ return this.videoId;
70
+ };
71
+
72
+ // Check if YouTube is active
73
+ this.player.isYouTubeActive = () => {
74
+ return this.ytPlayer !== null;
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Register plugin hooks
80
+ */
81
+ registerHooks() {
82
+ // Check for YouTube URL before play
83
+ this.api.registerHook('beforePlay', (data) => {
84
+ this.checkForYouTubeUrl();
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Auto-detect video ID from various sources
90
+ */
91
+ autoDetectVideoId() {
92
+ // Priority 1: Check data-video-id attribute
93
+ const dataVideoId = this.api.video.getAttribute('data-video-id');
94
+ const dataVideoType = this.api.video.getAttribute('data-video-type');
95
+
96
+ if (dataVideoId && dataVideoType === 'youtube') {
97
+ this.videoId = dataVideoId;
98
+ this.api.debug('YouTube video ID detected from data-video-id: ' + this.videoId);
99
+ return;
100
+ }
101
+
102
+ // Priority 2: Check video source URL
103
+ const src = this.api.video.src || this.api.video.currentSrc;
104
+ if (src) {
105
+ const extractedId = this.extractYouTubeVideoId(src);
106
+ if (extractedId) {
107
+ this.videoId = extractedId;
108
+ this.api.debug('YouTube video ID detected from src: ' + this.videoId);
109
+ return;
110
+ }
111
+ }
112
+
113
+ // Priority 3: Check source elements
114
+ const sources = this.api.video.querySelectorAll('source');
115
+ for (const source of sources) {
116
+ const sourceSrc = source.getAttribute('src');
117
+ const sourceType = source.getAttribute('type');
118
+
119
+ if (sourceType === 'video/youtube' || sourceType === 'video/x-youtube') {
120
+ const extractedId = this.extractYouTubeVideoId(sourceSrc);
121
+ if (extractedId) {
122
+ this.videoId = extractedId;
123
+ this.api.debug('YouTube video ID detected from source element: ' + this.videoId);
124
+ return;
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Wait for API to be ready then load video
132
+ */
133
+ waitForAPIThenLoad() {
134
+ if (this.isYouTubeReady) {
135
+ this.loadVideo(this.videoId);
136
+ } else {
137
+ // Wait for API to be ready
138
+ const checkInterval = setInterval(() => {
139
+ if (this.isYouTubeReady) {
140
+ clearInterval(checkInterval);
141
+ this.loadVideo(this.videoId);
142
+ }
143
+ }, 100);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Load YouTube IFrame API
149
+ */
150
+ loadYouTubeAPI() {
151
+ if (window.YT && window.YT.Player) {
152
+ this.isYouTubeReady = true;
153
+ this.api.debug('YouTube API already loaded');
154
+ return;
155
+ }
156
+
157
+ // Load YouTube IFrame API script
158
+ const tag = document.createElement('script');
159
+ tag.src = 'https://www.youtube.com/iframe_api';
160
+ const firstScriptTag = document.getElementsByTagName('script')[0];
161
+ firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
162
+
163
+ // Set callback for when API is ready
164
+ window.onYouTubeIframeAPIReady = () => {
165
+ this.isYouTubeReady = true;
166
+ this.api.debug('YouTube API loaded and ready');
167
+
168
+ // Trigger custom event
169
+ this.api.triggerEvent('youtubeplugin:ready', {});
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Check if current video source is a YouTube URL
175
+ */
176
+ checkForYouTubeUrl() {
177
+ const src = this.api.video.src || this.api.video.currentSrc;
178
+ const videoId = this.extractYouTubeVideoId(src);
179
+
180
+ if (videoId && videoId !== this.videoId) {
181
+ this.loadVideo(videoId);
182
+ return true;
183
+ }
184
+
185
+ return false;
186
+ }
187
+
188
+ /**
189
+ * Extract YouTube video ID from URL
190
+ * Supports various YouTube URL formats
191
+ * @param {String} url - YouTube URL or video ID
192
+ * @returns {String|null} Video ID or null
193
+ */
194
+ extractYouTubeVideoId(url) {
195
+ if (!url) return null;
196
+
197
+ // Remove whitespace
198
+ url = url.trim();
199
+
200
+ const patterns = [
201
+ // Standard watch URL
202
+ /(?:youtube\.com\/watch\?v=)([^&\n?#]+)/,
203
+ // Shortened youtu.be URL
204
+ /(?:youtu\.be\/)([^&\n?#]+)/,
205
+ // Embed URL
206
+ /(?:youtube\.com\/embed\/)([^&\n?#]+)/,
207
+ // Direct video ID (11 characters)
208
+ /^([a-zA-Z0-9_-]{11})$/
209
+ ];
210
+
211
+ for (const pattern of patterns) {
212
+ const match = url.match(pattern);
213
+ if (match && match[1]) {
214
+ return match[1];
215
+ }
216
+ }
217
+
218
+ return null;
219
+ }
220
+
221
+ /**
222
+ * Load YouTube video by ID
223
+ * @param {String} videoId - YouTube video ID
224
+ * @param {Object} options - Load options
225
+ */
226
+ loadVideo(videoId, options = {}) {
227
+ if (!videoId) {
228
+ this.api.debug('No video ID provided to loadVideo()');
229
+ return;
230
+ }
231
+
232
+ if (!this.isYouTubeReady) {
233
+ this.api.debug('YouTube API not ready yet, waiting...');
234
+ setTimeout(() => this.loadVideo(videoId, options), 100);
235
+ return;
236
+ }
237
+
238
+ this.videoId = videoId;
239
+
240
+ // Hide native video element
241
+ this.api.video.style.display = 'none';
242
+
243
+ // Create YouTube player container if not exists
244
+ if (!this.ytPlayerContainer) {
245
+ this.ytPlayerContainer = document.createElement('div');
246
+ this.ytPlayerContainer.id = 'yt-player-' + Date.now();
247
+ this.ytPlayerContainer.className = 'yt-player-container';
248
+
249
+ // Style to match video element
250
+ this.ytPlayerContainer.style.position = 'absolute';
251
+ this.ytPlayerContainer.style.top = '0';
252
+ this.ytPlayerContainer.style.left = '0';
253
+ this.ytPlayerContainer.style.width = '100%';
254
+ this.ytPlayerContainer.style.height = '100%';
255
+
256
+ this.api.container.insertBefore(this.ytPlayerContainer, this.api.controls);
257
+ }
258
+
259
+ // Destroy existing player if present
260
+ if (this.ytPlayer) {
261
+ this.ytPlayer.destroy();
262
+ }
263
+
264
+ // Merge options
265
+ const playerVars = {
266
+ autoplay: this.options.autoplay ? 1 : 0,
267
+ controls: this.options.showYouTubeUI ? 1 : 0,
268
+ modestbranding: 1,
269
+ rel: 0,
270
+ ...options.playerVars
271
+ };
272
+
273
+ // Initialize YouTube player
274
+ this.ytPlayer = new YT.Player(this.ytPlayerContainer.id, {
275
+ videoId: videoId,
276
+ playerVars: playerVars,
277
+ events: {
278
+ 'onReady': (event) => this.onPlayerReady(event),
279
+ 'onStateChange': (event) => this.onPlayerStateChange(event),
280
+ 'onError': (event) => this.onPlayerError(event)
281
+ }
282
+ });
283
+
284
+ this.api.debug('YouTube video loaded: ' + videoId);
285
+ this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
286
+ }
287
+
288
+ /**
289
+ * YouTube player ready callback
290
+ */
291
+ onPlayerReady(event) {
292
+ this.api.debug('YouTube player ready');
293
+
294
+ // Sync controls with YouTube player
295
+ this.syncControls();
296
+
297
+ // Set quality if specified
298
+ if (this.options.quality && this.options.quality !== 'default') {
299
+ this.ytPlayer.setPlaybackQuality(this.options.quality);
300
+ }
301
+
302
+ this.api.triggerEvent('youtubeplugin:playerready', {});
303
+ }
304
+
305
+ /**
306
+ * YouTube player state change callback
307
+ */
308
+ onPlayerStateChange(event) {
309
+ switch (event.data) {
310
+ case YT.PlayerState.PLAYING:
311
+ this.api.triggerEvent('played', {});
312
+ break;
313
+ case YT.PlayerState.PAUSED:
314
+ this.api.triggerEvent('paused', {});
315
+ break;
316
+ case YT.PlayerState.ENDED:
317
+ this.api.triggerEvent('ended', {});
318
+ break;
319
+ case YT.PlayerState.BUFFERING:
320
+ this.api.debug('YouTube player buffering');
321
+ break;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * YouTube player error callback
327
+ */
328
+ onPlayerError(event) {
329
+ const errorMessages = {
330
+ 2: 'Invalid video ID',
331
+ 5: 'HTML5 player error',
332
+ 100: 'Video not found or private',
333
+ 101: 'Video not allowed to be played in embedded players',
334
+ 150: 'Video not allowed to be played in embedded players'
335
+ };
336
+
337
+ const errorMsg = errorMessages[event.data] || 'Unknown error';
338
+ this.api.debug('YouTube player error: ' + errorMsg);
339
+
340
+ this.api.triggerEvent('youtubeplugin:error', {
341
+ errorCode: event.data,
342
+ errorMessage: errorMsg
343
+ });
344
+ }
345
+
346
+ /**
347
+ * Sync player controls with YouTube player
348
+ */
349
+ syncControls() {
350
+ // Override play/pause methods
351
+ const originalPlay = this.player.play;
352
+ const originalPause = this.player.pause;
353
+
354
+ this.player.play = () => {
355
+ if (this.ytPlayer && this.ytPlayer.playVideo) {
356
+ this.ytPlayer.playVideo();
357
+ } else {
358
+ originalPlay.call(this.player);
359
+ }
360
+ };
361
+
362
+ this.player.pause = () => {
363
+ if (this.ytPlayer && this.ytPlayer.pauseVideo) {
364
+ this.ytPlayer.pauseVideo();
365
+ } else {
366
+ originalPause.call(this.player);
367
+ }
368
+ };
369
+
370
+ // Sync time updates
371
+ if (this.ytPlayer) {
372
+ setInterval(() => {
373
+ if (this.ytPlayer && this.ytPlayer.getCurrentTime) {
374
+ const currentTime = this.ytPlayer.getCurrentTime();
375
+ const duration = this.ytPlayer.getDuration();
376
+
377
+ // Update progress bar if available
378
+ if (this.api.player.progressFilled && duration) {
379
+ const progress = (currentTime / duration) * 100;
380
+ this.api.player.progressFilled.style.width = progress + '%';
381
+ if (this.api.player.progressHandle) {
382
+ this.api.player.progressHandle.style.left = progress + '%';
383
+ }
384
+ }
385
+ }
386
+ }, 250);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Dispose plugin
392
+ */
393
+ dispose() {
394
+ this.api.debug('YouTube plugin disposed');
395
+
396
+ // Destroy YouTube player
397
+ if (this.ytPlayer && this.ytPlayer.destroy) {
398
+ this.ytPlayer.destroy();
399
+ this.ytPlayer = null;
400
+ }
401
+
402
+ // Remove container
403
+ if (this.ytPlayerContainer) {
404
+ this.ytPlayerContainer.remove();
405
+ this.ytPlayerContainer = null;
406
+ }
407
+
408
+ // Show native video element again
409
+ if (this.api.video) {
410
+ this.api.video.style.display = '';
411
+ }
412
+ }
413
+ }
414
+
415
+ // Register plugin globally
416
+ window.registerMYETVPlugin('youtube', YouTubePlugin);
417
+
418
+ })();
package/scss/README.md ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,21 @@
1
+ .audio-player {
2
+ width: 320px;
3
+ height: 80px;
4
+
5
+ video {
6
+ display: none !important;
7
+ }
8
+
9
+ .controls-wrapper {
10
+ height: 60px;
11
+ }
12
+
13
+ .audio-wave-canvas {
14
+ display: block;
15
+ width: 100%;
16
+ height: 60px;
17
+ background-color: #222;
18
+ border-radius: 4px;
19
+ margin-top: 5px;
20
+ }
21
+ }
@@ -0,0 +1,131 @@
1
+ // ===================================
2
+ // BASE STYLES
3
+ // ===================================
4
+
5
+ :root {
6
+ /* Main theme colors */
7
+ --player-primary-color: goldenrod;
8
+ --player-primary-hover: #daa520;
9
+ --player-primary-dark: #b8860b;
10
+ /* Control colors */
11
+ --player-button-color: white;
12
+ --player-button-hover: rgba(255, 255, 255, 0.1);
13
+ --player-button-active: rgba(255, 255, 255, 0.2);
14
+ /* Text colors */
15
+ --player-text-color: white;
16
+ --player-text-secondary: rgba(255, 255, 255, 0.8);
17
+ /* Background and overlay */
18
+ --player-bg-primary: #000;
19
+ --player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.8) 100%);
20
+ --player-bg-title-overlay: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%);
21
+ --player-bg-menu: rgba(20, 20, 20, 0.95);
22
+ --player-bg-loading: rgba(0, 0, 0, 0.7);
23
+ /* Dimensions */
24
+ --player-border-radius: 12px;
25
+ --player-controls-padding: 20px 15px 15px;
26
+ --player-title-overlay-padding: 15px 20px 25px;
27
+ --player-button-padding: 8px;
28
+ --player-icon-size: 20px;
29
+ /* Progress and Volume bars */
30
+ --player-progress-height: 6px;
31
+ --player-progress-bg: rgba(255, 255, 255, 0.2);
32
+ --player-progress-buffer: rgba(255, 255, 255, 0.3);
33
+ --player-progress-handle-size: 16px;
34
+ --player-volume-height: 4px;
35
+ --player-volume-bg: rgba(255, 255, 255, 0.2);
36
+ --player-volume-handle-size: 14px;
37
+ --player-volume-fill: 100%;
38
+ /* Transitions */
39
+ --player-transition-fast: 0.2s ease;
40
+ --player-transition-normal: 0.3s ease;
41
+ /* Shadows */
42
+ --player-shadow-main: 0 8px 32px rgba(0, 0, 0, 0.3);
43
+ --player-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.2);
44
+ --player-shadow-tooltip: 0 3px 12px rgba(0, 0, 0, 0.4);
45
+ }
46
+
47
+
48
+ * {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ body {
53
+ margin: 0;
54
+ padding: 20px;
55
+ background: #1a1a1a;
56
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
57
+ }
58
+
59
+ .video-container {
60
+ max-width: 1200px;
61
+ margin: 0 auto;
62
+ background: var(--player-bg-primary);
63
+ border-radius: var(--player-border-radius);
64
+ box-shadow: var(--player-shadow-main);
65
+ position: relative;
66
+ }
67
+
68
+ .video-container:fullscreen,
69
+ .video-container:-webkit-full-screen,
70
+ .video-container:-moz-full-screen {
71
+ width: 100vw;
72
+ height: 100vh;
73
+ border-radius: 0;
74
+ }
75
+
76
+ .video-wrapper {
77
+ position: relative;
78
+ width: 100%;
79
+ background: var(--player-bg-primary);
80
+ overflow: visible !important;
81
+ }
82
+
83
+ .video-wrapper.player-initialized .video-player {
84
+ visibility: visible;
85
+ opacity: 1;
86
+ transition: opacity 0.3s ease;
87
+ pointer-events: auto;
88
+ }
89
+
90
+ .video-wrapper.player-initialized .initial-loading {
91
+ opacity: 0;
92
+ visibility: hidden;
93
+ transition: opacity 0.3s ease, visibility 0.3s ease;
94
+ transition-delay: 0.2s;
95
+ display: none;
96
+ transition-delay: 0.5s;
97
+ }
98
+
99
+ .video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide {
100
+ visibility: hidden;
101
+ opacity: 0;
102
+ }
103
+
104
+ .video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
105
+ .video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
106
+ bottom: 15px;
107
+ }
108
+
109
+ .video-wrapper.has-controls .video-watermark {
110
+ visibility: visible;
111
+ opacity: 0.7;
112
+ }
113
+
114
+ .video-wrapper.has-controls .video-watermark.watermark-bottomleft,
115
+ .video-wrapper.has-controls .video-watermark.watermark-bottomright {
116
+ bottom: 90px;
117
+ }
118
+
119
+ .hidden {
120
+ display: none !important;
121
+ }
122
+
123
+
124
+ /* DARK MODE THEME */
125
+
126
+ .player-theme-dark {
127
+ --player-bg-primary: #1a1a1a;
128
+ --player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.9) 100%);
129
+ --player-bg-title-overlay: linear-gradient(180deg, rgba(30, 30, 30, 0.9) 0%, transparent 100%);
130
+ --player-bg-menu: rgba(30, 30, 30, 0.95);
131
+ }
@@ -0,0 +1,30 @@
1
+ // ===================================
2
+ // CONTROLS
3
+ // ===================================
4
+
5
+ @use 'mixins' as *;
6
+ @use 'variables' as *;
7
+
8
+ /* CONTROLS - IMPROVED RESPONSIVE DESIGN */
9
+ .controls {
10
+ position: absolute;
11
+ bottom: 0;
12
+ left: 0;
13
+ right: 0;
14
+ background: var(--player-bg-controls);
15
+ padding: var(--player-controls-padding);
16
+ opacity: 0;
17
+ transform: translateY(100%);
18
+ transition: all var(--player-transition-normal);
19
+ z-index: 10;
20
+ min-height: 70px !important;
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ .controls.show {
25
+ opacity: 1;
26
+ transform: translateY(0);
27
+ position: absolute !important;
28
+ bottom: 0 !important;
29
+ z-index: 20 !important;
30
+ }