unified-video-framework 1.4.117 → 1.4.119

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.
@@ -739,26 +739,87 @@ export class WebPlayer extends BasePlayer {
739
739
  this.debugWarn('Failed to exit fullscreen:', error.message);
740
740
  }
741
741
  }
742
- async enterPictureInPicture() {
742
+ isPictureInPictureSupported() {
743
743
  if (!this.video)
744
- return;
744
+ return false;
745
+ const hasStandardPiP = 'requestPictureInPicture' in this.video &&
746
+ 'pictureInPictureEnabled' in document;
747
+ const hasSafariPiP = 'webkitSupportsPresentationMode' in this.video &&
748
+ typeof this.video.webkitSupportsPresentationMode === 'function' &&
749
+ this.video.webkitSupportsPresentationMode('picture-in-picture');
750
+ const userAgent = navigator.userAgent.toLowerCase();
751
+ const isIOS = /iphone|ipad|ipod/.test(userAgent);
752
+ const isAndroid = /android/.test(userAgent);
753
+ const isChrome = /chrome/.test(userAgent) && !/edge/.test(userAgent);
754
+ const isSafari = /safari/.test(userAgent) && !/chrome/.test(userAgent);
755
+ const isFirefox = /firefox/.test(userAgent);
756
+ const iosSupport = isIOS && isSafari && hasSafariPiP;
757
+ const androidChromeSupport = isAndroid && isChrome && hasStandardPiP;
758
+ const firefoxSupport = isFirefox && hasStandardPiP && !this.isMobileDevice();
759
+ this.debugLog('PiP Support Detection:', {
760
+ hasStandardPiP,
761
+ hasSafariPiP,
762
+ isIOS,
763
+ isAndroid,
764
+ isChrome,
765
+ isSafari,
766
+ isFirefox,
767
+ iosSupport,
768
+ androidChromeSupport,
769
+ firefoxSupport,
770
+ overall: hasStandardPiP || iosSupport || androidChromeSupport || firefoxSupport
771
+ });
772
+ return hasStandardPiP || iosSupport || androidChromeSupport || firefoxSupport;
773
+ }
774
+ isMobileDevice() {
775
+ const userAgent = navigator.userAgent.toLowerCase();
776
+ return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/.test(userAgent) ||
777
+ (!!(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) && /macintosh/.test(userAgent));
778
+ }
779
+ async enterPictureInPicture() {
780
+ if (!this.video) {
781
+ throw new Error('Video element not available');
782
+ }
783
+ if (!this.isPictureInPictureSupported()) {
784
+ throw new Error('Picture-in-Picture not supported on this device/browser');
785
+ }
745
786
  try {
746
- if (this.video.requestPictureInPicture) {
787
+ if ('requestPictureInPicture' in this.video) {
747
788
  await this.video.requestPictureInPicture();
789
+ this.debugLog('PiP entered using standard API');
790
+ return;
748
791
  }
749
- else {
750
- throw new Error('Picture-in-Picture not supported');
792
+ if ('webkitSetPresentationMode' in this.video) {
793
+ this.video.webkitSetPresentationMode('picture-in-picture');
794
+ this.debugLog('PiP entered using WebKit API');
795
+ return;
751
796
  }
797
+ throw new Error('No supported PiP API available');
752
798
  }
753
799
  catch (error) {
754
- console.error('Failed to enter PiP:', error);
800
+ this.debugError('Failed to enter PiP:', error.message);
755
801
  throw error;
756
802
  }
757
803
  }
758
804
  async exitPictureInPicture() {
759
805
  try {
760
- if (document.exitPictureInPicture) {
806
+ const inStandardPiP = document.pictureInPictureElement;
807
+ const inWebkitPiP = this.video &&
808
+ 'webkitPresentationMode' in this.video &&
809
+ this.video.webkitPresentationMode === 'picture-in-picture';
810
+ if (!inStandardPiP && !inWebkitPiP) {
811
+ this.debugLog('Not currently in PiP mode');
812
+ return;
813
+ }
814
+ if (inStandardPiP && 'exitPictureInPicture' in document) {
761
815
  await document.exitPictureInPicture();
816
+ this.debugLog('PiP exited using standard API');
817
+ return;
818
+ }
819
+ if (inWebkitPiP && this.video && 'webkitSetPresentationMode' in this.video) {
820
+ this.video.webkitSetPresentationMode('inline');
821
+ this.debugLog('PiP exited using WebKit API');
822
+ return;
762
823
  }
763
824
  }
764
825
  catch (error) {
@@ -1459,19 +1520,51 @@ export class WebPlayer extends BasePlayer {
1459
1520
  .uvf-video-container {
1460
1521
  position: relative;
1461
1522
  width: 100%;
1462
- // aspect-ratio: 16 / 9;
1523
+ aspect-ratio: 16 / 9;
1463
1524
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
1464
1525
  overflow: hidden;
1526
+ display: flex;
1527
+ align-items: center;
1528
+ justify-content: center;
1465
1529
  }
1466
1530
 
1467
1531
  .uvf-video {
1468
1532
  position: absolute;
1469
- top: 0;
1470
- left: 0;
1471
- width: 100%;
1472
- height: 100%;
1533
+ top: 50%;
1534
+ left: 50%;
1535
+ transform: translate(-50%, -50%);
1536
+ max-width: 100%;
1537
+ max-height: 100%;
1538
+ width: auto;
1539
+ height: auto;
1473
1540
  background: #000;
1474
1541
  object-fit: contain;
1542
+ /* Ensure proper centering and scaling */
1543
+ object-position: center;
1544
+ }
1545
+
1546
+ /* Mobile-specific video centering improvements */
1547
+ @media screen and (max-width: 767px) {
1548
+ .uvf-video {
1549
+ /* Force full width/height on mobile for better centering */
1550
+ width: 100%;
1551
+ height: 100%;
1552
+ top: 0;
1553
+ left: 0;
1554
+ transform: none;
1555
+ object-fit: contain;
1556
+ object-position: center;
1557
+ }
1558
+
1559
+ .uvf-video-container {
1560
+ /* Remove aspect ratio constraint on mobile for full height usage */
1561
+ aspect-ratio: unset;
1562
+ min-height: 100%;
1563
+ height: 100%;
1564
+ display: flex;
1565
+ align-items: center;
1566
+ justify-content: center;
1567
+ }
1475
1568
  }
1476
1569
 
1477
1570
  .uvf-watermark-layer {
@@ -1658,6 +1751,7 @@ export class WebPlayer extends BasePlayer {
1658
1751
  width: 100%;
1659
1752
  position: relative;
1660
1753
  cursor: pointer;
1754
+ padding: 16px 0;
1661
1755
  overflow: visible;
1662
1756
  }
1663
1757
 
@@ -1771,105 +1865,10 @@ export class WebPlayer extends BasePlayer {
1771
1865
 
1772
1866
  /* Mobile responsive design with enhanced touch targets */
1773
1867
  @media (max-width: 768px) {
1774
- .uvf-player-wrapper {
1775
- position: relative;
1776
- width: 100% !important;
1777
- height: auto !important;
1778
- min-height: 200px;
1868
+ .uvf-progress-bar-wrapper {
1869
+ padding: 20px 0; /* Larger touch area */
1779
1870
  }
1780
1871
 
1781
- .uvf-video-container {
1782
- width: 100% !important;
1783
- height: auto !important;
1784
- aspect-ratio: 16/9;
1785
- position: relative;
1786
- }
1787
-
1788
- .uvf-video {
1789
- width: 100% !important;
1790
- height: 100% !important;
1791
- object-fit: contain;
1792
- }
1793
-
1794
- /* Fix controls bar positioning */
1795
- .uvf-controls-bar {
1796
- position: absolute;
1797
- bottom: 0;
1798
- left: 0;
1799
- right: 0;
1800
- padding: 8px 12px !important;
1801
- padding-bottom: max(8px, env(safe-area-inset-bottom)) !important;
1802
- background: linear-gradient(to top,
1803
- rgba(0,0,0,0.9) 0%,
1804
- rgba(0,0,0,0.7) 70%,
1805
- transparent 100%) !important;
1806
- z-index: 1000;
1807
- }
1808
-
1809
- /* Ensure controls are properly sized */
1810
- .uvf-controls-row {
1811
- display: flex;
1812
- align-items: center;
1813
- gap: 8px;
1814
- flex-wrap: nowrap;
1815
- min-height: 44px; /* Minimum touch target size */
1816
- }
1817
-
1818
- /* Touch-friendly button sizes */
1819
- .uvf-control-btn {
1820
- width: 44px !important;
1821
- height: 44px !important;
1822
- min-width: 44px !important;
1823
- min-height: 44px !important;
1824
- border-radius: 22px !important;
1825
- }
1826
-
1827
- .uvf-control-btn.play-pause {
1828
- width: 52px !important;
1829
- height: 52px !important;
1830
- min-width: 52px !important;
1831
- min-height: 52px !important;
1832
- }
1833
-
1834
- /* Progress bar adjustments */
1835
- .uvf-progress-section {
1836
- margin-bottom: 12px !important;
1837
- padding: 0 8px;
1838
- }
1839
-
1840
- .uvf-progress-bar {
1841
- height: 4px !important;
1842
- margin-bottom: 8px;
1843
- }
1844
-
1845
- /* Time display adjustments */
1846
- .uvf-time-display {
1847
- font-size: 12px !important;
1848
- min-width: 90px !important;
1849
- padding: 4px 8px !important;
1850
- background: rgba(0,0,0,0.5);
1851
- border-radius: 12px;
1852
- margin: 0 4px;
1853
- }
1854
-
1855
- /* Right controls spacing */
1856
- .uvf-right-controls {
1857
- gap: 6px !important;
1858
- margin-left: auto;
1859
- }
1860
-
1861
- /* Hide non-essential elements on mobile */
1862
- .uvf-quality-badge {
1863
- display: none !important;
1864
- }
1865
-
1866
- /* Ensure settings menu is accessible */
1867
- .uvf-settings-menu {
1868
- bottom: 60px !important;
1869
- right: 12px !important;
1870
- max-height: 60vh !important;
1871
- min-width: 160px !important;
1872
- }
1873
1872
  .uvf-progress-bar {
1874
1873
  height: 3px; /* Slightly thicker on mobile */
1875
1874
  }
@@ -1877,31 +1876,7 @@ export class WebPlayer extends BasePlayer {
1877
1876
  .uvf-progress-bar-wrapper:hover .uvf-progress-bar {
1878
1877
  height: 5px;
1879
1878
  }
1880
- }
1881
-
1882
- /* Mobile Landscape */
1883
- @media screen and (max-width: 767px) and (orientation: landscape) {
1884
- .uvf-controls-bar {
1885
- padding: 6px 10px !important;
1886
- padding-bottom: max(6px, env(safe-area-inset-bottom)) !important;
1887
- }
1888
-
1889
- .uvf-control-btn {
1890
- width: 40px !important;
1891
- height: 40px !important;
1892
- min-width: 40px !important;
1893
- min-height: 40px !important;
1894
- }
1895
1879
 
1896
- .uvf-control-btn.play-pause {
1897
- width: 46px !important;
1898
- height: 46px !important;
1899
- }
1900
-
1901
- .uvf-time-display {
1902
- font-size: 11px !important;
1903
- min-width: 80px !important;
1904
- }
1905
1880
  }
1906
1881
 
1907
1882
  /* Controls Row */
@@ -2631,28 +2606,276 @@ export class WebPlayer extends BasePlayer {
2631
2606
  }
2632
2607
  }
2633
2608
 
2609
+ /* Safe Area Variables - Support for modern mobile devices */
2610
+ :root {
2611
+ /* iOS Safe Area Fallbacks */
2612
+ --uvf-safe-area-top: env(safe-area-inset-top, 0px);
2613
+ --uvf-safe-area-right: env(safe-area-inset-right, 0px);
2614
+ --uvf-safe-area-bottom: env(safe-area-inset-bottom, 0px);
2615
+ --uvf-safe-area-left: env(safe-area-inset-left, 0px);
2616
+
2617
+ /* Dynamic Viewport Support */
2618
+ --uvf-dvh: 1dvh;
2619
+ --uvf-svh: 1svh;
2620
+ --uvf-lvh: 1lvh;
2621
+ }
2622
+
2623
+ /* Cross-Browser Mobile Viewport Fixes */
2624
+
2625
+ /* Modern browsers with dynamic viewport support */
2626
+ @supports (height: 100dvh) {
2627
+ .uvf-player-wrapper,
2628
+ .uvf-video-container {
2629
+ height: 100dvh;
2630
+ }
2631
+
2632
+ .uvf-responsive-container {
2633
+ height: 100dvh;
2634
+ }
2635
+ }
2636
+
2637
+ /* iOS Safari specific fixes - address bar handling */
2638
+ @supports (-webkit-appearance: none) {
2639
+ .uvf-player-wrapper.uvf-fullscreen,
2640
+ .uvf-video-container.uvf-fullscreen {
2641
+ height: -webkit-fill-available;
2642
+ min-height: -webkit-fill-available;
2643
+ }
2644
+
2645
+ /* Handle iOS Safari's dynamic address bar */
2646
+ @media screen and (max-width: 767px) {
2647
+ .uvf-responsive-container {
2648
+ height: -webkit-fill-available;
2649
+ min-height: 100vh;
2650
+ }
2651
+
2652
+ .uvf-player-wrapper {
2653
+ height: -webkit-fill-available;
2654
+ min-height: 100vh;
2655
+ }
2656
+ }
2657
+ }
2658
+
2659
+ /* Android Chrome specific fixes */
2660
+ @supports (display: -webkit-box) {
2661
+ .uvf-responsive-container {
2662
+ min-height: 100vh;
2663
+ }
2664
+
2665
+ /* Fix for Android Chrome's address bar behavior */
2666
+ @media screen and (max-width: 767px) {
2667
+ .uvf-video-container {
2668
+ min-height: calc(100vh - 56px); /* Chrome mobile address bar height */
2669
+ }
2670
+ }
2671
+ }
2672
+
2673
+ /* Samsung Internet Browser fixes */
2674
+ @media screen and (-webkit-min-device-pixel-ratio: 1) {
2675
+ @media screen and (max-width: 767px) {
2676
+ .uvf-responsive-container {
2677
+ position: fixed;
2678
+ top: 0;
2679
+ left: 0;
2680
+ width: 100vw;
2681
+ height: 100vh;
2682
+ }
2683
+ }
2684
+ }
2685
+
2686
+ /* Universal mobile fixes for all browsers */
2687
+ @media screen and (max-width: 767px) {
2688
+ html, body {
2689
+ overflow-x: hidden;
2690
+ /* Prevent iOS Safari address bar bounce */
2691
+ position: fixed;
2692
+ height: 100%;
2693
+ width: 100%;
2694
+ }
2695
+
2696
+ .uvf-player-wrapper {
2697
+ /* Prevent scroll bounce on iOS */
2698
+ -webkit-overflow-scrolling: touch;
2699
+ overflow: hidden;
2700
+
2701
+ /* Prevent zoom on double tap */
2702
+ touch-action: manipulation;
2703
+
2704
+ /* Ensure full viewport usage */
2705
+ position: relative;
2706
+ width: 100vw;
2707
+ height: 100vh;
2708
+
2709
+ /* iOS Safari fix for viewport height */
2710
+ min-height: -webkit-fill-available;
2711
+ }
2712
+
2713
+ .uvf-video-container {
2714
+ /* Full viewport container */
2715
+ width: 100vw;
2716
+ height: 100vh;
2717
+ min-height: -webkit-fill-available;
2718
+ position: relative;
2719
+ overflow: hidden;
2720
+ }
2721
+
2722
+ .uvf-video {
2723
+ /* Prevent video from being selectable */
2724
+ -webkit-user-select: none;
2725
+ -moz-user-select: none;
2726
+ -ms-user-select: none;
2727
+ user-select: none;
2728
+
2729
+ /* Ensure hardware acceleration */
2730
+ -webkit-transform: translateZ(0);
2731
+ transform: translateZ(0);
2732
+
2733
+ /* Full viewport video with proper centering */
2734
+ width: 100%;
2735
+ height: 100%;
2736
+ object-fit: contain;
2737
+ object-position: center center;
2738
+ }
2739
+
2740
+ /* Fix for controls being cut off by virtual keyboard */
2741
+ .uvf-controls-bar {
2742
+ position: fixed !important;
2743
+ bottom: var(--uvf-safe-area-bottom, 0) !important;
2744
+ left: var(--uvf-safe-area-left, 0) !important;
2745
+ right: var(--uvf-safe-area-right, 0) !important;
2746
+ width: auto !important;
2747
+ z-index: 9999;
2748
+ }
2749
+
2750
+ /* Top controls safe area positioning */
2751
+ .uvf-top-controls {
2752
+ position: fixed !important;
2753
+ top: var(--uvf-safe-area-top, 10px) !important;
2754
+ right: var(--uvf-safe-area-right, 10px) !important;
2755
+ z-index: 9999;
2756
+ }
2757
+
2758
+ .uvf-title-bar {
2759
+ position: fixed !important;
2760
+ top: var(--uvf-safe-area-top, 10px) !important;
2761
+ left: var(--uvf-safe-area-left, 10px) !important;
2762
+ right: calc(120px + var(--uvf-safe-area-right, 10px)) !important; /* Leave space for top controls */
2763
+ z-index: 9999;
2764
+ }
2765
+
2766
+ /* Ensure controls stay above virtual keyboards */
2767
+ @supports (bottom: env(keyboard-inset-height)) {
2768
+ .uvf-controls-bar {
2769
+ bottom: max(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0)) !important;
2770
+ }
2771
+ }
2772
+
2773
+ /* Enhanced safe area support for newer devices */
2774
+ @supports (padding: max(0px)) {
2775
+ .uvf-controls-bar {
2776
+ padding-bottom: max(16px, calc(16px + var(--uvf-safe-area-bottom, 0)));
2777
+ padding-left: max(12px, calc(12px + var(--uvf-safe-area-left, 0)));
2778
+ padding-right: max(12px, calc(12px + var(--uvf-safe-area-right, 0)));
2779
+ }
2780
+
2781
+ .uvf-top-controls {
2782
+ top: max(10px, calc(10px + var(--uvf-safe-area-top, 0))) !important;
2783
+ right: max(10px, calc(10px + var(--uvf-safe-area-right, 0))) !important;
2784
+ }
2785
+
2786
+ .uvf-title-bar {
2787
+ top: max(10px, calc(10px + var(--uvf-safe-area-top, 0))) !important;
2788
+ left: max(10px, calc(10px + var(--uvf-safe-area-left, 0))) !important;
2789
+ }
2790
+ }
2791
+ }
2792
+
2793
+ /* Specific fixes for iPhone X series and newer with notches */
2794
+ @media screen and (max-width: 767px) and (orientation: portrait) {
2795
+ @supports (top: env(safe-area-inset-top)) {
2796
+ .uvf-responsive-container,
2797
+ .uvf-player-wrapper,
2798
+ .uvf-video-container {
2799
+ height: 100vh;
2800
+ height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
2801
+ }
2802
+ }
2803
+ }
2804
+
2805
+ /* Landscape orientation fixes for mobile */
2806
+ @media screen and (max-width: 767px) and (orientation: landscape) {
2807
+ html, body {
2808
+ height: 100vh;
2809
+ overflow: hidden;
2810
+ }
2811
+
2812
+ .uvf-responsive-container,
2813
+ .uvf-player-wrapper,
2814
+ .uvf-video-container {
2815
+ height: 100vh;
2816
+ width: 100vw;
2817
+ }
2818
+
2819
+ @supports (height: 100dvh) {
2820
+ .uvf-responsive-container,
2821
+ .uvf-player-wrapper,
2822
+ .uvf-video-container {
2823
+ height: 100dvh;
2824
+ width: 100dvw;
2825
+ }
2826
+ }
2827
+ }
2828
+
2634
2829
  /* Enhanced Responsive Media Queries with UX Best Practices */
2635
- /* Mobile devices (portrait) - Enhanced UX */
2830
+ /* Mobile devices (portrait) - Enhanced UX with Safe Areas */
2636
2831
  @media screen and (max-width: 767px) and (orientation: portrait) {
2637
2832
  .uvf-responsive-container {
2638
2833
  padding: 0;
2639
2834
  width: 100vw !important;
2835
+ height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
2640
2836
  margin: 0;
2837
+ position: relative;
2838
+ overflow: hidden;
2839
+ }
2840
+
2841
+ @supports (height: 100dvh) {
2842
+ .uvf-responsive-container {
2843
+ height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
2844
+ }
2641
2845
  }
2642
2846
 
2643
2847
  .uvf-responsive-container .uvf-player-wrapper {
2644
2848
  width: 100vw !important;
2849
+ height: 100% !important;
2850
+ min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
2851
+ }
2852
+
2853
+ @supports (height: 100dvh) {
2854
+ .uvf-responsive-container .uvf-player-wrapper {
2855
+ min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
2856
+ }
2645
2857
  }
2646
2858
 
2647
2859
  .uvf-responsive-container .uvf-video-container {
2648
2860
  width: 100vw !important;
2649
- // aspect-ratio: unset !important;
2861
+ height: 100% !important;
2862
+ aspect-ratio: unset !important;
2863
+ min-height: inherit;
2650
2864
  }
2651
2865
 
2652
- /* Enhanced mobile controls bar with better spacing */
2866
+ /* Enhanced mobile controls bar with safe area padding */
2653
2867
  .uvf-controls-bar {
2868
+ position: absolute;
2869
+ bottom: 0;
2870
+ left: 0;
2871
+ right: 0;
2654
2872
  padding: 16px 12px;
2873
+ padding-bottom: calc(16px + var(--uvf-safe-area-bottom));
2874
+ padding-left: calc(12px + var(--uvf-safe-area-left));
2875
+ padding-right: calc(12px + var(--uvf-safe-area-right));
2655
2876
  background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
2877
+ box-sizing: border-box;
2878
+ z-index: 1000;
2656
2879
  }
2657
2880
 
2658
2881
  .uvf-progress-section {
@@ -2824,11 +3047,11 @@ export class WebPlayer extends BasePlayer {
2824
3047
  }
2825
3048
  }
2826
3049
 
2827
- /* Enhanced top controls for mobile with proper alignment */
3050
+ /* Enhanced top controls for mobile with safe area support */
2828
3051
  .uvf-top-controls {
2829
3052
  position: absolute;
2830
- top: 12px;
2831
- right: 12px;
3053
+ top: calc(12px + var(--uvf-safe-area-top));
3054
+ right: calc(12px + var(--uvf-safe-area-right));
2832
3055
  display: flex;
2833
3056
  align-items: center;
2834
3057
  gap: 8px;
@@ -2856,9 +3079,12 @@ export class WebPlayer extends BasePlayer {
2856
3079
  display: flex;
2857
3080
  }
2858
3081
 
2859
- /* Enhanced title bar for mobile */
3082
+ /* Enhanced title bar for mobile with safe area support */
2860
3083
  .uvf-title-bar {
2861
3084
  padding: 12px;
3085
+ padding-top: calc(12px + var(--uvf-safe-area-top));
3086
+ padding-left: calc(12px + var(--uvf-safe-area-left));
3087
+ padding-right: calc(12px + var(--uvf-safe-area-right));
2862
3088
  }
2863
3089
 
2864
3090
  .uvf-video-title {
@@ -2985,30 +3211,55 @@ export class WebPlayer extends BasePlayer {
2985
3211
  }
2986
3212
  }
2987
3213
 
2988
- /* Mobile devices (landscape) - Optimized for fullscreen viewing */
3214
+ /* Mobile devices (landscape) - Optimized for fullscreen viewing with safe areas */
2989
3215
  @media screen and (max-width: 767px) and (orientation: landscape) {
2990
3216
  .uvf-responsive-container {
2991
3217
  width: 100vw !important;
2992
- height: 100vh !important;
3218
+ height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
2993
3219
  margin: 0;
2994
3220
  padding: 0;
3221
+ position: relative;
3222
+ overflow: hidden;
3223
+ }
3224
+
3225
+ @supports (height: 100dvh) {
3226
+ .uvf-responsive-container {
3227
+ height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
3228
+ }
2995
3229
  }
2996
3230
 
2997
3231
  .uvf-responsive-container .uvf-player-wrapper {
2998
3232
  width: 100vw !important;
2999
- height: 100vh !important;
3233
+ height: 100% !important;
3234
+ min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
3235
+ }
3236
+
3237
+ @supports (height: 100dvh) {
3238
+ .uvf-responsive-container .uvf-player-wrapper {
3239
+ min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
3240
+ }
3000
3241
  }
3001
3242
 
3002
3243
  .uvf-responsive-container .uvf-video-container {
3003
3244
  width: 100vw !important;
3004
- height: 100vh !important;
3245
+ height: 100% !important;
3005
3246
  aspect-ratio: unset !important;
3247
+ min-height: inherit;
3006
3248
  }
3007
3249
 
3008
- /* Compact controls for landscape */
3250
+ /* Compact controls for landscape with safe area padding */
3009
3251
  .uvf-controls-bar {
3252
+ position: absolute;
3253
+ bottom: 0;
3254
+ left: 0;
3255
+ right: 0;
3010
3256
  padding: 10px 12px;
3257
+ padding-bottom: calc(10px + var(--uvf-safe-area-bottom));
3258
+ padding-left: calc(12px + var(--uvf-safe-area-left));
3259
+ padding-right: calc(12px + var(--uvf-safe-area-right));
3011
3260
  background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
3261
+ box-sizing: border-box;
3262
+ z-index: 1000;
3012
3263
  }
3013
3264
 
3014
3265
  .uvf-progress-section {
@@ -3044,13 +3295,20 @@ export class WebPlayer extends BasePlayer {
3044
3295
  height: 22px;
3045
3296
  }
3046
3297
 
3047
- /* Compact top controls */
3298
+ /* Compact top controls with safe area padding */
3048
3299
  .uvf-top-controls {
3049
- top: 8px;
3050
- right: 12px;
3300
+ top: calc(8px + var(--uvf-safe-area-top));
3301
+ right: calc(12px + var(--uvf-safe-area-right));
3051
3302
  gap: 6px;
3052
3303
  }
3053
3304
 
3305
+ .uvf-title-bar {
3306
+ padding: 8px 12px;
3307
+ padding-top: calc(8px + var(--uvf-safe-area-top));
3308
+ padding-left: calc(12px + var(--uvf-safe-area-left));
3309
+ padding-right: calc(12px + var(--uvf-safe-area-right));
3310
+ }
3311
+
3054
3312
  .uvf-top-btn {
3055
3313
  width: 40px;
3056
3314
  height: 40px;
@@ -3097,17 +3355,7 @@ export class WebPlayer extends BasePlayer {
3097
3355
  height: 16px;
3098
3356
  }
3099
3357
  }
3100
- @media (max-width: 768px) {
3101
- .uvf-video-container {
3102
- aspect-ratio: auto !important;
3103
- height: 100% !important;
3104
- }
3105
- .uvf-controls-bar {
3106
- bottom: 0 !important;
3107
- padding-bottom: 10px !important;
3108
- }
3109
- }
3110
-
3358
+
3111
3359
  /* Tablet devices - Enhanced UX with desktop features */
3112
3360
  @media screen and (min-width: 768px) and (max-width: 1023px) {
3113
3361
  .uvf-controls-bar {
@@ -3888,7 +4136,13 @@ export class WebPlayer extends BasePlayer {
3888
4136
  pipBtn.id = 'uvf-pip-btn';
3889
4137
  pipBtn.title = 'Picture-in-Picture';
3890
4138
  pipBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z"/></svg>';
3891
- rightControls.appendChild(pipBtn);
4139
+ if (this.isPictureInPictureSupported()) {
4140
+ rightControls.appendChild(pipBtn);
4141
+ this.debugLog('PiP button added - support detected');
4142
+ }
4143
+ else {
4144
+ this.debugLog('PiP button not added - no support detected');
4145
+ }
3892
4146
  const fullscreenBtn = document.createElement('button');
3893
4147
  fullscreenBtn.className = 'uvf-control-btn';
3894
4148
  fullscreenBtn.id = 'uvf-fullscreen-btn';
@@ -5001,17 +5255,41 @@ export class WebPlayer extends BasePlayer {
5001
5255
  this.setAutoQuality(true);
5002
5256
  }
5003
5257
  }
5258
+ isPictureInPictureActive() {
5259
+ const inStandardPiP = !!document.pictureInPictureElement;
5260
+ const inWebkitPiP = !!(this.video &&
5261
+ 'webkitPresentationMode' in this.video &&
5262
+ this.video.webkitPresentationMode === 'picture-in-picture');
5263
+ return inStandardPiP || inWebkitPiP;
5264
+ }
5004
5265
  async togglePiP() {
5266
+ if (!this.isPictureInPictureSupported()) {
5267
+ this.showShortcutIndicator('PiP not supported');
5268
+ this.debugWarn('PiP not supported on this device/browser');
5269
+ return;
5270
+ }
5005
5271
  try {
5006
- if (document.pictureInPictureElement) {
5272
+ if (this.isPictureInPictureActive()) {
5007
5273
  await this.exitPictureInPicture();
5274
+ this.showShortcutIndicator('Exit PiP');
5275
+ this.debugLog('PiP deactivated');
5008
5276
  }
5009
5277
  else {
5010
5278
  await this.enterPictureInPicture();
5279
+ this.showShortcutIndicator('Enter PiP');
5280
+ this.debugLog('PiP activated');
5011
5281
  }
5012
5282
  }
5013
5283
  catch (error) {
5014
- console.error('PiP toggle failed:', error);
5284
+ const errorMessage = error.message;
5285
+ this.showShortcutIndicator(`PiP Error: ${errorMessage}`);
5286
+ this.debugError('PiP toggle failed:', errorMessage);
5287
+ if (errorMessage.includes('not supported')) {
5288
+ this.showShortcutIndicator('PiP not supported');
5289
+ }
5290
+ else if (errorMessage.includes('user gesture')) {
5291
+ this.showShortcutIndicator('PiP requires user interaction');
5292
+ }
5015
5293
  }
5016
5294
  }
5017
5295
  setupCastContextSafe() {