unified-video-framework 1.4.209 → 1.4.211
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/BasePlayer.d.ts +6 -5
- package/packages/core/dist/BasePlayer.d.ts.map +1 -1
- package/packages/core/dist/BasePlayer.js.map +1 -1
- package/packages/core/dist/VideoPlayer.js +1 -1
- package/packages/core/dist/VideoPlayer.js.map +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +5 -3
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +26 -1
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/BasePlayer.ts +5 -5
- package/packages/core/src/VideoPlayer.ts +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +6 -3
- package/packages/core/src/interfaces.ts +60 -26
- package/packages/web/dist/WebPlayer.d.ts +8 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +541 -36
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +24 -0
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +8 -0
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.d.ts.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js +0 -8
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay.d.ts.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay.js +0 -8
- package/packages/web/dist/react/components/EPGOverlay.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +632 -41
- package/packages/web/src/react/WebPlayerView.tsx +39 -0
- package/packages/web/src/react/components/EPGOverlay-improved-positioning.tsx +0 -8
- package/packages/web/src/react/components/EPGOverlay.tsx +0 -8
|
@@ -3750,7 +3750,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
3750
3750
|
margin-left: 10px;
|
|
3751
3751
|
}
|
|
3752
3752
|
|
|
3753
|
-
/* Top Bar Container - Contains Title (left) and Controls (right) */
|
|
3753
|
+
/* Top Bar Container - Contains Navigation + Title (left) and Controls (right) */
|
|
3754
3754
|
.uvf-top-bar {
|
|
3755
3755
|
position: absolute;
|
|
3756
3756
|
top: 0;
|
|
@@ -3767,16 +3767,97 @@ export class WebPlayer extends BasePlayer {
|
|
|
3767
3767
|
transition: all 0.3s ease;
|
|
3768
3768
|
}
|
|
3769
3769
|
|
|
3770
|
+
/* Left side container for navigation + title */
|
|
3771
|
+
.uvf-left-side {
|
|
3772
|
+
display: flex;
|
|
3773
|
+
align-items: center;
|
|
3774
|
+
gap: 12px;
|
|
3775
|
+
flex: 1;
|
|
3776
|
+
max-width: 70%;
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
/* Navigation controls container */
|
|
3780
|
+
.uvf-navigation-controls {
|
|
3781
|
+
display: flex;
|
|
3782
|
+
align-items: center;
|
|
3783
|
+
gap: 8px;
|
|
3784
|
+
flex-shrink: 0;
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
/* Navigation button styles */
|
|
3788
|
+
.uvf-nav-btn {
|
|
3789
|
+
width: 40px;
|
|
3790
|
+
height: 40px;
|
|
3791
|
+
min-width: 40px;
|
|
3792
|
+
min-height: 40px;
|
|
3793
|
+
border-radius: 50%;
|
|
3794
|
+
background: rgba(0, 0, 0, 0.6);
|
|
3795
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
3796
|
+
color: white;
|
|
3797
|
+
cursor: pointer;
|
|
3798
|
+
display: flex;
|
|
3799
|
+
align-items: center;
|
|
3800
|
+
justify-content: center;
|
|
3801
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3802
|
+
backdrop-filter: blur(8px);
|
|
3803
|
+
position: relative;
|
|
3804
|
+
overflow: hidden;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
.uvf-nav-btn:hover {
|
|
3808
|
+
background: rgba(255, 255, 255, 0.15);
|
|
3809
|
+
border-color: rgba(255, 255, 255, 0.4);
|
|
3810
|
+
transform: scale(1.05);
|
|
3811
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
.uvf-nav-btn:active {
|
|
3815
|
+
transform: scale(0.95);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
.uvf-nav-btn svg {
|
|
3819
|
+
width: 20px;
|
|
3820
|
+
height: 20px;
|
|
3821
|
+
fill: currentColor;
|
|
3822
|
+
transition: all 0.2s ease;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
.uvf-nav-btn:hover svg {
|
|
3826
|
+
transform: scale(1.1);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
/* Back button specific styles */
|
|
3830
|
+
#uvf-back-btn {
|
|
3831
|
+
background: rgba(0, 0, 0, 0.7);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
#uvf-back-btn:hover {
|
|
3835
|
+
background: rgba(255, 255, 255, 0.2);
|
|
3836
|
+
border-color: var(--uvf-accent-1, #ff0000);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
/* Close button specific styles */
|
|
3840
|
+
#uvf-close-btn {
|
|
3841
|
+
background: rgba(220, 53, 69, 0.8);
|
|
3842
|
+
border-color: rgba(220, 53, 69, 0.6);
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
#uvf-close-btn:hover {
|
|
3846
|
+
background: rgba(220, 53, 69, 1);
|
|
3847
|
+
border-color: rgba(220, 53, 69, 1);
|
|
3848
|
+
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3770
3851
|
.uvf-player-wrapper:hover .uvf-top-bar,
|
|
3771
3852
|
.uvf-player-wrapper.controls-visible .uvf-top-bar {
|
|
3772
3853
|
opacity: 1;
|
|
3773
3854
|
transform: translateY(0);
|
|
3774
3855
|
}
|
|
3775
3856
|
|
|
3776
|
-
/* Title Bar -
|
|
3857
|
+
/* Title Bar - After navigation buttons */
|
|
3777
3858
|
.uvf-title-bar {
|
|
3778
|
-
flex:
|
|
3779
|
-
|
|
3859
|
+
flex: 1;
|
|
3860
|
+
min-width: 0; /* Allow shrinking */
|
|
3780
3861
|
}
|
|
3781
3862
|
|
|
3782
3863
|
/* Top Controls - Right side of top bar */
|
|
@@ -3791,33 +3872,112 @@ export class WebPlayer extends BasePlayer {
|
|
|
3791
3872
|
.uvf-title-content {
|
|
3792
3873
|
display: flex;
|
|
3793
3874
|
align-items: center;
|
|
3794
|
-
|
|
3875
|
+
width: 100%;
|
|
3876
|
+
min-width: 0; /* Allow shrinking */
|
|
3795
3877
|
}
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
border: 1px solid rgba(255,255,255,0.25);
|
|
3803
|
-
background: rgba(255,255,255,0.05);
|
|
3878
|
+
|
|
3879
|
+
.uvf-title-text {
|
|
3880
|
+
display: flex;
|
|
3881
|
+
flex-direction: column;
|
|
3882
|
+
min-width: 0; /* Allow shrinking */
|
|
3883
|
+
flex: 1;
|
|
3804
3884
|
}
|
|
3805
|
-
|
|
3885
|
+
|
|
3806
3886
|
.uvf-video-title {
|
|
3807
3887
|
color: var(--uvf-text-primary);
|
|
3808
|
-
font-size: 18px;
|
|
3888
|
+
font-size: clamp(14px, 2.5vw, 18px); /* Responsive font size */
|
|
3809
3889
|
font-weight: 600;
|
|
3810
3890
|
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
|
3891
|
+
line-height: 1.3;
|
|
3892
|
+
overflow: hidden;
|
|
3893
|
+
text-overflow: ellipsis;
|
|
3894
|
+
white-space: nowrap;
|
|
3895
|
+
max-width: 100%;
|
|
3896
|
+
cursor: pointer;
|
|
3897
|
+
transition: color 0.3s ease;
|
|
3898
|
+
position: relative;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
.uvf-video-title:hover {
|
|
3902
|
+
color: var(--uvf-accent-1, #ff0000);
|
|
3811
3903
|
}
|
|
3812
3904
|
|
|
3813
3905
|
.uvf-video-subtitle {
|
|
3814
3906
|
color: var(--uvf-text-secondary);
|
|
3815
|
-
font-size: 13px;
|
|
3816
|
-
margin-top:
|
|
3817
|
-
max-width: min(70vw, 900px);
|
|
3907
|
+
font-size: clamp(11px, 1.8vw, 13px); /* Responsive font size */
|
|
3908
|
+
margin-top: 2px;
|
|
3818
3909
|
overflow: hidden;
|
|
3819
3910
|
text-overflow: ellipsis;
|
|
3820
3911
|
white-space: nowrap;
|
|
3912
|
+
max-width: 100%;
|
|
3913
|
+
opacity: 0.9;
|
|
3914
|
+
line-height: 1.4;
|
|
3915
|
+
cursor: pointer;
|
|
3916
|
+
transition: opacity 0.3s ease;
|
|
3917
|
+
position: relative;
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
.uvf-video-subtitle:hover {
|
|
3921
|
+
opacity: 1;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
/* Tooltip for long text */
|
|
3925
|
+
.uvf-text-tooltip {
|
|
3926
|
+
position: absolute;
|
|
3927
|
+
bottom: 100%;
|
|
3928
|
+
left: 0;
|
|
3929
|
+
background: rgba(0, 0, 0, 0.9);
|
|
3930
|
+
color: white;
|
|
3931
|
+
padding: 8px 12px;
|
|
3932
|
+
border-radius: 6px;
|
|
3933
|
+
font-size: 13px;
|
|
3934
|
+
line-height: 1.4;
|
|
3935
|
+
max-width: 400px;
|
|
3936
|
+
word-wrap: break-word;
|
|
3937
|
+
white-space: normal;
|
|
3938
|
+
z-index: 1000;
|
|
3939
|
+
opacity: 0;
|
|
3940
|
+
visibility: hidden;
|
|
3941
|
+
transform: translateY(-5px);
|
|
3942
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3943
|
+
pointer-events: none;
|
|
3944
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
3945
|
+
backdrop-filter: blur(8px);
|
|
3946
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
.uvf-text-tooltip::before {
|
|
3950
|
+
content: '';
|
|
3951
|
+
position: absolute;
|
|
3952
|
+
top: 100%;
|
|
3953
|
+
left: 12px;
|
|
3954
|
+
border: 5px solid transparent;
|
|
3955
|
+
border-top-color: rgba(0, 0, 0, 0.9);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
.uvf-text-tooltip.show {
|
|
3959
|
+
opacity: 1;
|
|
3960
|
+
visibility: visible;
|
|
3961
|
+
transform: translateY(0);
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
/* Multi-line title option for desktop */
|
|
3965
|
+
.uvf-video-title.multiline {
|
|
3966
|
+
white-space: normal;
|
|
3967
|
+
display: -webkit-box;
|
|
3968
|
+
-webkit-line-clamp: 2;
|
|
3969
|
+
-webkit-box-orient: vertical;
|
|
3970
|
+
line-height: 1.2;
|
|
3971
|
+
max-height: 2.4em;
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
.uvf-video-subtitle.multiline {
|
|
3975
|
+
white-space: normal;
|
|
3976
|
+
display: -webkit-box;
|
|
3977
|
+
-webkit-line-clamp: 3;
|
|
3978
|
+
-webkit-box-orient: vertical;
|
|
3979
|
+
line-height: 1.3;
|
|
3980
|
+
max-height: 3.9em;
|
|
3821
3981
|
}
|
|
3822
3982
|
|
|
3823
3983
|
/* Above seekbar section with time and branding */
|
|
@@ -3896,7 +4056,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
3896
4056
|
}
|
|
3897
4057
|
}
|
|
3898
4058
|
|
|
4059
|
+
/* Ultra small screens */
|
|
3899
4060
|
@media (max-width: 480px) {
|
|
4061
|
+
.uvf-video-title {
|
|
4062
|
+
font-size: clamp(11px, 3.5vw, 14px) !important;
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
.uvf-video-subtitle {
|
|
4066
|
+
font-size: clamp(9px, 2.5vw, 11px) !important;
|
|
4067
|
+
-webkit-line-clamp: 1; /* Single line on very small screens */
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
.uvf-left-side {
|
|
4071
|
+
max-width: 80%;
|
|
4072
|
+
}
|
|
4073
|
+
|
|
3900
4074
|
.uvf-above-seekbar-section {
|
|
3901
4075
|
margin-bottom: 5px;
|
|
3902
4076
|
}
|
|
@@ -4284,14 +4458,55 @@ export class WebPlayer extends BasePlayer {
|
|
|
4284
4458
|
}
|
|
4285
4459
|
|
|
4286
4460
|
|
|
4287
|
-
/* Mobile
|
|
4461
|
+
/* Mobile responsive styles for navigation buttons */
|
|
4462
|
+
@media screen and (max-width: 767px) {
|
|
4463
|
+
.uvf-nav-btn {
|
|
4464
|
+
width: 36px;
|
|
4465
|
+
height: 36px;
|
|
4466
|
+
min-width: 36px;
|
|
4467
|
+
min-height: 36px;
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
.uvf-nav-btn svg {
|
|
4471
|
+
width: 18px;
|
|
4472
|
+
height: 18px;
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
.uvf-navigation-controls {
|
|
4476
|
+
gap: 6px;
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
.uvf-left-side {
|
|
4480
|
+
gap: 8px;
|
|
4481
|
+
max-width: 75%;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
/* Mobile title adjustments */
|
|
4485
|
+
.uvf-video-title {
|
|
4486
|
+
font-size: clamp(12px, 3vw, 16px) !important;
|
|
4487
|
+
line-height: 1.2;
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
.uvf-video-subtitle {
|
|
4491
|
+
font-size: clamp(10px, 2.2vw, 12px) !important;
|
|
4492
|
+
margin-top: 1px;
|
|
4493
|
+
/* Allow wrapping on mobile if needed */
|
|
4494
|
+
white-space: normal;
|
|
4495
|
+
display: -webkit-box;
|
|
4496
|
+
-webkit-line-clamp: 2;
|
|
4497
|
+
-webkit-box-orient: vertical;
|
|
4498
|
+
overflow: hidden;
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
/* Mobile portrait - hide skip buttons, ensure top bar syncs with controls */
|
|
4288
4503
|
@media screen and (max-width: 767px) and (orientation: portrait) {
|
|
4289
4504
|
#uvf-skip-back,
|
|
4290
4505
|
#uvf-skip-forward {
|
|
4291
4506
|
display: none !important;
|
|
4292
4507
|
}
|
|
4293
4508
|
|
|
4294
|
-
/*
|
|
4509
|
+
/* Top bar structure */
|
|
4295
4510
|
.uvf-top-bar {
|
|
4296
4511
|
display: flex !important;
|
|
4297
4512
|
z-index: 10 !important;
|
|
@@ -4301,8 +4516,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
4301
4516
|
display: flex !important;
|
|
4302
4517
|
}
|
|
4303
4518
|
|
|
4304
|
-
/*
|
|
4305
|
-
.uvf-player-wrapper:hover .uvf-top-bar
|
|
4519
|
+
/* Disable hover effect on mobile - use only controls-visible class */
|
|
4520
|
+
.uvf-player-wrapper:hover .uvf-top-bar {
|
|
4521
|
+
opacity: 0 !important;
|
|
4522
|
+
transform: translateY(-10px) !important;
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
/* Show top bar ONLY when controls are visible */
|
|
4306
4526
|
.uvf-player-wrapper.controls-visible .uvf-top-bar {
|
|
4307
4527
|
opacity: 1 !important;
|
|
4308
4528
|
transform: translateY(0) !important;
|
|
@@ -4438,6 +4658,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
4438
4658
|
height: 16px;
|
|
4439
4659
|
}
|
|
4440
4660
|
|
|
4661
|
+
/* Disable hover effect on mobile landscape - use only controls-visible class */
|
|
4662
|
+
.uvf-player-wrapper:hover .uvf-top-bar {
|
|
4663
|
+
opacity: 0 !important;
|
|
4664
|
+
transform: translateY(-10px) !important;
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
/* Show top bar ONLY when controls are visible */
|
|
4668
|
+
.uvf-player-wrapper.controls-visible .uvf-top-bar {
|
|
4669
|
+
opacity: 1 !important;
|
|
4670
|
+
transform: translateY(0) !important;
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4441
4673
|
/* Top bar in fullscreen landscape */
|
|
4442
4674
|
.uvf-player-wrapper.uvf-fullscreen .uvf-top-bar,
|
|
4443
4675
|
.uvf-player-wrapper.uvf-fullscreen .uvf-video-container .uvf-top-bar,
|
|
@@ -4451,6 +4683,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4451
4683
|
|
|
4452
4684
|
/* Tablet devices - Enhanced UX with desktop features */
|
|
4453
4685
|
@media screen and (min-width: 768px) and (max-width: 1023px) {
|
|
4686
|
+
/* Tablet navigation and title adjustments */
|
|
4687
|
+
.uvf-nav-btn {
|
|
4688
|
+
width: 38px;
|
|
4689
|
+
height: 38px;
|
|
4690
|
+
min-width: 38px;
|
|
4691
|
+
min-height: 38px;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4694
|
+
.uvf-nav-btn svg {
|
|
4695
|
+
width: 19px;
|
|
4696
|
+
height: 19px;
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
.uvf-left-side {
|
|
4700
|
+
max-width: 70%;
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
.uvf-video-title {
|
|
4704
|
+
font-size: clamp(15px, 2.2vw, 17px) !important;
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
.uvf-video-subtitle {
|
|
4708
|
+
font-size: clamp(12px, 1.8vw, 13px) !important;
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4454
4711
|
.uvf-controls-bar {
|
|
4455
4712
|
padding: 18px 16px;
|
|
4456
4713
|
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 70%, var(--uvf-overlay-transparent) 100%);
|
|
@@ -4983,6 +5240,40 @@ export class WebPlayer extends BasePlayer {
|
|
|
4983
5240
|
}
|
|
4984
5241
|
}
|
|
4985
5242
|
|
|
5243
|
+
/* Desktop styles for title and navigation */
|
|
5244
|
+
@media screen and (min-width: 1024px) {
|
|
5245
|
+
.uvf-left-side {
|
|
5246
|
+
max-width: 65%; /* More space for title on desktop */
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
.uvf-video-title {
|
|
5250
|
+
font-size: clamp(16px, 1.8vw, 20px) !important;
|
|
5251
|
+
font-weight: 700; /* Bolder on desktop */
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
.uvf-video-subtitle {
|
|
5255
|
+
font-size: clamp(13px, 1.4vw, 15px) !important;
|
|
5256
|
+
margin-top: 3px;
|
|
5257
|
+
}
|
|
5258
|
+
|
|
5259
|
+
/* Allow hover effects on desktop */
|
|
5260
|
+
.uvf-title-bar:hover .uvf-video-title {
|
|
5261
|
+
color: var(--uvf-accent-1, #ff0000);
|
|
5262
|
+
transition: color 0.3s ease;
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5266
|
+
/* Ultra-wide screens */
|
|
5267
|
+
@media screen and (min-width: 1440px) {
|
|
5268
|
+
.uvf-video-title {
|
|
5269
|
+
font-size: clamp(18px, 1.6vw, 22px) !important;
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5272
|
+
.uvf-video-subtitle {
|
|
5273
|
+
font-size: clamp(14px, 1.2vw, 16px) !important;
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
|
|
4986
5277
|
/* Paywall Desktop */
|
|
4987
5278
|
@media screen and (min-width: 1024px) {
|
|
4988
5279
|
.uvf-paywall-modal {
|
|
@@ -5058,6 +5349,132 @@ export class WebPlayer extends BasePlayer {
|
|
|
5058
5349
|
this.debugLog('Framework branding added');
|
|
5059
5350
|
}
|
|
5060
5351
|
|
|
5352
|
+
/**
|
|
5353
|
+
* Create navigation buttons (back/close) based on configuration
|
|
5354
|
+
*/
|
|
5355
|
+
private createNavigationButtons(container: HTMLElement): void {
|
|
5356
|
+
const navigationConfig = (this.config as any).navigation;
|
|
5357
|
+
if (!navigationConfig) return;
|
|
5358
|
+
|
|
5359
|
+
const { backButton, closeButton } = navigationConfig;
|
|
5360
|
+
|
|
5361
|
+
// Back button
|
|
5362
|
+
if (backButton?.enabled) {
|
|
5363
|
+
const backBtn = document.createElement('button');
|
|
5364
|
+
backBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5365
|
+
backBtn.id = 'uvf-back-btn';
|
|
5366
|
+
backBtn.title = backButton.title || 'Back';
|
|
5367
|
+
backBtn.setAttribute('aria-label', backButton.ariaLabel || 'Go back');
|
|
5368
|
+
|
|
5369
|
+
// Get icon based on config
|
|
5370
|
+
const backIcon = this.getNavigationIcon(backButton.icon || 'arrow', backButton.customIcon);
|
|
5371
|
+
backBtn.innerHTML = backIcon;
|
|
5372
|
+
|
|
5373
|
+
// Add click handler
|
|
5374
|
+
backBtn.addEventListener('click', async (e) => {
|
|
5375
|
+
e.preventDefault();
|
|
5376
|
+
e.stopPropagation();
|
|
5377
|
+
|
|
5378
|
+
if (backButton.onClick) {
|
|
5379
|
+
await backButton.onClick();
|
|
5380
|
+
} else if (backButton.href) {
|
|
5381
|
+
if (backButton.replace) {
|
|
5382
|
+
window.history.replaceState(null, '', backButton.href);
|
|
5383
|
+
} else {
|
|
5384
|
+
window.location.href = backButton.href;
|
|
5385
|
+
}
|
|
5386
|
+
} else {
|
|
5387
|
+
// Default: go back in history
|
|
5388
|
+
window.history.back();
|
|
5389
|
+
}
|
|
5390
|
+
|
|
5391
|
+
this.emit('navigationBackClicked');
|
|
5392
|
+
});
|
|
5393
|
+
|
|
5394
|
+
container.appendChild(backBtn);
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
// Close button
|
|
5398
|
+
if (closeButton?.enabled) {
|
|
5399
|
+
const closeBtn = document.createElement('button');
|
|
5400
|
+
closeBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5401
|
+
closeBtn.id = 'uvf-close-btn';
|
|
5402
|
+
closeBtn.title = closeButton.title || 'Close';
|
|
5403
|
+
closeBtn.setAttribute('aria-label', closeButton.ariaLabel || 'Close player');
|
|
5404
|
+
|
|
5405
|
+
// Get icon based on config
|
|
5406
|
+
const closeIcon = this.getNavigationIcon(closeButton.icon || 'x', closeButton.customIcon);
|
|
5407
|
+
closeBtn.innerHTML = closeIcon;
|
|
5408
|
+
|
|
5409
|
+
// Add click handler
|
|
5410
|
+
closeBtn.addEventListener('click', async (e) => {
|
|
5411
|
+
e.preventDefault();
|
|
5412
|
+
e.stopPropagation();
|
|
5413
|
+
|
|
5414
|
+
if (closeButton.onClick) {
|
|
5415
|
+
await closeButton.onClick();
|
|
5416
|
+
} else {
|
|
5417
|
+
// Default behaviors
|
|
5418
|
+
if (closeButton.exitFullscreen && this.isFullscreen()) {
|
|
5419
|
+
await this.exitFullscreen();
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5422
|
+
if (closeButton.closeModal) {
|
|
5423
|
+
// Hide player or remove from DOM
|
|
5424
|
+
const playerWrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement;
|
|
5425
|
+
if (playerWrapper) {
|
|
5426
|
+
playerWrapper.style.display = 'none';
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5431
|
+
this.emit('navigationCloseClicked');
|
|
5432
|
+
});
|
|
5433
|
+
|
|
5434
|
+
container.appendChild(closeBtn);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
|
|
5438
|
+
/**
|
|
5439
|
+
* Get navigation icon SVG based on type
|
|
5440
|
+
*/
|
|
5441
|
+
private getNavigationIcon(iconType: string, customIcon?: string): string {
|
|
5442
|
+
if (customIcon) {
|
|
5443
|
+
// If it's a URL, create img tag, otherwise assume it's SVG
|
|
5444
|
+
if (customIcon.startsWith('http') || customIcon.includes('.')) {
|
|
5445
|
+
return `<img src="${customIcon}" alt="" style="width: 20px; height: 20px;" />`;
|
|
5446
|
+
}
|
|
5447
|
+
return customIcon;
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
switch (iconType) {
|
|
5451
|
+
case 'arrow':
|
|
5452
|
+
return `<svg viewBox="0 0 24 24">
|
|
5453
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5454
|
+
</svg>`;
|
|
5455
|
+
|
|
5456
|
+
case 'chevron':
|
|
5457
|
+
return `<svg viewBox="0 0 24 24">
|
|
5458
|
+
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
|
|
5459
|
+
</svg>`;
|
|
5460
|
+
|
|
5461
|
+
case 'x':
|
|
5462
|
+
return `<svg viewBox="0 0 24 24">
|
|
5463
|
+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
|
|
5464
|
+
</svg>`;
|
|
5465
|
+
|
|
5466
|
+
case 'close':
|
|
5467
|
+
return `<svg viewBox="0 0 24 24">
|
|
5468
|
+
<path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" fill="currentColor"/>
|
|
5469
|
+
</svg>`;
|
|
5470
|
+
|
|
5471
|
+
default:
|
|
5472
|
+
return `<svg viewBox="0 0 24 24">
|
|
5473
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5474
|
+
</svg>`;
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
|
|
5061
5478
|
private createCustomControls(container: HTMLElement): void {
|
|
5062
5479
|
// Add gradients
|
|
5063
5480
|
const topGradient = document.createElement('div');
|
|
@@ -5068,23 +5485,34 @@ export class WebPlayer extends BasePlayer {
|
|
|
5068
5485
|
controlsGradient.className = 'uvf-controls-gradient';
|
|
5069
5486
|
container.appendChild(controlsGradient);
|
|
5070
5487
|
|
|
5071
|
-
// Combined top bar:
|
|
5488
|
+
// Combined top bar: navigation buttons → title → controls
|
|
5072
5489
|
const topBar = document.createElement('div');
|
|
5073
5490
|
topBar.className = 'uvf-top-bar';
|
|
5074
5491
|
|
|
5075
|
-
//
|
|
5492
|
+
// Left side container for navigation + title
|
|
5493
|
+
const leftSide = document.createElement('div');
|
|
5494
|
+
leftSide.className = 'uvf-left-side';
|
|
5495
|
+
|
|
5496
|
+
// Navigation buttons (back/close)
|
|
5497
|
+
const navigationControls = document.createElement('div');
|
|
5498
|
+
navigationControls.className = 'uvf-navigation-controls';
|
|
5499
|
+
this.createNavigationButtons(navigationControls);
|
|
5500
|
+
leftSide.appendChild(navigationControls);
|
|
5501
|
+
|
|
5502
|
+
// Title bar (after navigation buttons)
|
|
5076
5503
|
const titleBar = document.createElement('div');
|
|
5077
5504
|
titleBar.className = 'uvf-title-bar';
|
|
5078
5505
|
titleBar.innerHTML = `
|
|
5079
5506
|
<div class="uvf-title-content">
|
|
5080
|
-
<img class="uvf-video-thumb" id="uvf-video-thumb" alt="thumbnail" style="display:none;" />
|
|
5081
5507
|
<div class="uvf-title-text">
|
|
5082
5508
|
<div class=\"uvf-video-title\" id=\"uvf-video-title\" style=\"display:none;\"></div>
|
|
5083
5509
|
<div class=\"uvf-video-subtitle\" id=\"uvf-video-description\" style=\"display:none;\"></div>
|
|
5084
5510
|
</div>
|
|
5085
5511
|
</div>
|
|
5086
5512
|
`;
|
|
5087
|
-
|
|
5513
|
+
leftSide.appendChild(titleBar);
|
|
5514
|
+
|
|
5515
|
+
topBar.appendChild(leftSide);
|
|
5088
5516
|
|
|
5089
5517
|
// Top controls (right side - Cast and Share buttons)
|
|
5090
5518
|
const topControls = document.createElement('div');
|
|
@@ -5931,11 +6359,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
5931
6359
|
break;
|
|
5932
6360
|
case 'ArrowLeft':
|
|
5933
6361
|
e.preventDefault();
|
|
6362
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5934
6363
|
this.seek(Math.max(0, this.video!.currentTime - 10));
|
|
5935
6364
|
shortcutText = '-10s';
|
|
5936
6365
|
break;
|
|
5937
6366
|
case 'ArrowRight':
|
|
5938
6367
|
e.preventDefault();
|
|
6368
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5939
6369
|
this.seek(Math.min(this.video!.duration, this.video!.currentTime + 10));
|
|
5940
6370
|
shortcutText = '+10s';
|
|
5941
6371
|
break;
|
|
@@ -7116,7 +7546,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
7116
7546
|
autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
|
|
7117
7547
|
onChapterChange: (chapter: Chapter | null) => {
|
|
7118
7548
|
this.debugLog('Core chapter changed:', chapter?.title || 'none');
|
|
7119
|
-
this.emit('
|
|
7549
|
+
this.emit('onChapterchange', chapter);
|
|
7120
7550
|
},
|
|
7121
7551
|
onSegmentEntered: (segment: ChapterSegment) => {
|
|
7122
7552
|
this.debugLog('Core segment entered:', segment.title);
|
|
@@ -8399,6 +8829,168 @@ export class WebPlayer extends BasePlayer {
|
|
|
8399
8829
|
}
|
|
8400
8830
|
}
|
|
8401
8831
|
|
|
8832
|
+
/**
|
|
8833
|
+
* Check if text is truncated and needs tooltip
|
|
8834
|
+
*/
|
|
8835
|
+
private isTextTruncated(element: HTMLElement): boolean {
|
|
8836
|
+
return element.scrollWidth > element.offsetWidth || element.scrollHeight > element.offsetHeight;
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
/**
|
|
8840
|
+
* Show tooltip for truncated text
|
|
8841
|
+
*/
|
|
8842
|
+
private showTextTooltip(element: HTMLElement, text: string): void {
|
|
8843
|
+
// Remove existing tooltip
|
|
8844
|
+
const existingTooltip = element.querySelector('.uvf-text-tooltip');
|
|
8845
|
+
if (existingTooltip) {
|
|
8846
|
+
existingTooltip.remove();
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
// Create tooltip
|
|
8850
|
+
const tooltip = document.createElement('div');
|
|
8851
|
+
tooltip.className = 'uvf-text-tooltip';
|
|
8852
|
+
tooltip.textContent = text;
|
|
8853
|
+
|
|
8854
|
+
element.appendChild(tooltip);
|
|
8855
|
+
|
|
8856
|
+
// Show tooltip with delay
|
|
8857
|
+
setTimeout(() => {
|
|
8858
|
+
tooltip.classList.add('show');
|
|
8859
|
+
}, 100);
|
|
8860
|
+
}
|
|
8861
|
+
|
|
8862
|
+
/**
|
|
8863
|
+
* Hide tooltip
|
|
8864
|
+
*/
|
|
8865
|
+
private hideTextTooltip(element: HTMLElement): void {
|
|
8866
|
+
const tooltip = element.querySelector('.uvf-text-tooltip');
|
|
8867
|
+
if (tooltip) {
|
|
8868
|
+
tooltip.classList.remove('show');
|
|
8869
|
+
setTimeout(() => {
|
|
8870
|
+
if (tooltip.parentElement) {
|
|
8871
|
+
tooltip.remove();
|
|
8872
|
+
}
|
|
8873
|
+
}, 300);
|
|
8874
|
+
}
|
|
8875
|
+
}
|
|
8876
|
+
|
|
8877
|
+
/**
|
|
8878
|
+
* Setup tooltip handlers for title and description
|
|
8879
|
+
*/
|
|
8880
|
+
private setupTextTooltips(): void {
|
|
8881
|
+
const titleElement = document.getElementById('uvf-video-title');
|
|
8882
|
+
const descElement = document.getElementById('uvf-video-description');
|
|
8883
|
+
|
|
8884
|
+
if (titleElement) {
|
|
8885
|
+
titleElement.addEventListener('mouseenter', () => {
|
|
8886
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8887
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8888
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8889
|
+
}
|
|
8890
|
+
});
|
|
8891
|
+
|
|
8892
|
+
titleElement.addEventListener('mouseleave', () => {
|
|
8893
|
+
this.hideTextTooltip(titleElement);
|
|
8894
|
+
});
|
|
8895
|
+
|
|
8896
|
+
// Touch support for mobile
|
|
8897
|
+
titleElement.addEventListener('touchstart', () => {
|
|
8898
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8899
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8900
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8901
|
+
// Auto-hide after 3 seconds on touch
|
|
8902
|
+
setTimeout(() => {
|
|
8903
|
+
this.hideTextTooltip(titleElement);
|
|
8904
|
+
}, 3000);
|
|
8905
|
+
}
|
|
8906
|
+
});
|
|
8907
|
+
}
|
|
8908
|
+
|
|
8909
|
+
if (descElement) {
|
|
8910
|
+
descElement.addEventListener('mouseenter', () => {
|
|
8911
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8912
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8913
|
+
this.showTextTooltip(descElement, descText);
|
|
8914
|
+
}
|
|
8915
|
+
});
|
|
8916
|
+
|
|
8917
|
+
descElement.addEventListener('mouseleave', () => {
|
|
8918
|
+
this.hideTextTooltip(descElement);
|
|
8919
|
+
});
|
|
8920
|
+
|
|
8921
|
+
// Touch support for mobile
|
|
8922
|
+
descElement.addEventListener('touchstart', () => {
|
|
8923
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8924
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8925
|
+
this.showTextTooltip(descElement, descText);
|
|
8926
|
+
// Auto-hide after 3 seconds on touch
|
|
8927
|
+
setTimeout(() => {
|
|
8928
|
+
this.hideTextTooltip(descElement);
|
|
8929
|
+
}, 3000);
|
|
8930
|
+
}
|
|
8931
|
+
});
|
|
8932
|
+
}
|
|
8933
|
+
}
|
|
8934
|
+
|
|
8935
|
+
/**
|
|
8936
|
+
* Smart text truncation based on word count
|
|
8937
|
+
*/
|
|
8938
|
+
private smartTruncateText(text: string, maxWords: number = 12): { truncated: string, needsTooltip: boolean } {
|
|
8939
|
+
const words = text.split(' ');
|
|
8940
|
+
if (words.length <= maxWords) {
|
|
8941
|
+
return { truncated: text, needsTooltip: false };
|
|
8942
|
+
}
|
|
8943
|
+
|
|
8944
|
+
const truncated = words.slice(0, maxWords).join(' ') + '...';
|
|
8945
|
+
return { truncated, needsTooltip: true };
|
|
8946
|
+
}
|
|
8947
|
+
|
|
8948
|
+
/**
|
|
8949
|
+
* Apply smart text display based on screen size and content length
|
|
8950
|
+
*/
|
|
8951
|
+
private applySmartTextDisplay(titleEl: HTMLElement | null, descEl: HTMLElement | null, titleText: string, descText: string): void {
|
|
8952
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
8953
|
+
const isMobile = window.innerWidth < 768;
|
|
8954
|
+
|
|
8955
|
+
if (titleEl && titleText) {
|
|
8956
|
+
const wordCount = titleText.split(' ').length;
|
|
8957
|
+
|
|
8958
|
+
if (isDesktop && wordCount > 8 && wordCount <= 15) {
|
|
8959
|
+
// Use multiline for moderately long titles on desktop
|
|
8960
|
+
titleEl.classList.add('multiline');
|
|
8961
|
+
titleEl.textContent = titleText;
|
|
8962
|
+
} else if (wordCount > 12) {
|
|
8963
|
+
// Smart truncation for very long titles
|
|
8964
|
+
const maxWords = isMobile ? 8 : isDesktop ? 12 : 10;
|
|
8965
|
+
const { truncated } = this.smartTruncateText(titleText, maxWords);
|
|
8966
|
+
titleEl.textContent = truncated;
|
|
8967
|
+
titleEl.classList.remove('multiline');
|
|
8968
|
+
} else {
|
|
8969
|
+
titleEl.textContent = titleText;
|
|
8970
|
+
titleEl.classList.remove('multiline');
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
|
|
8974
|
+
if (descEl && descText) {
|
|
8975
|
+
const wordCount = descText.split(' ').length;
|
|
8976
|
+
|
|
8977
|
+
if (isDesktop && wordCount > 15 && wordCount <= 25) {
|
|
8978
|
+
// Use multiline for moderately long descriptions on desktop
|
|
8979
|
+
descEl.classList.add('multiline');
|
|
8980
|
+
descEl.textContent = descText;
|
|
8981
|
+
} else if (wordCount > 20) {
|
|
8982
|
+
// Smart truncation for very long descriptions
|
|
8983
|
+
const maxWords = isMobile ? 12 : isDesktop ? 18 : 15;
|
|
8984
|
+
const { truncated } = this.smartTruncateText(descText, maxWords);
|
|
8985
|
+
descEl.textContent = truncated;
|
|
8986
|
+
descEl.classList.remove('multiline');
|
|
8987
|
+
} else {
|
|
8988
|
+
descEl.textContent = descText;
|
|
8989
|
+
descEl.classList.remove('multiline');
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
|
|
8402
8994
|
private updateMetadataUI(): void {
|
|
8403
8995
|
try {
|
|
8404
8996
|
const md = this.source?.metadata || ({} as any);
|
|
@@ -8411,34 +9003,33 @@ export class WebPlayer extends BasePlayer {
|
|
|
8411
9003
|
const descText = (md.description || '').toString().trim();
|
|
8412
9004
|
const thumbUrl = (md.thumbnailUrl || '').toString().trim();
|
|
8413
9005
|
|
|
8414
|
-
//
|
|
9006
|
+
// Apply smart text display with truncation and multiline support
|
|
9007
|
+
this.applySmartTextDisplay(titleEl, descEl, titleText, descText);
|
|
9008
|
+
|
|
9009
|
+
// Show/hide elements
|
|
8415
9010
|
if (titleEl) {
|
|
8416
|
-
titleEl.textContent = titleText;
|
|
8417
9011
|
titleEl.style.display = titleText ? 'block' : 'none';
|
|
8418
9012
|
}
|
|
8419
|
-
|
|
8420
|
-
// Description
|
|
8421
9013
|
if (descEl) {
|
|
8422
|
-
descEl.textContent = descText;
|
|
8423
9014
|
descEl.style.display = descText ? 'block' : 'none';
|
|
8424
9015
|
}
|
|
8425
9016
|
|
|
8426
|
-
// Thumbnail
|
|
9017
|
+
// Thumbnail (removed from layout but keeping for compatibility)
|
|
8427
9018
|
if (thumbEl) {
|
|
8428
|
-
|
|
8429
|
-
thumbEl.src = thumbUrl;
|
|
8430
|
-
thumbEl.style.display = 'block';
|
|
8431
|
-
} else {
|
|
8432
|
-
thumbEl.removeAttribute('src');
|
|
8433
|
-
thumbEl.style.display = 'none';
|
|
8434
|
-
}
|
|
9019
|
+
thumbEl.style.display = 'none'; // Always hidden in new layout
|
|
8435
9020
|
}
|
|
8436
9021
|
|
|
8437
9022
|
// Hide entire title bar if nothing to show
|
|
8438
|
-
const hasAny = !!(titleText || descText
|
|
9023
|
+
const hasAny = !!(titleText || descText);
|
|
8439
9024
|
if (titleBar) {
|
|
8440
9025
|
titleBar.style.display = hasAny ? '' : 'none';
|
|
8441
9026
|
}
|
|
9027
|
+
|
|
9028
|
+
// Setup tooltips for truncated text
|
|
9029
|
+
setTimeout(() => {
|
|
9030
|
+
this.setupTextTooltips();
|
|
9031
|
+
}, 100); // Small delay to ensure elements are rendered
|
|
9032
|
+
|
|
8442
9033
|
} catch (_) { /* ignore */ }
|
|
8443
9034
|
}
|
|
8444
9035
|
|