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.
@@ -408,22 +408,23 @@ constructor(videoElement, options = {}) {
408
408
  }
409
409
 
410
410
  this.options = {
411
- showQualitySelector: true,
412
- showSpeedControl: true,
413
- showFullscreen: true,
414
- showPictureInPicture: true,
415
- showSubtitles: true,
416
- subtitlesEnabled: false,
417
- autoHide: true,
418
- autoHideDelay: 3000,
411
+ showQualitySelector: true, // Enable quality selector button
412
+ showSpeedControl: true, // Enable speed control button
413
+ showFullscreen: true, // Enable fullscreen button
414
+ showPictureInPicture: true, // Enable PiP button
415
+ showSubtitles: true, // Enable subtitles button
416
+ subtitlesEnabled: false, // Enable subtitles by default if available
417
+ autoHide: true, // auto-hide controls when idle
418
+ autoHideDelay: 3000, // hide controls after ... seconds of inactivity (specificed in milliseconds)
419
+ hideCursor: true, // hide mouse cursor when idle
419
420
  poster: null, // URL of poster image
420
421
  showPosterOnEnd: false, // Show poster again when video ends
421
- keyboardControls: true,
422
- showSeekTooltip: true,
423
- showTitleOverlay: false,
424
- videoTitle: '',
425
- videoSubtitle: '',
426
- persistentTitle: false,
422
+ keyboardControls: true, // Enable keyboard controls
423
+ showSeekTooltip: true, // Show tooltip on seek bar at mouse hover
424
+ showTitleOverlay: false, // Show video title overlay
425
+ videoTitle: '', // Title text to show in overlay
426
+ videoSubtitle: '', // Subtitle text to show in overlay
427
+ persistentTitle: false, // If true, title overlay stays visible
427
428
  debug: false, // Enable/disable debug logging
428
429
  autoplay: false, // if video should autoplay at start
429
430
  defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
@@ -453,8 +454,8 @@ constructor(videoElement, options = {}) {
453
454
  //seek shape
454
455
  seekHandleShape: 'circle', // Available shape: none, circle, square, diamond, arrow, triangle, heart, star
455
456
  // AUDIO PLAYER
456
- audiofile: false,
457
- audiowave: false,
457
+ audiofile: false, // if true, adapt player to audio file (hide video element)
458
+ audiowave: false, // if true, show audio wave visualization (Web Audio API)
458
459
  // RESOLUTION CONTROL
459
460
  resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
460
461
  ...options
@@ -2845,6 +2846,7 @@ initAutoHide() {
2845
2846
 
2846
2847
  onMouseMoveInPlayer(e) {
2847
2848
  this.showControlsNow();
2849
+ this.showCursor();
2848
2850
  this.resetAutoHideTimer();
2849
2851
  }
2850
2852
 
@@ -2902,66 +2904,69 @@ resetAutoHideTimer() {
2902
2904
  showControlsNow() {
2903
2905
  if (this.controls) {
2904
2906
  this.controls.classList.add('show');
2905
- }
2906
2907
 
2907
- // Add has-controls class to container for watermark visibility
2908
- if (this.container) {
2909
- this.container.classList.add('has-controls');
2908
+ // Add has-controls class to container (for watermark visibility)
2909
+ if (this.container) {
2910
+ this.container.classList.add('has-controls');
2911
+ }
2912
+
2910
2913
  this.updateControlbarHeight();
2914
+
2911
2915
  // Update watermark position
2912
2916
  if (this.updateWatermarkPosition) {
2913
2917
  this.updateWatermarkPosition();
2914
2918
  }
2915
- }
2916
2919
 
2917
- // Show title overlay with controls if not persistent
2918
- if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
2919
- this.showTitleOverlay();
2920
- }
2920
+ // Show title overlay with controls (if not persistent)
2921
+ if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
2922
+ this.showTitleOverlay();
2923
+ }
2924
+
2925
+ // *show cursor when controls are shown*
2926
+ this.showCursor();
2921
2927
 
2922
- if (this.autoHideDebug && this.options.debug) console.log('✅ Controls shown');
2928
+ if (this.autoHideDebug && this.options.debug) console.log('✅ Controls shown');
2929
+ }
2923
2930
  }
2924
2931
 
2925
2932
  hideControlsNow() {
2926
- // Don't hide if mouse is still over controls (allow hiding on touch devices)
2933
+ // Dont hide if mouse is still over controls (allow hiding on touch devices)
2927
2934
  const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
2928
-
2929
2935
  if (this.mouseOverControls && !isTouchDevice) {
2930
- if (this.autoHideDebug && this.options.debug) {
2931
- console.log('🚫 Not hiding - mouse still over controls');
2932
- }
2936
+ if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - mouse still over controls');
2933
2937
  return;
2934
2938
  }
2935
2939
 
2936
- // Don't hide if video is paused
2940
+ // Dont hide if video is paused
2937
2941
  if (this.video && this.video.paused) {
2938
- if (this.autoHideDebug && this.options.debug) {
2939
- console.log('🚫 Not hiding - video is paused');
2940
- }
2942
+ if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - video is paused');
2941
2943
  return;
2942
2944
  }
2943
2945
 
2944
2946
  if (this.controls) {
2945
2947
  this.controls.classList.remove('show');
2946
2948
 
2947
- // Remove has-controls class from container for watermark visibility
2949
+ // Remove has-controls class from container (for watermark visibility)
2948
2950
  if (this.container) {
2949
2951
  this.container.classList.remove('has-controls');
2950
- this.updateControlbarHeight();
2951
- // Update watermark position
2952
- if (this.updateWatermarkPosition) {
2953
- this.updateWatermarkPosition();
2954
- }
2955
2952
  }
2956
- }
2957
2953
 
2958
- // Hide title overlay with controls (if not persistent)
2959
- if (this.options.showTitleOverlay && !this.options.persistentTitle) {
2960
- this.hideTitleOverlay();
2961
- }
2954
+ this.updateControlbarHeight();
2962
2955
 
2963
- if (this.autoHideDebug && this.options.debug) {
2964
- console.log('👁️ Controls hidden');
2956
+ // Update watermark position
2957
+ if (this.updateWatermarkPosition) {
2958
+ this.updateWatermarkPosition();
2959
+ }
2960
+
2961
+ // Hide title overlay with controls (if not persistent)
2962
+ if (this.options.showTitleOverlay && !this.options.persistentTitle) {
2963
+ this.hideTitleOverlay();
2964
+ }
2965
+
2966
+ // *hide cursor after controls are hidden*
2967
+ this.hideCursor();
2968
+
2969
+ if (this.autoHideDebug && this.options.debug) console.log('✅ Controls hidden');
2965
2970
  }
2966
2971
  }
2967
2972
 
@@ -3360,7 +3365,9 @@ updateSettingsMenuVisibility() {
3360
3365
  }
3361
3366
  }
3362
3367
 
3363
- /* Populate settings menu with controls */
3368
+ /**
3369
+ * Populate settings menu with controls
3370
+ */
3364
3371
  populateSettingsMenu() {
3365
3372
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3366
3373
  if (!settingsMenu) return;
@@ -3375,54 +3382,104 @@ populateSettingsMenu() {
3375
3382
  </div>`;
3376
3383
  }
3377
3384
 
3378
- // Speed Control submenu
3385
+ // Speed Control - expandable
3379
3386
  if (this.options.showSpeedControl) {
3380
3387
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
3381
3388
  const currentSpeed = this.video ? this.video.playbackRate : 1;
3382
- menuHTML += `<div class="settings-option" data-action="speed">
3383
- <span class="settings-option-label">${speedLabel}</span>
3384
- <span class="settings-option-value">${currentSpeed}x</span>
3385
- <div class="settings-submenu speed-submenu">
3386
- <div class="settings-suboption ${currentSpeed === 0.5 ? 'active' : ''}" data-speed="0.5">0.5x</div>
3387
- <div class="settings-suboption ${currentSpeed === 0.75 ? 'active' : ''}" data-speed="0.75">0.75x</div>
3388
- <div class="settings-suboption ${currentSpeed === 1 ? 'active' : ''}" data-speed="1">1x</div>
3389
- <div class="settings-suboption ${currentSpeed === 1.25 ? 'active' : ''}" data-speed="1.25">1.25x</div>
3390
- <div class="settings-suboption ${currentSpeed === 1.5 ? 'active' : ''}" data-speed="1.5">1.5x</div>
3391
- <div class="settings-suboption ${currentSpeed === 2 ? 'active' : ''}" data-speed="2">2x</div>
3392
- </div>
3393
- </div>`;
3389
+
3390
+ menuHTML += `
3391
+ <div class="settings-expandable-wrapper">
3392
+ <div class="settings-option expandable-trigger" data-action="speed-expand">
3393
+ <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
3394
+ <span class="expand-arrow">▼</span>
3395
+ </div>
3396
+ <div class="settings-expandable-content" style="display: none;">`;
3397
+
3398
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
3399
+ speeds.forEach(speed => {
3400
+ const isActive = Math.abs(speed - currentSpeed) < 0.01;
3401
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
3402
+ });
3403
+
3404
+ menuHTML += `</div></div>`;
3394
3405
  }
3395
3406
 
3396
- // Subtitles submenu
3407
+ // Subtitles - expandable
3397
3408
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
3398
3409
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3399
3410
  const currentTrack = this.currentSubtitleTrack;
3400
- const currentLabel = this.subtitlesEnabled && currentTrack
3401
- ? (currentTrack.label || 'Track')
3402
- : (this.t('subtitlesoff') || 'Off');
3403
-
3404
- menuHTML += `<div class="settings-option" data-action="subtitles">
3405
- <span class="settings-option-label">${subtitlesLabel}</span>
3406
- <span class="settings-option-value">${currentLabel}</span>
3407
- <div class="settings-submenu subtitles-submenu">
3408
- <div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">
3409
- ${this.t('subtitlesoff') || 'Off'}
3410
- </div>`;
3411
+ const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
3412
+
3413
+ menuHTML += `
3414
+ <div class="settings-expandable-wrapper">
3415
+ <div class="settings-option expandable-trigger" data-action="subtitles-expand">
3416
+ <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
3417
+ <span class="expand-arrow">▼</span>
3418
+ </div>
3419
+ <div class="settings-expandable-content" style="display: none;">`;
3411
3420
 
3421
+ // Off option
3422
+ menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
3423
+
3424
+ // Subtitle tracks
3412
3425
  this.textTracks.forEach((trackData, index) => {
3413
3426
  const isActive = this.currentSubtitleTrack === trackData.track;
3414
- menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">
3415
- ${trackData.label}
3416
- </div>`;
3427
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
3417
3428
  });
3418
3429
 
3419
- menuHTML += '</div></div>';
3430
+ menuHTML += `</div></div>`;
3420
3431
  }
3421
3432
 
3422
3433
  settingsMenu.innerHTML = menuHTML;
3434
+
3435
+ // Add scrollbar if needed
3436
+ this.addSettingsMenuScrollbar();
3437
+ }
3438
+
3439
+ /**
3440
+ * Add scrollbar to settings menu on mobile
3441
+ */
3442
+ addSettingsMenuScrollbar() {
3443
+ const settingsMenu = this.controls?.querySelector('.settings-menu');
3444
+ if (!settingsMenu) return;
3445
+
3446
+ const playerHeight = this.container.offsetHeight;
3447
+ const maxMenuHeight = playerHeight - 100;
3448
+
3449
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3450
+ settingsMenu.style.overflowY = 'auto';
3451
+ settingsMenu.style.overflowX = 'hidden';
3452
+
3453
+ // Add scrollbar styling
3454
+ if (!document.getElementById('player-settings-scrollbar-style')) {
3455
+ const scrollbarStyle = document.createElement('style');
3456
+ scrollbarStyle.id = 'player-settings-scrollbar-style';
3457
+ scrollbarStyle.textContent = `
3458
+ .settings-menu::-webkit-scrollbar {
3459
+ width: 6px;
3460
+ }
3461
+ .settings-menu::-webkit-scrollbar-track {
3462
+ background: rgba(255,255,255,0.05);
3463
+ border-radius: 3px;
3464
+ }
3465
+ .settings-menu::-webkit-scrollbar-thumb {
3466
+ background: rgba(255,255,255,0.3);
3467
+ border-radius: 3px;
3468
+ }
3469
+ .settings-menu::-webkit-scrollbar-thumb:hover {
3470
+ background: rgba(255,255,255,0.5);
3471
+ }
3472
+ `;
3473
+ document.head.appendChild(scrollbarStyle);
3474
+ }
3475
+
3476
+ settingsMenu.style.scrollbarWidth = 'thin';
3477
+ settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
3423
3478
  }
3424
3479
 
3425
- /* Bind settings menu events */
3480
+ /**
3481
+ * Bind settings menu events
3482
+ */
3426
3483
  bindSettingsMenuEvents() {
3427
3484
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3428
3485
  if (!settingsMenu) return;
@@ -3430,7 +3487,26 @@ bindSettingsMenuEvents() {
3430
3487
  settingsMenu.addEventListener('click', (e) => {
3431
3488
  e.stopPropagation();
3432
3489
 
3433
- // Handle direct actions
3490
+ // Handle expandable triggers
3491
+ if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
3492
+ const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
3493
+ const wrapper = trigger.closest('.settings-expandable-wrapper');
3494
+ const content = wrapper.querySelector('.settings-expandable-content');
3495
+ const arrow = trigger.querySelector('.expand-arrow');
3496
+
3497
+ const isExpanded = content.style.display !== 'none';
3498
+
3499
+ if (isExpanded) {
3500
+ content.style.display = 'none';
3501
+ arrow.style.transform = 'rotate(0deg)';
3502
+ } else {
3503
+ content.style.display = 'block';
3504
+ arrow.style.transform = 'rotate(180deg)';
3505
+ }
3506
+ return;
3507
+ }
3508
+
3509
+ // Handle direct actions (like PiP)
3434
3510
  if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
3435
3511
  const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
3436
3512
  const action = option.getAttribute('data-action');
@@ -3443,32 +3519,31 @@ bindSettingsMenuEvents() {
3443
3519
 
3444
3520
  // Handle submenu actions
3445
3521
  if (e.target.classList.contains('settings-suboption')) {
3446
- const parent = e.target.closest('.settings-option');
3447
- const action = parent?.getAttribute('data-action');
3522
+ const wrapper = e.target.closest('.settings-expandable-wrapper');
3523
+ const trigger = wrapper.querySelector('.expandable-trigger');
3524
+ const action = trigger.getAttribute('data-action');
3448
3525
 
3449
- if (action === 'speed') {
3526
+ if (action === 'speed-expand') {
3450
3527
  const speed = parseFloat(e.target.getAttribute('data-speed'));
3451
3528
  if (speed && speed > 0 && this.video && !this.isChangingQuality) {
3452
3529
  this.video.playbackRate = speed;
3453
3530
 
3454
3531
  // Update active states
3455
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
3456
- opt.classList.remove('active');
3457
- });
3532
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
3458
3533
  e.target.classList.add('active');
3459
3534
 
3460
- // Update value display
3461
- const valueEl = parent.querySelector('.settings-option-value');
3462
- if (valueEl) valueEl.textContent = speed + 'x';
3535
+ // Update trigger text
3536
+ const label = trigger.querySelector('.settings-option-label');
3537
+ if (label) {
3538
+ const speedLabel = this.t('playback_speed') || 'Playback Speed';
3539
+ label.textContent = `${speedLabel}: ${speed}x`;
3540
+ }
3463
3541
 
3464
3542
  // Trigger event
3465
3543
  this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
3466
3544
  }
3467
- }
3468
-
3469
- else if (action === 'subtitles') {
3545
+ } else if (action === 'subtitles-expand') {
3470
3546
  const trackData = e.target.getAttribute('data-track');
3471
-
3472
3547
  if (trackData === 'off') {
3473
3548
  this.disableSubtitles();
3474
3549
  } else {
@@ -3477,14 +3552,15 @@ bindSettingsMenuEvents() {
3477
3552
  }
3478
3553
 
3479
3554
  // Update active states
3480
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
3481
- opt.classList.remove('active');
3482
- });
3555
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
3483
3556
  e.target.classList.add('active');
3484
3557
 
3485
- // Update value display
3486
- const valueEl = parent.querySelector('.settings-option-value');
3487
- if (valueEl) valueEl.textContent = e.target.textContent;
3558
+ // Update trigger text
3559
+ const label = trigger.querySelector('.settings-option-label');
3560
+ if (label) {
3561
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3562
+ label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
3563
+ }
3488
3564
  }
3489
3565
  }
3490
3566
  });
@@ -3756,6 +3832,60 @@ isAutoHideInitialized() {
3756
3832
  return this.autoHideInitialized;
3757
3833
  }
3758
3834
 
3835
+ /**
3836
+ * Hide mouse cursor in player container
3837
+ * Only hides cursor in main container, not in plugin iframes
3838
+ */
3839
+ hideCursor() {
3840
+ if (!this.options.hideCursor) {
3841
+ return; // Do not hide cursor if option is disabled
3842
+ }
3843
+
3844
+ if (this.container) {
3845
+ this.container.classList.add('hide-cursor');
3846
+ if (this.options.debug) console.log('🖱️ Cursor hidden');
3847
+ }
3848
+ }
3849
+
3850
+ /**
3851
+ * Show mouse cursor in player container
3852
+ */
3853
+ showCursor() {
3854
+ if (this.container) {
3855
+ this.container.classList.remove('hide-cursor');
3856
+ if (this.options.debug) console.log('🖱️ Cursor shown');
3857
+ }
3858
+ }
3859
+
3860
+ /**
3861
+ * Enable cursor hiding when controlbar is hidden
3862
+ * @returns {Object} this
3863
+ */
3864
+ enableCursorHiding() {
3865
+ this.options.hideCursor = true;
3866
+ if (this.options.debug) console.log('Cursor hiding enabled');
3867
+ return this;
3868
+ }
3869
+
3870
+ /**
3871
+ * Disable cursor hiding - cursor will always be visible
3872
+ * @returns {Object} this
3873
+ */
3874
+ disableCursorHiding() {
3875
+ this.options.hideCursor = false;
3876
+ this.showCursor(); // Ensure cursor is shown immediately
3877
+ if (this.options.debug) console.log('Cursor hiding disabled');
3878
+ return this;
3879
+ }
3880
+
3881
+ /**
3882
+ * Check if cursor hiding is enabled
3883
+ * @returns {Boolean} True if cursor hiding is enabled
3884
+ */
3885
+ isCursorHidingEnabled() {
3886
+ return this.options.hideCursor;
3887
+ }
3888
+
3759
3889
  /* PLAYLIST CONTROLS */
3760
3890
  showPlaylistControls() {
3761
3891
  if (!this.playlistPrevBtn || !this.playlistNextBtn) return;