myetv-player 1.0.8 → 1.1.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.
@@ -422,6 +422,7 @@ constructor(videoElement, options = {}) {
422
422
  showSeekTooltip: true,
423
423
  showTitleOverlay: false,
424
424
  videoTitle: '',
425
+ videoSubtitle: '',
425
426
  persistentTitle: false,
426
427
  debug: false, // Enable/disable debug logging
427
428
  autoplay: false, // if video should autoplay at start
@@ -978,9 +979,16 @@ createTitleOverlay() {
978
979
  const titleText = document.createElement('h2');
979
980
  titleText.className = 'title-text';
980
981
  titleText.textContent = this.options.videoTitle || '';
981
-
982
982
  overlay.appendChild(titleText);
983
983
 
984
+ // add subtitles
985
+ if (this.options.videoSubtitle) {
986
+ const subtitleText = document.createElement('p');
987
+ subtitleText.className = 'subtitle-text';
988
+ subtitleText.textContent = this.options.videoSubtitle;
989
+ overlay.appendChild(subtitleText);
990
+ }
991
+
984
992
  if (this.controls) {
985
993
  this.container.insertBefore(overlay, this.controls);
986
994
  } else {
@@ -1054,6 +1062,31 @@ getVideoTitle() {
1054
1062
  return this.options.videoTitle;
1055
1063
  }
1056
1064
 
1065
+ setVideoSubtitle(subtitle) {
1066
+ this.options.videoSubtitle = subtitle || '';
1067
+
1068
+ if (this.titleOverlay) {
1069
+ let subtitleElement = this.titleOverlay.querySelector('.subtitle-text');
1070
+
1071
+ if (subtitle) {
1072
+ if (!subtitleElement) {
1073
+ subtitleElement = document.createElement('p');
1074
+ subtitleElement.className = 'subtitle-text';
1075
+ this.titleOverlay.appendChild(subtitleElement);
1076
+ }
1077
+ subtitleElement.textContent = subtitle;
1078
+ } else if (subtitleElement) {
1079
+ subtitleElement.remove();
1080
+ }
1081
+ }
1082
+
1083
+ return this;
1084
+ }
1085
+
1086
+ getVideoSubtitle() {
1087
+ return this.options.videoSubtitle;
1088
+ }
1089
+
1057
1090
  setPersistentTitle(persistent) {
1058
1091
  this.options.persistentTitle = persistent;
1059
1092
 
@@ -1533,14 +1566,19 @@ seek(e) {
1533
1566
  if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
1534
1567
 
1535
1568
  const rect = this.progressContainer.getBoundingClientRect();
1536
- const clickX = e.clientX - rect.left;
1569
+
1570
+ // Support both mouse and touch events
1571
+ const clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches[0] ? e.touches[0].clientX : (e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : 0));
1572
+
1573
+ const clickX = clientX - rect.left;
1537
1574
  const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1538
1575
 
1539
1576
  if (this.video.duration && !isNaN(this.video.duration)) {
1540
1577
  this.video.currentTime = percentage * this.video.duration;
1541
- const progress = percentage * 100;
1542
- this.progressFilled.style.width = progress + '%';
1543
- this.progressHandle.style.left = progress + '%';
1578
+
1579
+ const progress = `${percentage * 100}%`;
1580
+ this.progressFilled.style.width = progress;
1581
+ this.progressHandle.style.left = progress;
1544
1582
  }
1545
1583
  }
1546
1584
 
@@ -2514,12 +2552,28 @@ addEventListener(eventType, callback) {
2514
2552
  });
2515
2553
  }
2516
2554
 
2517
- if (this.progressContainer) {
2518
- this.progressContainer.addEventListener('click', (e) => this.seek(e));
2519
- this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
2520
- }
2555
+ if (this.progressContainer) {
2556
+ // Mouse events (desktop)
2557
+ this.progressContainer.addEventListener('click', (e) => this.seek(e));
2558
+ this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
2559
+
2560
+ // Touch events (mobile)
2561
+ this.progressContainer.addEventListener('touchstart', (e) => {
2562
+ e.preventDefault(); // Prevent scrolling when touching the seek bar
2563
+ this.startSeeking(e);
2564
+ }, { passive: false });
2521
2565
 
2522
2566
  this.setupSeekTooltip();
2567
+ }
2568
+
2569
+ // Add touch events directly on the handle for better mobile dragging
2570
+ if (this.progressHandle) {
2571
+ this.progressHandle.addEventListener('touchstart', (e) => {
2572
+ e.preventDefault(); // Prevent default touch behavior
2573
+ e.stopPropagation(); // Stop event from bubbling to progressContainer
2574
+ this.startSeeking(e);
2575
+ }, { passive: false });
2576
+ }
2523
2577
 
2524
2578
  // NOTE: Auto-hide events are handled in initAutoHide() after everything is ready
2525
2579
 
@@ -2540,7 +2594,19 @@ addEventListener(eventType, callback) {
2540
2594
  document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
2541
2595
 
2542
2596
  document.addEventListener('mousemove', (e) => this.continueSeeking(e));
2543
- document.addEventListener('mouseup', () => this.endSeeking());
2597
+ document.addEventListener('mouseup', () => this.endSeeking());
2598
+
2599
+ // Touch events for seeking (mobile)
2600
+ document.addEventListener('touchmove', (e) => {
2601
+ if (this.isUserSeeking) {
2602
+ e.preventDefault(); // Prevent scrolling while seeking
2603
+ this.continueSeeking(e);
2604
+ }
2605
+ }, { passive: false });
2606
+
2607
+ document.addEventListener('touchend', () => this.endSeeking());
2608
+ document.addEventListener('touchcancel', () => this.endSeeking());
2609
+
2544
2610
  }
2545
2611
 
2546
2612
  /* Controls Module for MYETV Video Player
@@ -2548,7 +2614,7 @@ addEventListener(eventType, callback) {
2548
2614
  * Created by https://www.myetv.tv https://oskarcosimo.com
2549
2615
  */
2550
2616
 
2551
- /* AUTO-HIDE SYSTEM - IMPROVED AND COMPREHENSIVE */
2617
+ /* AUTO-HIDE SYSTEM */
2552
2618
  initAutoHide() {
2553
2619
  if (!this.options.autoHide) {
2554
2620
  if (this.options.debug) console.log('Auto-hide disabled in options');
@@ -2688,12 +2754,17 @@ showControlsNow() {
2688
2754
  this.controls.classList.add('show');
2689
2755
  }
2690
2756
 
2691
- // ADD THIS: Add has-controls class to container for watermark visibility
2757
+ // Add has-controls class to container for watermark visibility
2692
2758
  if (this.container) {
2693
2759
  this.container.classList.add('has-controls');
2760
+ this.updateControlbarHeight();
2761
+ // Update watermark position
2762
+ if (this.updateWatermarkPosition) {
2763
+ this.updateWatermarkPosition();
2764
+ }
2694
2765
  }
2695
2766
 
2696
- // Fix: Show title overlay with controls if not persistent
2767
+ // Show title overlay with controls if not persistent
2697
2768
  if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
2698
2769
  this.showTitleOverlay();
2699
2770
  }
@@ -2704,32 +2775,44 @@ showControlsNow() {
2704
2775
  hideControlsNow() {
2705
2776
  // Don't hide if mouse is still over controls (allow hiding on touch devices)
2706
2777
  const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
2778
+
2707
2779
  if (this.mouseOverControls && !isTouchDevice) {
2708
- if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - mouse still over controls');
2780
+ if (this.autoHideDebug && this.options.debug) {
2781
+ console.log('🚫 Not hiding - mouse still over controls');
2782
+ }
2709
2783
  return;
2710
2784
  }
2711
2785
 
2712
2786
  // Don't hide if video is paused
2713
2787
  if (this.video && this.video.paused) {
2714
- if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - video is paused');
2788
+ if (this.autoHideDebug && this.options.debug) {
2789
+ console.log('🚫 Not hiding - video is paused');
2790
+ }
2715
2791
  return;
2716
2792
  }
2717
2793
 
2718
2794
  if (this.controls) {
2719
2795
  this.controls.classList.remove('show');
2720
- }
2721
2796
 
2722
- // ADD THIS: Remove has-controls class from container for watermark visibility
2723
- if (this.container) {
2724
- this.container.classList.remove('has-controls');
2797
+ // Remove has-controls class from container for watermark visibility
2798
+ if (this.container) {
2799
+ this.container.classList.remove('has-controls');
2800
+ this.updateControlbarHeight();
2801
+ // Update watermark position
2802
+ if (this.updateWatermarkPosition) {
2803
+ this.updateWatermarkPosition();
2804
+ }
2805
+ }
2725
2806
  }
2726
2807
 
2727
- // Fix: Hide title overlay with controls if not persistent
2808
+ // Hide title overlay with controls (if not persistent)
2728
2809
  if (this.options.showTitleOverlay && !this.options.persistentTitle) {
2729
2810
  this.hideTitleOverlay();
2730
2811
  }
2731
2812
 
2732
- if (this.autoHideDebug && this.options.debug) console.log('❌ Controls hidden');
2813
+ if (this.autoHideDebug && this.options.debug) {
2814
+ console.log('👁️ Controls hidden');
2815
+ }
2733
2816
  }
2734
2817
 
2735
2818
  showControls() {
@@ -3008,28 +3091,62 @@ createControls() {
3008
3091
  // NEW: Initialize responsive settings menu
3009
3092
  setTimeout(() => {
3010
3093
  this.initializeResponsiveMenu();
3094
+ this.updateControlbarHeight();
3011
3095
  }, 100);
3012
3096
  }
3013
3097
 
3014
- /* NEW: Initialize responsive menu with dynamic width calculation */
3098
+ /* Initialize responsive menu with dynamic width calculation */
3015
3099
  initializeResponsiveMenu() {
3016
3100
  if (!this.controls) return;
3017
3101
 
3018
3102
  // Track screen size
3019
3103
  this.isSmallScreen = false;
3020
3104
 
3021
- // Check initial size and set up listener
3105
+ // Check initial size
3022
3106
  this.checkScreenSize();
3023
3107
 
3024
- window.addEventListener('resize', () => {
3108
+ // Bind resize handler with updateControlbarHeight
3109
+ const resizeHandler = () => {
3025
3110
  this.checkScreenSize();
3026
- });
3111
+ this.updateControlbarHeight();
3112
+ };
3113
+
3114
+ // Bind del context
3115
+ this.resizeHandler = resizeHandler.bind(this);
3116
+ window.addEventListener('resize', this.resizeHandler);
3027
3117
 
3028
3118
  // Bind events for settings menu
3029
3119
  this.bindSettingsMenuEvents();
3030
3120
  }
3031
3121
 
3032
- /* NEW: Dynamic width calculation based on logo presence */
3122
+ // Dynamic controlbar height tracking for watermark positioning
3123
+ updateControlbarHeight() {
3124
+ if (!this.controls) return;
3125
+
3126
+ const height = this.controls.offsetHeight;
3127
+ if (this.container) {
3128
+
3129
+ this.container.style.setProperty('--player-controls-height', `${height}px`);
3130
+
3131
+ const watermark = this.container.querySelector('.video-watermark.watermark-bottomleft, .video-watermark.watermark-bottomright');
3132
+ if (watermark) {
3133
+ const hasControls = this.container.classList.contains('has-controls');
3134
+ const isHideOnAutoHide = watermark.classList.contains('hide-on-autohide');
3135
+
3136
+ if (hasControls || !isHideOnAutoHide) {
3137
+ watermark.style.bottom = `${height + 15}px`;
3138
+ } else {
3139
+ watermark.style.bottom = '15px';
3140
+ }
3141
+ }
3142
+ }
3143
+
3144
+ if (this.options.debug) {
3145
+ console.log(`Controlbar height updated: ${height}px`);
3146
+ }
3147
+ }
3148
+
3149
+ /* Dynamic width calculation based on logo presence */
3033
3150
  getResponsiveThreshold() {
3034
3151
  // Check if brand logo is enabled and present
3035
3152
  const hasLogo = this.options.brandLogoEnabled && this.options.brandLogoUrl;
@@ -3038,7 +3155,7 @@ getResponsiveThreshold() {
3038
3155
  return hasLogo ? 650 : 550;
3039
3156
  }
3040
3157
 
3041
- /* NEW: Check if screen is under dynamic threshold */
3158
+ /* Check if screen is under dynamic threshold */
3042
3159
  checkScreenSize() {
3043
3160
  const threshold = this.getResponsiveThreshold();
3044
3161
  const newIsSmallScreen = window.innerWidth <= threshold;
@@ -3048,12 +3165,12 @@ checkScreenSize() {
3048
3165
  this.updateSettingsMenuVisibility();
3049
3166
 
3050
3167
  if (this.options.debug) {
3051
- console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px threshold (logo: ${this.options.brandLogoEnabled}), small: ${this.isSmallScreen}`);
3168
+ console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px (threshold), logo: ${this.options.brandLogoEnabled}, small: ${this.isSmallScreen}`);
3052
3169
  }
3053
3170
  }
3054
3171
  }
3055
3172
 
3056
- /* NEW: Update settings menu visibility based on screen size */
3173
+ /* Update settings menu visibility based on screen size */
3057
3174
  updateSettingsMenuVisibility() {
3058
3175
  const settingsControl = this.controls?.querySelector('.settings-control');
3059
3176
  if (!settingsControl) return;
@@ -3093,7 +3210,7 @@ updateSettingsMenuVisibility() {
3093
3210
  }
3094
3211
  }
3095
3212
 
3096
- /* NEW: Populate settings menu with controls */
3213
+ /* Populate settings menu with controls */
3097
3214
  populateSettingsMenu() {
3098
3215
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3099
3216
  if (!settingsMenu) return;
@@ -3155,7 +3272,7 @@ populateSettingsMenu() {
3155
3272
  settingsMenu.innerHTML = menuHTML;
3156
3273
  }
3157
3274
 
3158
- /* NEW: Bind settings menu events */
3275
+ /* Bind settings menu events */
3159
3276
  bindSettingsMenuEvents() {
3160
3277
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3161
3278
  if (!settingsMenu) return;
@@ -5784,9 +5901,19 @@ initializeWatermark() {
5784
5901
  this.container.appendChild(watermark);
5785
5902
  }
5786
5903
 
5904
+ // Store reference to watermark element
5787
5905
  // Store reference to watermark element
5788
5906
  this.watermarkElement = watermark;
5789
5907
 
5908
+ // Set initial position
5909
+ this.updateWatermarkPosition();
5910
+
5911
+ // Update position on window resize
5912
+ this.watermarkResizeHandler = () => {
5913
+ this.updateWatermarkPosition();
5914
+ };
5915
+ window.addEventListener('resize', this.watermarkResizeHandler);
5916
+
5790
5917
  if (this.options.debug) {
5791
5918
  console.log('🏷️ Watermark created:', {
5792
5919
  url: this.options.watermarkUrl,
@@ -5805,6 +5932,7 @@ initializeWatermark() {
5805
5932
  * @param {string} position - Position of watermark (topleft, topright, bottomleft, bottomright)
5806
5933
  * @param {string} title - Optional tooltip title for the watermark
5807
5934
  */
5935
+
5808
5936
  setWatermark(url, link = '', position = 'bottomright', title = '') {
5809
5937
  // Update options
5810
5938
  this.options.watermarkUrl = url;
@@ -5835,6 +5963,12 @@ removeWatermark() {
5835
5963
  this.watermarkElement = null;
5836
5964
  }
5837
5965
 
5966
+ // Remove resize listener
5967
+ if (this.watermarkResizeHandler) {
5968
+ window.removeEventListener('resize', this.watermarkResizeHandler);
5969
+ this.watermarkResizeHandler = null;
5970
+ }
5971
+
5838
5972
  this.options.watermarkUrl = '';
5839
5973
  this.options.watermarkLink = '';
5840
5974
  this.options.watermarkPosition = 'bottomright';
@@ -5849,6 +5983,7 @@ removeWatermark() {
5849
5983
  * Update watermark position
5850
5984
  * @param {string} position - New position (topleft, topright, bottomleft, bottomright)
5851
5985
  */
5986
+
5852
5987
  setWatermarkPosition(position) {
5853
5988
  if (!['topleft', 'topright', 'bottomleft', 'bottomright'].includes(position)) {
5854
5989
  if (this.options.debug) console.warn('🏷️ Invalid watermark position:', position);
@@ -5875,6 +6010,39 @@ setWatermarkPosition(position) {
5875
6010
  return this;
5876
6011
  }
5877
6012
 
6013
+ /**
6014
+ * Update watermark position based on current controlbar height
6015
+ * Called during window resize to keep watermark above controlbar
6016
+ */
6017
+ updateWatermarkPosition() {
6018
+ if (!this.watermarkElement) return;
6019
+ if (!this.controls) return;
6020
+
6021
+ const position = this.options.watermarkPosition || 'bottomright';
6022
+
6023
+ // Only update bottom positions (top positions don't need adjustment)
6024
+ if (position === 'bottomleft' || position === 'bottomright') {
6025
+ const controlsHeight = this.controls.offsetHeight;
6026
+ const spacing = 15; // Same spacing used in CSS
6027
+ const bottomValue = controlsHeight + spacing;
6028
+
6029
+ // Check if controls are visible
6030
+ const hasControls = this.container.classList.contains('has-controls');
6031
+
6032
+ if (hasControls || !this.options.hideWatermark) {
6033
+ // Position above controlbar
6034
+ this.watermarkElement.style.bottom = `${bottomValue}px`;
6035
+ } else {
6036
+ // Position at bottom corner when controls hidden
6037
+ this.watermarkElement.style.bottom = '15px';
6038
+ }
6039
+
6040
+ if (this.options.debug) {
6041
+ console.log(`🏷️ Watermark position updated: bottom ${this.watermarkElement.style.bottom}`);
6042
+ }
6043
+ }
6044
+ }
6045
+
5878
6046
  /**
5879
6047
  * Set whether watermark should hide with controls
5880
6048
  * @param {boolean} hide - True to hide watermark with controls, false to keep always visible
@@ -6631,15 +6799,29 @@ getBufferedTime() {
6631
6799
  this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
6632
6800
  }
6633
6801
 
6634
- updateTimeDisplay() {
6635
- if (this.currentTimeEl && this.video) {
6636
- this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
6637
- }
6802
+ updateTimeDisplay() {
6803
+ // update current time
6804
+ if (this.currentTimeEl && this.video) {
6805
+ this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
6806
+ }
6807
+
6808
+ // update duration or show badge if encoding
6809
+ if (this.durationEl && this.video) {
6810
+ const duration = this.video.duration;
6638
6811
 
6639
- if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
6640
- this.durationEl.textContent = this.formatTime(this.video.duration);
6812
+ // check if duration is valid
6813
+ if (!duration || isNaN(duration) || !isFinite(duration)) {
6814
+ // Video in encoding - show badge instead of duration
6815
+ this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
6816
+ this.durationEl.classList.add('encoding-state');
6817
+ } else {
6818
+ // valid duration - show normal
6819
+ this.durationEl.textContent = this.formatTime(duration);
6820
+ this.durationEl.classList.remove('encoding-state');
6641
6821
  }
6642
6822
  }
6823
+ }
6824
+
6643
6825
 
6644
6826
  formatTime(seconds) {
6645
6827
  if (isNaN(seconds) || seconds < 0) return '0:00';