unified-video-framework 1.4.448 → 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.
- package/package.json +1 -1
- package/packages/core/dist/version.d.ts +1 -1
- package/packages/core/dist/version.js +1 -1
- package/packages/core/src/version.ts +1 -1
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +72 -3
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +82 -3
|
@@ -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');
|
|
@@ -5804,7 +5856,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
5804
5856
|
z-index: 25;
|
|
5805
5857
|
opacity: 0;
|
|
5806
5858
|
transform: translateX(-50%) translateY(8px);
|
|
5807
|
-
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;
|
|
5808
5861
|
}
|
|
5809
5862
|
|
|
5810
5863
|
.uvf-thumbnail-preview.visible {
|
|
@@ -5820,6 +5873,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
5820
5873
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
|
5821
5874
|
backdrop-filter: blur(12px);
|
|
5822
5875
|
-webkit-backdrop-filter: blur(12px);
|
|
5876
|
+
/* Prevent container size changes */
|
|
5877
|
+
flex-shrink: 0;
|
|
5823
5878
|
}
|
|
5824
5879
|
|
|
5825
5880
|
.uvf-thumbnail-preview-image-wrapper {
|
|
@@ -5829,6 +5884,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
5829
5884
|
border-radius: 6px;
|
|
5830
5885
|
overflow: hidden;
|
|
5831
5886
|
background: rgba(30, 30, 30, 0.95);
|
|
5887
|
+
/* Prevent layout shifts during image loading */
|
|
5888
|
+
flex-shrink: 0;
|
|
5832
5889
|
}
|
|
5833
5890
|
|
|
5834
5891
|
.uvf-thumbnail-preview-image {
|
|
@@ -5838,6 +5895,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
5838
5895
|
display: block;
|
|
5839
5896
|
opacity: 0;
|
|
5840
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;
|
|
5841
5902
|
}
|
|
5842
5903
|
|
|
5843
5904
|
.uvf-thumbnail-preview-image.loaded {
|
|
@@ -8588,7 +8649,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
8588
8649
|
opacity: 0 !important;
|
|
8589
8650
|
pointer-events: none !important;
|
|
8590
8651
|
}
|
|
8591
|
-
|
|
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
|
+
|
|
8592
8671
|
/* Ultra-wide screens */
|
|
8593
8672
|
@media screen and (min-width: 1440px) {
|
|
8594
8673
|
.uvf-video-title {
|