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.
- 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 +111 -4
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +125 -4
|
@@ -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');
|