unified-video-framework 1.4.447 → 1.4.449

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.
@@ -1725,8 +1725,44 @@ export class WebPlayer extends BasePlayer {
1725
1725
  existingPlayer.remove();
1726
1726
  }
1727
1727
 
1728
+ // Remove existing overlay if any
1729
+ const existingOverlay = container.querySelector('.uvf-youtube-overlay');
1730
+ if (existingOverlay) {
1731
+ existingOverlay.remove();
1732
+ }
1733
+
1728
1734
  container.appendChild(iframeContainer);
1729
1735
 
1736
+ // Create transparent overlay to block YouTube's native controls (when custom controls are enabled)
1737
+ if (!this.youtubeNativeControls) {
1738
+ const overlay = document.createElement('div');
1739
+ overlay.className = 'uvf-youtube-overlay';
1740
+ // Styles are handled in CSS, just set essentials here
1741
+ overlay.style.cssText = `
1742
+ position: absolute;
1743
+ top: 0;
1744
+ left: 0;
1745
+ width: 100%;
1746
+ z-index: 2;
1747
+ `;
1748
+
1749
+ // Handle click to toggle play/pause
1750
+ overlay.addEventListener('click', () => {
1751
+ this.togglePlayPause();
1752
+ });
1753
+
1754
+ // Handle double-click for fullscreen
1755
+ overlay.addEventListener('dblclick', () => {
1756
+ if (this.isFullscreen()) {
1757
+ this.exitFullscreen();
1758
+ } else {
1759
+ this.enterFullscreen();
1760
+ }
1761
+ });
1762
+
1763
+ container.appendChild(overlay);
1764
+ }
1765
+
1730
1766
  // Load YouTube IFrame API if not loaded
1731
1767
  if (!window.YT) {
1732
1768
  await this.loadYouTubeAPI();
@@ -1802,6 +1838,10 @@ export class WebPlayer extends BasePlayer {
1802
1838
  this.youtubePlayerReady = true;
1803
1839
  this.debugLog('YouTube player ready');
1804
1840
 
1841
+ // Hide loading spinner when YouTube player is ready
1842
+ const loading = document.getElementById('uvf-loading');
1843
+ if (loading) loading.classList.remove('active');
1844
+
1805
1845
  // If YouTube native controls are enabled, hide custom controls
1806
1846
  if (this.youtubeNativeControls && this.playerWrapper) {
1807
1847
  this.debugLog('[YouTube] Native controls enabled - hiding custom controls');
@@ -1851,6 +1891,10 @@ export class WebPlayer extends BasePlayer {
1851
1891
  const state = event.data;
1852
1892
 
1853
1893
  switch (state) {
1894
+ case window.YT.PlayerState.UNSTARTED: // -1: Video is loaded but not started
1895
+ this.updateYouTubeUI('unstarted');
1896
+ break;
1897
+
1854
1898
  case window.YT.PlayerState.PLAYING:
1855
1899
  this.state.isPlaying = true;
1856
1900
  this.state.isPaused = false;
@@ -1903,12 +1947,20 @@ export class WebPlayer extends BasePlayer {
1903
1947
  const playIcon = document.getElementById('uvf-play-icon');
1904
1948
  const pauseIcon = document.getElementById('uvf-pause-icon');
1905
1949
  const centerPlay = document.getElementById('uvf-center-play');
1950
+ const loading = document.getElementById('uvf-loading');
1951
+
1952
+ // Handle loading spinner for YouTube
1953
+ if (state === 'buffering') {
1954
+ if (loading) loading.classList.add('active');
1955
+ } else if (state === 'playing' || state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
1956
+ if (loading) loading.classList.remove('active');
1957
+ }
1906
1958
 
1907
1959
  if (state === 'playing' || state === 'buffering') {
1908
1960
  if (playIcon) playIcon.style.display = 'none';
1909
1961
  if (pauseIcon) pauseIcon.style.display = 'block';
1910
1962
  if (centerPlay) centerPlay.classList.add('hidden');
1911
- } else if (state === 'paused' || state === 'cued' || state === 'ended') {
1963
+ } else if (state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
1912
1964
  if (playIcon) playIcon.style.display = 'block';
1913
1965
  if (pauseIcon) pauseIcon.style.display = 'none';
1914
1966
  if (centerPlay) centerPlay.classList.remove('hidden');
@@ -5705,7 +5757,37 @@ export class WebPlayer extends BasePlayer {
5705
5757
  top: 2px; /* Center on the 4px hover progress bar (2px from top) */
5706
5758
  transform: translate(-50%, -50%) scale(1);
5707
5759
  }
5708
-
5760
+
5761
+ /* Prevent text selection during seekbar drag */
5762
+ .uvf-player-wrapper.seeking {
5763
+ user-select: none;
5764
+ -webkit-user-select: none;
5765
+ -moz-user-select: none;
5766
+ -ms-user-select: none;
5767
+ }
5768
+
5769
+ /* Maintain expanded seekbar state during drag (same as hover) */
5770
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-bar {
5771
+ height: 4px;
5772
+ background: rgba(255, 255, 255, 0.2);
5773
+ border-radius: 6px;
5774
+ transform: scaleY(1.1);
5775
+ }
5776
+
5777
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-handle {
5778
+ opacity: 1;
5779
+ top: 2px;
5780
+ transform: translate(-50%, -50%) scale(1);
5781
+ }
5782
+
5783
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-buffered {
5784
+ border-radius: 6px;
5785
+ }
5786
+
5787
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-filled {
5788
+ border-radius: 6px;
5789
+ }
5790
+
5709
5791
  .uvf-progress-handle:hover {
5710
5792
  transform: translate(-50%, -50%) scale(1.2);
5711
5793
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.4);
@@ -5774,7 +5856,8 @@ export class WebPlayer extends BasePlayer {
5774
5856
  z-index: 25;
5775
5857
  opacity: 0;
5776
5858
  transform: translateX(-50%) translateY(8px);
5777
- transition: opacity 0.15s ease, transform 0.15s ease;
5859
+ transition: opacity 0.15s ease, transform 0.15s ease, left 0.05s ease-out;
5860
+ will-change: left, transform, opacity;
5778
5861
  }
5779
5862
 
5780
5863
  .uvf-thumbnail-preview.visible {
@@ -5790,6 +5873,8 @@ export class WebPlayer extends BasePlayer {
5790
5873
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
5791
5874
  backdrop-filter: blur(12px);
5792
5875
  -webkit-backdrop-filter: blur(12px);
5876
+ /* Prevent container size changes */
5877
+ flex-shrink: 0;
5793
5878
  }
5794
5879
 
5795
5880
  .uvf-thumbnail-preview-image-wrapper {
@@ -5799,6 +5884,8 @@ export class WebPlayer extends BasePlayer {
5799
5884
  border-radius: 6px;
5800
5885
  overflow: hidden;
5801
5886
  background: rgba(30, 30, 30, 0.95);
5887
+ /* Prevent layout shifts during image loading */
5888
+ flex-shrink: 0;
5802
5889
  }
5803
5890
 
5804
5891
  .uvf-thumbnail-preview-image {
@@ -5808,6 +5895,10 @@ export class WebPlayer extends BasePlayer {
5808
5895
  display: block;
5809
5896
  opacity: 0;
5810
5897
  transition: opacity 0.2s ease;
5898
+ /* Prevent reflow/repaint jitter during load */
5899
+ will-change: opacity;
5900
+ backface-visibility: hidden;
5901
+ -webkit-backface-visibility: hidden;
5811
5902
  }
5812
5903
 
5813
5904
  .uvf-thumbnail-preview-image.loaded {
@@ -8558,7 +8649,25 @@ export class WebPlayer extends BasePlayer {
8558
8649
  opacity: 0 !important;
8559
8650
  pointer-events: none !important;
8560
8651
  }
8561
-
8652
+
8653
+ /* YouTube overlay to block native controls */
8654
+ .uvf-youtube-overlay {
8655
+ position: absolute;
8656
+ top: 0;
8657
+ left: 0;
8658
+ width: 100%;
8659
+ height: calc(100% - 60px); /* Leave bottom area for our controls */
8660
+ z-index: 2;
8661
+ cursor: pointer;
8662
+ background: transparent;
8663
+ pointer-events: auto;
8664
+ }
8665
+
8666
+ /* When controls are hidden, cover entire video */
8667
+ .uvf-player-wrapper:not(:hover) .uvf-youtube-overlay {
8668
+ height: 100%;
8669
+ }
8670
+
8562
8671
  /* Ultra-wide screens */
8563
8672
  @media screen and (min-width: 1440px) {
8564
8673
  .uvf-video-title {
@@ -9376,6 +9485,9 @@ export class WebPlayer extends BasePlayer {
9376
9485
  progressBar?.addEventListener('mousedown', (e) => {
9377
9486
  this.isDragging = true;
9378
9487
  this.showTimeTooltip = true;
9488
+ // Maintain seekbar expanded state and prevent text selection during drag
9489
+ progressBar.classList.add('dragging');
9490
+ this.playerWrapper?.classList.add('seeking');
9379
9491
  this.seekToPosition(e as MouseEvent);
9380
9492
  this.updateTimeTooltip(e as MouseEvent);
9381
9493
  });
@@ -9405,6 +9517,9 @@ export class WebPlayer extends BasePlayer {
9405
9517
  progressBar?.addEventListener('touchstart', (e) => {
9406
9518
  e.preventDefault(); // Prevent scrolling
9407
9519
  this.isDragging = true;
9520
+ // Maintain seekbar expanded state and prevent text selection during drag
9521
+ progressBar.classList.add('dragging');
9522
+ this.playerWrapper?.classList.add('seeking');
9408
9523
  const touch = e.touches[0];
9409
9524
  const mouseEvent = new MouseEvent('mousedown', {
9410
9525
  clientX: touch.clientX,
@@ -9451,6 +9566,9 @@ export class WebPlayer extends BasePlayer {
9451
9566
 
9452
9567
  if (this.isDragging) {
9453
9568
  this.isDragging = false;
9569
+ // Remove dragging classes
9570
+ progressBar?.classList.remove('dragging');
9571
+ this.playerWrapper?.classList.remove('seeking');
9454
9572
  // Remove dragging class from handle
9455
9573
  const handle = document.getElementById('uvf-progress-handle');
9456
9574
  handle?.classList.remove('dragging');
@@ -9466,6 +9584,9 @@ export class WebPlayer extends BasePlayer {
9466
9584
  document.addEventListener('touchend', () => {
9467
9585
  if (this.isDragging) {
9468
9586
  this.isDragging = false;
9587
+ // Remove dragging classes
9588
+ progressBar?.classList.remove('dragging');
9589
+ this.playerWrapper?.classList.remove('seeking');
9469
9590
  // Remove dragging class from handle
9470
9591
  const handle = document.getElementById('uvf-progress-handle');
9471
9592
  handle?.classList.remove('dragging');