unified-video-framework 1.4.448 → 1.4.450

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,12 +1838,19 @@ export class WebPlayer extends BasePlayer {
1802
1838
  this.youtubePlayerReady = true;
1803
1839
  this.debugLog('YouTube player ready');
1804
1840
 
1805
- // If YouTube native controls are enabled, hide custom controls
1806
- if (this.youtubeNativeControls && this.playerWrapper) {
1807
- this.debugLog('[YouTube] Native controls enabled - hiding custom controls');
1841
+ // Hide loading spinner when YouTube player is ready
1842
+ const loading = document.getElementById('uvf-loading');
1843
+ if (loading) loading.classList.remove('active');
1844
+
1845
+ // If YouTube native controls are enabled AND custom controls are disabled, hide custom controls
1846
+ // This allows hybrid mode where both YouTube native controls and custom controls can be shown
1847
+ if (this.youtubeNativeControls && !this.useCustomControls && this.playerWrapper) {
1848
+ this.debugLog('[YouTube] Native controls enabled & custom controls disabled - hiding custom controls');
1808
1849
  this.playerWrapper.classList.add('youtube-native-controls-mode');
1809
1850
  // Also hide center play button and custom controls
1810
1851
  this.hideCustomControls();
1852
+ } else if (this.youtubeNativeControls && this.useCustomControls) {
1853
+ this.debugLog('[YouTube] Hybrid mode - both YouTube native controls and custom controls enabled');
1811
1854
  }
1812
1855
 
1813
1856
  // Set initial volume
@@ -1851,6 +1894,10 @@ export class WebPlayer extends BasePlayer {
1851
1894
  const state = event.data;
1852
1895
 
1853
1896
  switch (state) {
1897
+ case window.YT.PlayerState.UNSTARTED: // -1: Video is loaded but not started
1898
+ this.updateYouTubeUI('unstarted');
1899
+ break;
1900
+
1854
1901
  case window.YT.PlayerState.PLAYING:
1855
1902
  this.state.isPlaying = true;
1856
1903
  this.state.isPaused = false;
@@ -1903,12 +1950,20 @@ export class WebPlayer extends BasePlayer {
1903
1950
  const playIcon = document.getElementById('uvf-play-icon');
1904
1951
  const pauseIcon = document.getElementById('uvf-pause-icon');
1905
1952
  const centerPlay = document.getElementById('uvf-center-play');
1953
+ const loading = document.getElementById('uvf-loading');
1954
+
1955
+ // Handle loading spinner for YouTube
1956
+ if (state === 'buffering') {
1957
+ if (loading) loading.classList.add('active');
1958
+ } else if (state === 'playing' || state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
1959
+ if (loading) loading.classList.remove('active');
1960
+ }
1906
1961
 
1907
1962
  if (state === 'playing' || state === 'buffering') {
1908
1963
  if (playIcon) playIcon.style.display = 'none';
1909
1964
  if (pauseIcon) pauseIcon.style.display = 'block';
1910
1965
  if (centerPlay) centerPlay.classList.add('hidden');
1911
- } else if (state === 'paused' || state === 'cued' || state === 'ended') {
1966
+ } else if (state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
1912
1967
  if (playIcon) playIcon.style.display = 'block';
1913
1968
  if (pauseIcon) pauseIcon.style.display = 'none';
1914
1969
  if (centerPlay) centerPlay.classList.remove('hidden');
@@ -5804,7 +5859,8 @@ export class WebPlayer extends BasePlayer {
5804
5859
  z-index: 25;
5805
5860
  opacity: 0;
5806
5861
  transform: translateX(-50%) translateY(8px);
5807
- transition: opacity 0.15s ease, transform 0.15s ease;
5862
+ transition: opacity 0.15s ease, transform 0.15s ease, left 0.05s ease-out;
5863
+ will-change: left, transform, opacity;
5808
5864
  }
5809
5865
 
5810
5866
  .uvf-thumbnail-preview.visible {
@@ -5820,6 +5876,8 @@ export class WebPlayer extends BasePlayer {
5820
5876
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
5821
5877
  backdrop-filter: blur(12px);
5822
5878
  -webkit-backdrop-filter: blur(12px);
5879
+ /* Prevent container size changes */
5880
+ flex-shrink: 0;
5823
5881
  }
5824
5882
 
5825
5883
  .uvf-thumbnail-preview-image-wrapper {
@@ -5829,6 +5887,8 @@ export class WebPlayer extends BasePlayer {
5829
5887
  border-radius: 6px;
5830
5888
  overflow: hidden;
5831
5889
  background: rgba(30, 30, 30, 0.95);
5890
+ /* Prevent layout shifts during image loading */
5891
+ flex-shrink: 0;
5832
5892
  }
5833
5893
 
5834
5894
  .uvf-thumbnail-preview-image {
@@ -5838,6 +5898,10 @@ export class WebPlayer extends BasePlayer {
5838
5898
  display: block;
5839
5899
  opacity: 0;
5840
5900
  transition: opacity 0.2s ease;
5901
+ /* Prevent reflow/repaint jitter during load */
5902
+ will-change: opacity;
5903
+ backface-visibility: hidden;
5904
+ -webkit-backface-visibility: hidden;
5841
5905
  }
5842
5906
 
5843
5907
  .uvf-thumbnail-preview-image.loaded {
@@ -8588,7 +8652,25 @@ export class WebPlayer extends BasePlayer {
8588
8652
  opacity: 0 !important;
8589
8653
  pointer-events: none !important;
8590
8654
  }
8591
-
8655
+
8656
+ /* YouTube overlay to block native controls */
8657
+ .uvf-youtube-overlay {
8658
+ position: absolute;
8659
+ top: 0;
8660
+ left: 0;
8661
+ width: 100%;
8662
+ height: calc(100% - 60px); /* Leave bottom area for our controls */
8663
+ z-index: 2;
8664
+ cursor: pointer;
8665
+ background: transparent;
8666
+ pointer-events: auto;
8667
+ }
8668
+
8669
+ /* When controls are hidden, cover entire video */
8670
+ .uvf-player-wrapper:not(:hover) .uvf-youtube-overlay {
8671
+ height: 100%;
8672
+ }
8673
+
8592
8674
  /* Ultra-wide screens */
8593
8675
  @media screen and (min-width: 1440px) {
8594
8676
  .uvf-video-title {