myetv-player 1.2.0 → 1.3.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 (47) hide show
  1. package/css/myetv-player.css +131 -0
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +547 -102
  4. package/dist/myetv-player.min.js +486 -93
  5. package/package.json +35 -17
  6. package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
  7. package/plugins/vimeo/myetv-player-vimeo.js +80 -49
  8. package/plugins/youtube/README.md +5 -2
  9. package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
  10. package/.github/workflows/codeql.yml +0 -100
  11. package/.github/workflows/npm-publish.yml +0 -30
  12. package/SECURITY.md +0 -50
  13. package/build.js +0 -195
  14. package/scss/README.md +0 -161
  15. package/scss/_audio-player.scss +0 -21
  16. package/scss/_base.scss +0 -116
  17. package/scss/_controls.scss +0 -204
  18. package/scss/_loading.scss +0 -111
  19. package/scss/_menus.scss +0 -432
  20. package/scss/_mixins.scss +0 -112
  21. package/scss/_poster.scss +0 -8
  22. package/scss/_progress-bar.scss +0 -319
  23. package/scss/_resolution.scss +0 -68
  24. package/scss/_responsive.scss +0 -1368
  25. package/scss/_themes.scss +0 -30
  26. package/scss/_title-overlay.scss +0 -60
  27. package/scss/_tooltips.scss +0 -7
  28. package/scss/_variables.scss +0 -49
  29. package/scss/_video.scss +0 -221
  30. package/scss/_volume.scss +0 -122
  31. package/scss/_watermark.scss +0 -128
  32. package/scss/myetv-player.scss +0 -51
  33. package/scss/package.json +0 -16
  34. package/src/README.md +0 -560
  35. package/src/chapters.js +0 -521
  36. package/src/controls.js +0 -1242
  37. package/src/core.js +0 -1922
  38. package/src/events.js +0 -537
  39. package/src/fullscreen.js +0 -82
  40. package/src/i18n.js +0 -374
  41. package/src/playlist.js +0 -177
  42. package/src/plugins.js +0 -384
  43. package/src/quality.js +0 -963
  44. package/src/streaming.js +0 -346
  45. package/src/subtitles.js +0 -524
  46. package/src/utils.js +0 -65
  47. package/src/watermark.js +0 -246
package/package.json CHANGED
@@ -1,8 +1,36 @@
1
1
  {
2
2
  "name": "myetv-player",
3
- "version": "1.2.0",
4
- "description": "MYETV Video Player - Modular JavaScript and SCSS Build System",
3
+ "version": "1.3.0",
4
+ "description": "MYETV Video Player - Modular HTML5 video player with plugin support for YouTube, Vimeo, Twitch, Facebook, and streaming protocols (HLS/DASH)",
5
5
  "main": "dist/myetv-player.js",
6
+ "files": [
7
+ "dist/",
8
+ "css/",
9
+ "plugins/",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "video",
15
+ "player",
16
+ "html5",
17
+ "video-player",
18
+ "media-player",
19
+ "streaming",
20
+ "hls",
21
+ "dash",
22
+ "plugin",
23
+ "youtube",
24
+ "vimeo",
25
+ "twitch",
26
+ "facebook",
27
+ "cloudflare-stream",
28
+ "subtitles",
29
+ "webvtt",
30
+ "picture-in-picture",
31
+ "responsive",
32
+ "open-source"
33
+ ],
6
34
  "scripts": {
7
35
  "build": "node build.js",
8
36
  "build:js": "node build.js",
@@ -23,20 +51,10 @@
23
51
  "repository": {
24
52
  "type": "git",
25
53
  "url": "https://github.com/OskarCosimo/myetv-video-player-opensource"
26
- }
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/OskarCosimo/myetv-video-player-opensource/issues"
57
+ },
58
+ "homepage": "https://oskarcosimo.com/myetv-video-player/myetv-player-demo.html"
27
59
  }
28
60
 
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
@@ -36,6 +36,7 @@
36
36
  this.isPausedState = true; // Track state manually
37
37
  this.extractedBrandLogo = null; // Store reference to extracted brand logo
38
38
  this.brandLogoHideTimer = null; // Timer for auto-hide
39
+ this.containerHeightObserver = null; // MutationObserver for container height
39
40
 
40
41
  this.api = player.getPluginAPI ? player.getPluginAPI() : {
41
42
  player: player,
@@ -196,16 +197,104 @@
196
197
  this.api.video.style.display = 'none';
197
198
  }
198
199
 
199
- this.twitchContainer = document.createElement('div');
200
- this.twitchContainer.id = 'twitch-player-' + Date.now();
201
- this.twitchContainer.style.cssText = `
202
- position: fixed;
203
- top: 0;
204
- left: 0;
205
- width: 100vw;
206
- height: 100vh;
207
- z-index: 1;
208
- `;
200
+ // Detect if we're inside an iframe
201
+ const isInIframe = window.self !== window.top;
202
+
203
+ // Set position: relative to container
204
+ this.api.container.style.position = 'relative';
205
+
206
+ if (isInIframe) {
207
+ // Inside iframe: use original system (no MutationObserver needed)
208
+ this.twitchContainer = document.createElement('div');
209
+ this.twitchContainer.id = `twitch-player-${Date.now()}`;
210
+
211
+ this.twitchContainer.style.cssText = `
212
+ position: absolute !important;
213
+ top: 0 !important;
214
+ left: 0 !important;
215
+ width: 100% !important;
216
+ height: 100% !important;
217
+ z-index: 1 !important;
218
+ `;
219
+
220
+ if (this.api.player.options.debug) {
221
+ console.log('🎮 Twitch Plugin: Inside iframe - using absolute positioning');
222
+ }
223
+
224
+ } else {
225
+ // Outside iframe: lock height with MutationObserver
226
+ const containerHeight = this.api.container.offsetHeight;
227
+ if (containerHeight > 0) {
228
+ this.api.container.style.height = containerHeight + 'px';
229
+ if (this.api.player.options.debug) {
230
+ console.log('🎮 Twitch Plugin: Container height locked to', containerHeight + 'px');
231
+ }
232
+ } else {
233
+ // Fallback if offsetHeight is 0
234
+ this.api.container.style.height = '500px';
235
+ if (this.api.player.options.debug) {
236
+ console.log('🎮 Twitch Plugin: Container height fallback to 500px');
237
+ }
238
+ }
239
+
240
+ // MutationObserver to protect container height from being reset
241
+ const lockedHeight = this.api.container.style.height; // Save locked height
242
+
243
+ this.containerHeightObserver = new MutationObserver((mutations) => {
244
+ mutations.forEach((mutation) => {
245
+ if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
246
+ const currentHeight = this.api.container.style.height;
247
+
248
+ // Check if we're in fullscreen
249
+ const isFullscreen = document.fullscreenElement ||
250
+ document.webkitFullscreenElement ||
251
+ document.mozFullScreenElement ||
252
+ document.msFullscreenElement;
253
+
254
+ // If in fullscreen, allow height: 100%
255
+ if (isFullscreen) {
256
+ if (currentHeight !== '100%') {
257
+ this.api.container.style.height = '100%';
258
+ if (this.api.player.options.debug) {
259
+ console.log('🎮 Twitch Plugin: Fullscreen mode - height set to 100%');
260
+ }
261
+ }
262
+ } else {
263
+ // If NOT in fullscreen and height was changed to 100% or removed, restore locked height
264
+ if (currentHeight !== lockedHeight && (currentHeight === '100%' || currentHeight === '' || currentHeight === 'auto')) {
265
+ this.api.container.style.height = lockedHeight;
266
+
267
+ if (this.api.player.options.debug) {
268
+ console.log('🎮 Twitch Plugin: Container height restored to', lockedHeight);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ });
274
+ });
275
+
276
+ // Observe changes to container's style attribute
277
+ this.containerHeightObserver.observe(this.api.container, {
278
+ attributes: true,
279
+ attributeFilter: ['style']
280
+ });
281
+
282
+ if (this.api.player.options.debug) {
283
+ console.log('🎮 Twitch Plugin: MutationObserver active on container');
284
+ }
285
+
286
+ this.twitchContainer = document.createElement('div');
287
+ this.twitchContainer.id = `twitch-player-${Date.now()}`;
288
+
289
+ this.twitchContainer.style.cssText = `
290
+ position: absolute !important;
291
+ top: 0 !important;
292
+ left: 0 !important;
293
+ width: 100% !important;
294
+ height: 100% !important;
295
+ z-index: 1 !important;
296
+ `;
297
+ }
209
298
 
210
299
  this.api.container.appendChild(this.twitchContainer);
211
300
 
@@ -231,9 +320,26 @@ z-index: 1;
231
320
  }
232
321
 
233
322
  this.twitchPlayer = new Twitch.Player(this.twitchContainer.id, playerOptions);
323
+
324
+ setTimeout(() => {
325
+ const twitchIframe = this.twitchContainer.querySelector('iframe');
326
+ if (twitchIframe) {
327
+ twitchIframe.style.cssText = `
328
+ position: absolute !important;
329
+ top: 0 !important;
330
+ left: 0 !important;
331
+ width: 100% !important;
332
+ height: 100% !important;
333
+ `;
334
+ if (this.api.player.options.debug) {
335
+ console.log('🎮 Twitch Plugin: Iframe dimensions forced to 100%');
336
+ }
337
+ }
338
+ }, 200);
339
+
234
340
  this.setupEventListeners();
235
341
 
236
- if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Player created');
342
+ if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Player created (iframe: ' + isInIframe + ')');
237
343
 
238
344
  this.api.triggerEvent('twitchplugin:playerready', {
239
345
  channel: this.options.channel,
@@ -436,6 +542,7 @@ bottom: 35px !important;
436
542
  right: 20px !important;
437
543
  z-index: 2000 !important;
438
544
  pointer-events: auto !important;
545
+ cursor: pointer !important;
439
546
  display: block !important;
440
547
  opacity: 0 !important;
441
548
  transition: opacity 0.3s ease !important;
@@ -760,6 +867,13 @@ transition: opacity 0.3s ease !important;
760
867
  this.brandLogoHideTimer = null;
761
868
  }
762
869
 
870
+ // Disconnect MutationObserver
871
+ if (this.containerHeightObserver) {
872
+ this.containerHeightObserver.disconnect();
873
+ this.containerHeightObserver = null;
874
+ if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: MutationObserver disconnected');
875
+ }
876
+
763
877
  // Remove mouse event listeners for brand logo auto-hide
764
878
  if (this.api.container._brandLogoMouseHandler) {
765
879
  this.api.container.removeEventListener('mousemove', this.api.container._brandLogoMouseHandler);
@@ -65,6 +65,8 @@
65
65
  console.error('🎬 Vimeo Plugin: videoId or videoUrl is required');
66
66
  return;
67
67
  }
68
+ // Ensure video wrapper has 100% height for proper fullscreen positioning
69
+ this.player.container.style.height = '100%';
68
70
  }
69
71
 
70
72
  /**
@@ -764,43 +766,52 @@
764
766
  /* Controlbar gradient - dark opaque at bottom, semi-transparent at top */
765
767
  /* ONLY applied when Vimeo plugin is active */
766
768
  .video-wrapper.vimeo-active .controls {
767
- background: linear-gradient(
768
- to top,
769
- rgba(0, 0, 0, ${controlBarOpacity}) 0%, /* Maximum opacity at bottom */
770
- rgba(0, 0, 0, ${controlBarOpacity * 0.89}) 20%, /* 89% of max opacity */
771
- rgba(0, 0, 0, ${controlBarOpacity * 0.74}) 40%, /* 74% */
772
- rgba(0, 0, 0, ${controlBarOpacity * 0.53}) 60%, /* 53% */
773
- rgba(0, 0, 0, ${controlBarOpacity * 0.32}) 80%, /* 32% */
774
- rgba(0, 0, 0, ${controlBarOpacity * 0.21}) 100% /* 21% at top */
769
+ background: linear-gradient(to top,
770
+ rgba(0, 0, 0, ${controlBarOpacity * 0}) 0%,
771
+ rgba(0, 0, 0, ${controlBarOpacity * 0.89}) 20%,
772
+ rgba(0, 0, 0, ${controlBarOpacity * 0.74}) 40%,
773
+ rgba(0, 0, 0, ${controlBarOpacity * 0.53}) 60%,
774
+ rgba(0, 0, 0, ${controlBarOpacity * 0.32}) 80%,
775
+ rgba(0, 0, 0, ${controlBarOpacity * 0.21}) 100%
775
776
  ) !important;
776
777
  backdrop-filter: blur(3px);
777
778
  min-height: 60px;
778
779
  padding-bottom: 10px;
779
780
  }
780
-
781
+
782
+ /* FIX: Ensure controlbar stays at bottom in fullscreen */
783
+ .video-wrapper.vimeo-active:-webkit-full-screen .controls,
784
+ .video-wrapper.vimeo-active:-moz-full-screen .controls,
785
+ .video-wrapper.vimeo-active:fullscreen .controls {
786
+ position: fixed !important;
787
+ bottom: 0 !important;
788
+ left: 0 !important;
789
+ right: 0 !important;
790
+ z-index: 999999 !important;
791
+ }
792
+
781
793
  /* Title overlay gradient - dark opaque at top, semi-transparent at bottom */
782
794
  /* ONLY applied when Vimeo plugin is active */
783
795
  .video-wrapper.vimeo-active .title-overlay {
784
- background: linear-gradient(
785
- to bottom,
786
- rgba(0, 0, 0, ${titleOverlayOpacity}) 0%, /* Maximum opacity at top */
787
- rgba(0, 0, 0, ${titleOverlayOpacity * 0.89}) 20%, /* 89% of max opacity */
788
- rgba(0, 0, 0, ${titleOverlayOpacity * 0.74}) 40%, /* 74% */
789
- rgba(0, 0, 0, ${titleOverlayOpacity * 0.53}) 60%, /* 53% */
790
- rgba(0, 0, 0, ${titleOverlayOpacity * 0.32}) 80%, /* 32% */
791
- rgba(0, 0, 0, ${titleOverlayOpacity * 0.21}) 100% /* 21% at bottom */
796
+ background: linear-gradient(to bottom,
797
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0}) 0%,
798
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.89}) 20%,
799
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.74}) 40%,
800
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.53}) 60%,
801
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.32}) 80%,
802
+ rgba(0, 0, 0, ${titleOverlayOpacity * 0.21}) 100%
792
803
  ) !important;
793
804
  backdrop-filter: blur(3px);
794
805
  min-height: 80px;
795
806
  padding-top: 20px;
796
807
  }
797
-
808
+
798
809
  /* Keep controlbar visible when video is paused */
799
810
  .video-wrapper.vimeo-active.video-paused .controls.show {
800
811
  opacity: 1 !important;
801
812
  visibility: visible !important;
802
813
  }
803
-
814
+
804
815
  /* Keep title overlay visible when video is paused */
805
816
  .video-wrapper.vimeo-active.video-paused .title-overlay.show {
806
817
  opacity: 1 !important;
@@ -945,26 +956,40 @@
945
956
  console.log('🎬 Vimeo Plugin: Showing Vimeo native controls');
946
957
  }
947
958
 
948
- const iframe = this.player.container.querySelector('iframe');
949
959
  const controlbar = this.player.container.querySelector('.controls');
950
960
  const titleOverlay = this.player.container.querySelector('.title-overlay');
961
+ const mouseMoveOverlay = this.player.container.querySelector('.vimeo-mousemove-overlay');
962
+ const vimeoContainer = this.player.container.querySelector('.vimeo-player-container');
951
963
 
952
- if (iframe) {
953
- // Bring iframe to front
954
- iframe.style.pointerEvents = 'auto';
955
- iframe.style.zIndex = '9999';
964
+ // Remove inline styles from mousemove overlay
965
+ if (mouseMoveOverlay) {
966
+ this.savedMouseMoveStyle = mouseMoveOverlay.getAttribute('style');
967
+ mouseMoveOverlay.removeAttribute('style');
968
+ }
956
969
 
957
- // Hide controlbar and title
958
- if (controlbar) controlbar.style.display = 'none';
959
- if (titleOverlay) titleOverlay.style.display = 'none';
970
+ // Remove "vimeo-container-no-controls" class from vimeo container
971
+ if (vimeoContainer) {
972
+ vimeoContainer.classList.remove('vimeo-container-no-controls');
973
+ }
960
974
 
961
- // Auto-restore after 10 seconds
962
- this.vimeoControlsTimeout = setTimeout(() => {
963
- if (this.options.debug) {
964
- console.log('🎬 Vimeo Plugin: Restoring custom controls after 10 seconds');
965
- }
966
- this.hideVimeoControls();
967
- }, 10000);
975
+ // Hide MYETV controls
976
+ if (controlbar) {
977
+ controlbar.style.display = 'none';
978
+ }
979
+ if (titleOverlay) {
980
+ titleOverlay.style.display = 'none';
981
+ }
982
+
983
+ // Auto-restore after 10 seconds
984
+ this.vimeoControlsTimeout = setTimeout(() => {
985
+ if (this.options.debug) {
986
+ console.log('🎬 Vimeo Plugin: Restoring custom controls after 10 seconds');
987
+ }
988
+ this.hideVimeoControls();
989
+ }, 10000);
990
+
991
+ if (this.options.debug) {
992
+ console.log('🎬 Vimeo Plugin: Vimeo native controls enabled');
968
993
  }
969
994
  }
970
995
 
@@ -974,31 +999,37 @@
974
999
  console.log('🎬 Vimeo Plugin: Hiding Vimeo native controls');
975
1000
  }
976
1001
 
977
- // Clear timeout if exists
978
1002
  if (this.vimeoControlsTimeout) {
979
1003
  clearTimeout(this.vimeoControlsTimeout);
980
1004
  this.vimeoControlsTimeout = null;
981
1005
  }
982
1006
 
983
- const iframe = this.player.container.querySelector('iframe');
984
1007
  const controlbar = this.player.container.querySelector('.controls');
985
1008
  const titleOverlay = this.player.container.querySelector('.title-overlay');
1009
+ const mouseMoveOverlay = this.player.container.querySelector('.vimeo-mousemove-overlay');
1010
+ const vimeoContainer = this.player.container.querySelector('.vimeo-player-container');
986
1011
 
987
- if (iframe) {
988
- // Disable clicks on Vimeo controls
989
- iframe.style.pointerEvents = 'none';
990
- iframe.style.zIndex = '1';
1012
+ // Restore inline styles to mousemove overlay
1013
+ if (mouseMoveOverlay && this.savedMouseMoveStyle) {
1014
+ mouseMoveOverlay.setAttribute('style', this.savedMouseMoveStyle);
1015
+ this.savedMouseMoveStyle = null;
1016
+ }
991
1017
 
992
- // Restore controlbar and title
993
- if (controlbar) {
994
- controlbar.style.display = '';
995
- controlbar.style.zIndex = '10';
996
- }
997
- if (titleOverlay) titleOverlay.style.display = '';
1018
+ // Add back "vimeo-container-no-controls" class
1019
+ if (vimeoContainer) {
1020
+ vimeoContainer.classList.add('vimeo-container-no-controls');
1021
+ }
998
1022
 
999
- if (this.options.debug) {
1000
- console.log('🎬 Vimeo Plugin: Custom controls restored');
1001
- }
1023
+ // Show MYETV controls
1024
+ if (controlbar) {
1025
+ controlbar.style.display = '';
1026
+ }
1027
+ if (titleOverlay) {
1028
+ titleOverlay.style.display = '';
1029
+ }
1030
+
1031
+ if (this.options.debug) {
1032
+ console.log('🎬 Vimeo Plugin: Custom controls restored');
1002
1033
  }
1003
1034
  }
1004
1035
 
@@ -101,7 +101,7 @@ const player = new MYETVPlayer('myVideo', {
101
101
  // YouTube video ID (required if not using auto-detection)
102
102
  videoId: 'dQw4w9WgXcQ',
103
103
 
104
- // YouTube Data API key (optional, for future features)
104
+ // YouTube Data API key (optional, for improved features)
105
105
  apiKey: null,
106
106
 
107
107
  // Auto-play video on load
@@ -113,6 +113,9 @@ const player = new MYETVPlayer('myVideo', {
113
113
  // Show the button on the controlbar to enable the YouTube native controls temporarily
114
114
  showNativeControlsButton: true,
115
115
 
116
+ // Extract and show the Youtube chapters (API Key is required)
117
+ showYoutubeChapters : true,
118
+
116
119
  controlBarOpacity: 0.95, // The controlbar opacity or semi-transparency or transparency - values: from 0 to 1
117
120
 
118
121
  titleOverlayOpacity: 0.95, // The overlay title opacity or semi-transparency or transparency - values: from 0 to 1
@@ -129,7 +132,7 @@ const player = new MYETVPlayer('myVideo', {
129
132
  // Enable quality control in player UI
130
133
  enableQualityControl: true,
131
134
 
132
- // enable channel information retrieval (NEED THE APIKEY TO WORK)
135
+ // enable channel information retrieval (API key is required)
133
136
  enableChannelWatermark: false,
134
137
 
135
138
  // Set the default language for auto subtitles