myetv-player 1.1.3 → 1.1.5

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.
@@ -165,3 +165,24 @@
165
165
  .controls-right .brand-logo-link .brand-logo {
166
166
  margin-right: 0;
167
167
  }
168
+
169
+ /* Hide cursor when controlbar is hidden */
170
+ .video-wrapper.hide-cursor {
171
+ cursor: none !important;
172
+ }
173
+
174
+ /* Ensure cursor is visible on controls even when hide-cursor is active */
175
+ .video-wrapper.hide-cursor .controls {
176
+ cursor: default !important;
177
+ }
178
+
179
+ /* Ensure cursor is visible on buttons */
180
+ .video-wrapper.hide-cursor .control-btn {
181
+ cursor: pointer !important;
182
+ }
183
+
184
+ /* do not hide mouse on iframes */
185
+ .video-wrapper.hide-cursor iframe {
186
+ cursor: auto !important;
187
+ pointer-events: auto !important;
188
+ }
package/scss/_menus.scss CHANGED
@@ -381,3 +381,52 @@
381
381
  display: none !important;
382
382
  }
383
383
  }
384
+ // Expandable settings menu styles
385
+ .settings-expandable-wrapper {
386
+ position: relative;
387
+ display: block;
388
+ }
389
+
390
+ .settings-option.expandable-trigger {
391
+ display: flex;
392
+ justify-content: space-between;
393
+ align-items: center;
394
+ cursor: pointer;
395
+ font-size: 10px !important;
396
+
397
+ .settings-option-label {
398
+ font-size: 10px !important;
399
+ }
400
+
401
+ .expand-arrow {
402
+ font-size: 8px;
403
+ transition: transform 0.2s ease;
404
+ margin-left: 8px;
405
+ }
406
+ }
407
+
408
+ .settings-expandable-content {
409
+ padding-left: 15px;
410
+ margin-top: 4px;
411
+
412
+ .settings-suboption {
413
+ padding: 6px 12px;
414
+ cursor: pointer;
415
+ color: white;
416
+ font-size: 10px;
417
+ white-space: normal;
418
+ word-wrap: break-word;
419
+ opacity: 0.8;
420
+ transition: opacity 0.2s;
421
+
422
+ &.active {
423
+ opacity: 1;
424
+ font-weight: bold;
425
+ }
426
+
427
+ &:hover {
428
+ opacity: 1;
429
+ background: rgba(255, 255, 255, 0.1);
430
+ }
431
+ }
432
+ }
package/src/controls.js CHANGED
@@ -85,6 +85,7 @@ initAutoHide() {
85
85
 
86
86
  onMouseMoveInPlayer(e) {
87
87
  this.showControlsNow();
88
+ this.showCursor();
88
89
  this.resetAutoHideTimer();
89
90
  }
90
91
 
@@ -142,66 +143,69 @@ resetAutoHideTimer() {
142
143
  showControlsNow() {
143
144
  if (this.controls) {
144
145
  this.controls.classList.add('show');
145
- }
146
146
 
147
- // Add has-controls class to container for watermark visibility
148
- if (this.container) {
149
- this.container.classList.add('has-controls');
147
+ // Add has-controls class to container (for watermark visibility)
148
+ if (this.container) {
149
+ this.container.classList.add('has-controls');
150
+ }
151
+
150
152
  this.updateControlbarHeight();
153
+
151
154
  // Update watermark position
152
155
  if (this.updateWatermarkPosition) {
153
156
  this.updateWatermarkPosition();
154
157
  }
155
- }
156
158
 
157
- // Show title overlay with controls if not persistent
158
- if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
159
- this.showTitleOverlay();
160
- }
159
+ // Show title overlay with controls (if not persistent)
160
+ if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
161
+ this.showTitleOverlay();
162
+ }
161
163
 
162
- if (this.autoHideDebug && this.options.debug) console.log('✅ Controls shown');
164
+ // *show cursor when controls are shown*
165
+ this.showCursor();
166
+
167
+ if (this.autoHideDebug && this.options.debug) console.log('✅ Controls shown');
168
+ }
163
169
  }
164
170
 
165
171
  hideControlsNow() {
166
- // Don't hide if mouse is still over controls (allow hiding on touch devices)
172
+ // Dont hide if mouse is still over controls (allow hiding on touch devices)
167
173
  const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
168
-
169
174
  if (this.mouseOverControls && !isTouchDevice) {
170
- if (this.autoHideDebug && this.options.debug) {
171
- console.log('🚫 Not hiding - mouse still over controls');
172
- }
175
+ if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - mouse still over controls');
173
176
  return;
174
177
  }
175
178
 
176
- // Don't hide if video is paused
179
+ // Dont hide if video is paused
177
180
  if (this.video && this.video.paused) {
178
- if (this.autoHideDebug && this.options.debug) {
179
- console.log('🚫 Not hiding - video is paused');
180
- }
181
+ if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - video is paused');
181
182
  return;
182
183
  }
183
184
 
184
185
  if (this.controls) {
185
186
  this.controls.classList.remove('show');
186
187
 
187
- // Remove has-controls class from container for watermark visibility
188
+ // Remove has-controls class from container (for watermark visibility)
188
189
  if (this.container) {
189
190
  this.container.classList.remove('has-controls');
190
- this.updateControlbarHeight();
191
- // Update watermark position
192
- if (this.updateWatermarkPosition) {
193
- this.updateWatermarkPosition();
194
- }
195
191
  }
196
- }
197
192
 
198
- // Hide title overlay with controls (if not persistent)
199
- if (this.options.showTitleOverlay && !this.options.persistentTitle) {
200
- this.hideTitleOverlay();
201
- }
193
+ this.updateControlbarHeight();
202
194
 
203
- if (this.autoHideDebug && this.options.debug) {
204
- console.log('👁️ Controls hidden');
195
+ // Update watermark position
196
+ if (this.updateWatermarkPosition) {
197
+ this.updateWatermarkPosition();
198
+ }
199
+
200
+ // Hide title overlay with controls (if not persistent)
201
+ if (this.options.showTitleOverlay && !this.options.persistentTitle) {
202
+ this.hideTitleOverlay();
203
+ }
204
+
205
+ // *hide cursor after controls are hidden*
206
+ this.hideCursor();
207
+
208
+ if (this.autoHideDebug && this.options.debug) console.log('✅ Controls hidden');
205
209
  }
206
210
  }
207
211
 
@@ -600,7 +604,9 @@ updateSettingsMenuVisibility() {
600
604
  }
601
605
  }
602
606
 
603
- /* Populate settings menu with controls */
607
+ /**
608
+ * Populate settings menu with controls
609
+ */
604
610
  populateSettingsMenu() {
605
611
  const settingsMenu = this.controls?.querySelector('.settings-menu');
606
612
  if (!settingsMenu) return;
@@ -615,54 +621,104 @@ populateSettingsMenu() {
615
621
  </div>`;
616
622
  }
617
623
 
618
- // Speed Control submenu
624
+ // Speed Control - expandable
619
625
  if (this.options.showSpeedControl) {
620
626
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
621
627
  const currentSpeed = this.video ? this.video.playbackRate : 1;
622
- menuHTML += `<div class="settings-option" data-action="speed">
623
- <span class="settings-option-label">${speedLabel}</span>
624
- <span class="settings-option-value">${currentSpeed}x</span>
625
- <div class="settings-submenu speed-submenu">
626
- <div class="settings-suboption ${currentSpeed === 0.5 ? 'active' : ''}" data-speed="0.5">0.5x</div>
627
- <div class="settings-suboption ${currentSpeed === 0.75 ? 'active' : ''}" data-speed="0.75">0.75x</div>
628
- <div class="settings-suboption ${currentSpeed === 1 ? 'active' : ''}" data-speed="1">1x</div>
629
- <div class="settings-suboption ${currentSpeed === 1.25 ? 'active' : ''}" data-speed="1.25">1.25x</div>
630
- <div class="settings-suboption ${currentSpeed === 1.5 ? 'active' : ''}" data-speed="1.5">1.5x</div>
631
- <div class="settings-suboption ${currentSpeed === 2 ? 'active' : ''}" data-speed="2">2x</div>
632
- </div>
633
- </div>`;
628
+
629
+ menuHTML += `
630
+ <div class="settings-expandable-wrapper">
631
+ <div class="settings-option expandable-trigger" data-action="speed-expand">
632
+ <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
633
+ <span class="expand-arrow">▼</span>
634
+ </div>
635
+ <div class="settings-expandable-content" style="display: none;">`;
636
+
637
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
638
+ speeds.forEach(speed => {
639
+ const isActive = Math.abs(speed - currentSpeed) < 0.01;
640
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
641
+ });
642
+
643
+ menuHTML += `</div></div>`;
634
644
  }
635
645
 
636
- // Subtitles submenu
646
+ // Subtitles - expandable
637
647
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
638
648
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
639
649
  const currentTrack = this.currentSubtitleTrack;
640
- const currentLabel = this.subtitlesEnabled && currentTrack
641
- ? (currentTrack.label || 'Track')
642
- : (this.t('subtitlesoff') || 'Off');
643
-
644
- menuHTML += `<div class="settings-option" data-action="subtitles">
645
- <span class="settings-option-label">${subtitlesLabel}</span>
646
- <span class="settings-option-value">${currentLabel}</span>
647
- <div class="settings-submenu subtitles-submenu">
648
- <div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">
649
- ${this.t('subtitlesoff') || 'Off'}
650
- </div>`;
650
+ const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
651
+
652
+ menuHTML += `
653
+ <div class="settings-expandable-wrapper">
654
+ <div class="settings-option expandable-trigger" data-action="subtitles-expand">
655
+ <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
656
+ <span class="expand-arrow">▼</span>
657
+ </div>
658
+ <div class="settings-expandable-content" style="display: none;">`;
651
659
 
660
+ // Off option
661
+ menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
662
+
663
+ // Subtitle tracks
652
664
  this.textTracks.forEach((trackData, index) => {
653
665
  const isActive = this.currentSubtitleTrack === trackData.track;
654
- menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">
655
- ${trackData.label}
656
- </div>`;
666
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
657
667
  });
658
668
 
659
- menuHTML += '</div></div>';
669
+ menuHTML += `</div></div>`;
660
670
  }
661
671
 
662
672
  settingsMenu.innerHTML = menuHTML;
673
+
674
+ // Add scrollbar if needed
675
+ this.addSettingsMenuScrollbar();
676
+ }
677
+
678
+ /**
679
+ * Add scrollbar to settings menu on mobile
680
+ */
681
+ addSettingsMenuScrollbar() {
682
+ const settingsMenu = this.controls?.querySelector('.settings-menu');
683
+ if (!settingsMenu) return;
684
+
685
+ const playerHeight = this.container.offsetHeight;
686
+ const maxMenuHeight = playerHeight - 100;
687
+
688
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
689
+ settingsMenu.style.overflowY = 'auto';
690
+ settingsMenu.style.overflowX = 'hidden';
691
+
692
+ // Add scrollbar styling
693
+ if (!document.getElementById('player-settings-scrollbar-style')) {
694
+ const scrollbarStyle = document.createElement('style');
695
+ scrollbarStyle.id = 'player-settings-scrollbar-style';
696
+ scrollbarStyle.textContent = `
697
+ .settings-menu::-webkit-scrollbar {
698
+ width: 6px;
699
+ }
700
+ .settings-menu::-webkit-scrollbar-track {
701
+ background: rgba(255,255,255,0.05);
702
+ border-radius: 3px;
703
+ }
704
+ .settings-menu::-webkit-scrollbar-thumb {
705
+ background: rgba(255,255,255,0.3);
706
+ border-radius: 3px;
707
+ }
708
+ .settings-menu::-webkit-scrollbar-thumb:hover {
709
+ background: rgba(255,255,255,0.5);
710
+ }
711
+ `;
712
+ document.head.appendChild(scrollbarStyle);
713
+ }
714
+
715
+ settingsMenu.style.scrollbarWidth = 'thin';
716
+ settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
663
717
  }
664
718
 
665
- /* Bind settings menu events */
719
+ /**
720
+ * Bind settings menu events
721
+ */
666
722
  bindSettingsMenuEvents() {
667
723
  const settingsMenu = this.controls?.querySelector('.settings-menu');
668
724
  if (!settingsMenu) return;
@@ -670,7 +726,26 @@ bindSettingsMenuEvents() {
670
726
  settingsMenu.addEventListener('click', (e) => {
671
727
  e.stopPropagation();
672
728
 
673
- // Handle direct actions
729
+ // Handle expandable triggers
730
+ if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
731
+ const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
732
+ const wrapper = trigger.closest('.settings-expandable-wrapper');
733
+ const content = wrapper.querySelector('.settings-expandable-content');
734
+ const arrow = trigger.querySelector('.expand-arrow');
735
+
736
+ const isExpanded = content.style.display !== 'none';
737
+
738
+ if (isExpanded) {
739
+ content.style.display = 'none';
740
+ arrow.style.transform = 'rotate(0deg)';
741
+ } else {
742
+ content.style.display = 'block';
743
+ arrow.style.transform = 'rotate(180deg)';
744
+ }
745
+ return;
746
+ }
747
+
748
+ // Handle direct actions (like PiP)
674
749
  if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
675
750
  const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
676
751
  const action = option.getAttribute('data-action');
@@ -683,32 +758,31 @@ bindSettingsMenuEvents() {
683
758
 
684
759
  // Handle submenu actions
685
760
  if (e.target.classList.contains('settings-suboption')) {
686
- const parent = e.target.closest('.settings-option');
687
- const action = parent?.getAttribute('data-action');
761
+ const wrapper = e.target.closest('.settings-expandable-wrapper');
762
+ const trigger = wrapper.querySelector('.expandable-trigger');
763
+ const action = trigger.getAttribute('data-action');
688
764
 
689
- if (action === 'speed') {
765
+ if (action === 'speed-expand') {
690
766
  const speed = parseFloat(e.target.getAttribute('data-speed'));
691
767
  if (speed && speed > 0 && this.video && !this.isChangingQuality) {
692
768
  this.video.playbackRate = speed;
693
769
 
694
770
  // Update active states
695
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
696
- opt.classList.remove('active');
697
- });
771
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
698
772
  e.target.classList.add('active');
699
773
 
700
- // Update value display
701
- const valueEl = parent.querySelector('.settings-option-value');
702
- if (valueEl) valueEl.textContent = speed + 'x';
774
+ // Update trigger text
775
+ const label = trigger.querySelector('.settings-option-label');
776
+ if (label) {
777
+ const speedLabel = this.t('playback_speed') || 'Playback Speed';
778
+ label.textContent = `${speedLabel}: ${speed}x`;
779
+ }
703
780
 
704
781
  // Trigger event
705
782
  this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
706
783
  }
707
- }
708
-
709
- else if (action === 'subtitles') {
784
+ } else if (action === 'subtitles-expand') {
710
785
  const trackData = e.target.getAttribute('data-track');
711
-
712
786
  if (trackData === 'off') {
713
787
  this.disableSubtitles();
714
788
  } else {
@@ -717,14 +791,15 @@ bindSettingsMenuEvents() {
717
791
  }
718
792
 
719
793
  // Update active states
720
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
721
- opt.classList.remove('active');
722
- });
794
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
723
795
  e.target.classList.add('active');
724
796
 
725
- // Update value display
726
- const valueEl = parent.querySelector('.settings-option-value');
727
- if (valueEl) valueEl.textContent = e.target.textContent;
797
+ // Update trigger text
798
+ const label = trigger.querySelector('.settings-option-label');
799
+ if (label) {
800
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
801
+ label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
802
+ }
728
803
  }
729
804
  }
730
805
  });
@@ -996,6 +1071,60 @@ isAutoHideInitialized() {
996
1071
  return this.autoHideInitialized;
997
1072
  }
998
1073
 
1074
+ /**
1075
+ * Hide mouse cursor in player container
1076
+ * Only hides cursor in main container, not in plugin iframes
1077
+ */
1078
+ hideCursor() {
1079
+ if (!this.options.hideCursor) {
1080
+ return; // Do not hide cursor if option is disabled
1081
+ }
1082
+
1083
+ if (this.container) {
1084
+ this.container.classList.add('hide-cursor');
1085
+ if (this.options.debug) console.log('🖱️ Cursor hidden');
1086
+ }
1087
+ }
1088
+
1089
+ /**
1090
+ * Show mouse cursor in player container
1091
+ */
1092
+ showCursor() {
1093
+ if (this.container) {
1094
+ this.container.classList.remove('hide-cursor');
1095
+ if (this.options.debug) console.log('🖱️ Cursor shown');
1096
+ }
1097
+ }
1098
+
1099
+ /**
1100
+ * Enable cursor hiding when controlbar is hidden
1101
+ * @returns {Object} this
1102
+ */
1103
+ enableCursorHiding() {
1104
+ this.options.hideCursor = true;
1105
+ if (this.options.debug) console.log('Cursor hiding enabled');
1106
+ return this;
1107
+ }
1108
+
1109
+ /**
1110
+ * Disable cursor hiding - cursor will always be visible
1111
+ * @returns {Object} this
1112
+ */
1113
+ disableCursorHiding() {
1114
+ this.options.hideCursor = false;
1115
+ this.showCursor(); // Ensure cursor is shown immediately
1116
+ if (this.options.debug) console.log('Cursor hiding disabled');
1117
+ return this;
1118
+ }
1119
+
1120
+ /**
1121
+ * Check if cursor hiding is enabled
1122
+ * @returns {Boolean} True if cursor hiding is enabled
1123
+ */
1124
+ isCursorHidingEnabled() {
1125
+ return this.options.hideCursor;
1126
+ }
1127
+
999
1128
  /* PLAYLIST CONTROLS */
1000
1129
  showPlaylistControls() {
1001
1130
  if (!this.playlistPrevBtn || !this.playlistNextBtn) return;
package/src/core.js CHANGED
@@ -12,22 +12,23 @@ constructor(videoElement, options = {}) {
12
12
  }
13
13
 
14
14
  this.options = {
15
- showQualitySelector: true,
16
- showSpeedControl: true,
17
- showFullscreen: true,
18
- showPictureInPicture: true,
19
- showSubtitles: true,
20
- subtitlesEnabled: false,
21
- autoHide: true,
22
- autoHideDelay: 3000,
15
+ showQualitySelector: true, // Enable quality selector button
16
+ showSpeedControl: true, // Enable speed control button
17
+ showFullscreen: true, // Enable fullscreen button
18
+ showPictureInPicture: true, // Enable PiP button
19
+ showSubtitles: true, // Enable subtitles button
20
+ subtitlesEnabled: false, // Enable subtitles by default if available
21
+ autoHide: true, // auto-hide controls when idle
22
+ autoHideDelay: 3000, // hide controls after ... seconds of inactivity (specificed in milliseconds)
23
+ hideCursor: true, // hide mouse cursor when idle
23
24
  poster: null, // URL of poster image
24
25
  showPosterOnEnd: false, // Show poster again when video ends
25
- keyboardControls: true,
26
- showSeekTooltip: true,
27
- showTitleOverlay: false,
28
- videoTitle: '',
29
- videoSubtitle: '',
30
- persistentTitle: false,
26
+ keyboardControls: true, // Enable keyboard controls
27
+ showSeekTooltip: true, // Show tooltip on seek bar at mouse hover
28
+ showTitleOverlay: false, // Show video title overlay
29
+ videoTitle: '', // Title text to show in overlay
30
+ videoSubtitle: '', // Subtitle text to show in overlay
31
+ persistentTitle: false, // If true, title overlay stays visible
31
32
  debug: false, // Enable/disable debug logging
32
33
  autoplay: false, // if video should autoplay at start
33
34
  defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
@@ -57,8 +58,8 @@ constructor(videoElement, options = {}) {
57
58
  //seek shape
58
59
  seekHandleShape: 'circle', // Available shape: none, circle, square, diamond, arrow, triangle, heart, star
59
60
  // AUDIO PLAYER
60
- audiofile: false,
61
- audiowave: false,
61
+ audiofile: false, // if true, adapt player to audio file (hide video element)
62
+ audiowave: false, // if true, show audio wave visualization (Web Audio API)
62
63
  // RESOLUTION CONTROL
63
64
  resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
64
65
  ...options