unified-video-framework 1.4.209 → 1.4.210
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 +520 -32
- package/packages/web/dist/WebPlayer.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 +611 -37
- 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,6 +4458,47 @@ export class WebPlayer extends BasePlayer {
|
|
|
4284
4458
|
}
|
|
4285
4459
|
|
|
4286
4460
|
|
|
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
|
+
|
|
4287
4502
|
/* Mobile portrait - hide skip buttons, ensure top bar visible */
|
|
4288
4503
|
@media screen and (max-width: 767px) and (orientation: portrait) {
|
|
4289
4504
|
#uvf-skip-back,
|
|
@@ -4451,6 +4666,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4451
4666
|
|
|
4452
4667
|
/* Tablet devices - Enhanced UX with desktop features */
|
|
4453
4668
|
@media screen and (min-width: 768px) and (max-width: 1023px) {
|
|
4669
|
+
/* Tablet navigation and title adjustments */
|
|
4670
|
+
.uvf-nav-btn {
|
|
4671
|
+
width: 38px;
|
|
4672
|
+
height: 38px;
|
|
4673
|
+
min-width: 38px;
|
|
4674
|
+
min-height: 38px;
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
.uvf-nav-btn svg {
|
|
4678
|
+
width: 19px;
|
|
4679
|
+
height: 19px;
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
.uvf-left-side {
|
|
4683
|
+
max-width: 70%;
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
.uvf-video-title {
|
|
4687
|
+
font-size: clamp(15px, 2.2vw, 17px) !important;
|
|
4688
|
+
}
|
|
4689
|
+
|
|
4690
|
+
.uvf-video-subtitle {
|
|
4691
|
+
font-size: clamp(12px, 1.8vw, 13px) !important;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4454
4694
|
.uvf-controls-bar {
|
|
4455
4695
|
padding: 18px 16px;
|
|
4456
4696
|
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 70%, var(--uvf-overlay-transparent) 100%);
|
|
@@ -4983,6 +5223,40 @@ export class WebPlayer extends BasePlayer {
|
|
|
4983
5223
|
}
|
|
4984
5224
|
}
|
|
4985
5225
|
|
|
5226
|
+
/* Desktop styles for title and navigation */
|
|
5227
|
+
@media screen and (min-width: 1024px) {
|
|
5228
|
+
.uvf-left-side {
|
|
5229
|
+
max-width: 65%; /* More space for title on desktop */
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5232
|
+
.uvf-video-title {
|
|
5233
|
+
font-size: clamp(16px, 1.8vw, 20px) !important;
|
|
5234
|
+
font-weight: 700; /* Bolder on desktop */
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
.uvf-video-subtitle {
|
|
5238
|
+
font-size: clamp(13px, 1.4vw, 15px) !important;
|
|
5239
|
+
margin-top: 3px;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
/* Allow hover effects on desktop */
|
|
5243
|
+
.uvf-title-bar:hover .uvf-video-title {
|
|
5244
|
+
color: var(--uvf-accent-1, #ff0000);
|
|
5245
|
+
transition: color 0.3s ease;
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
/* Ultra-wide screens */
|
|
5250
|
+
@media screen and (min-width: 1440px) {
|
|
5251
|
+
.uvf-video-title {
|
|
5252
|
+
font-size: clamp(18px, 1.6vw, 22px) !important;
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5255
|
+
.uvf-video-subtitle {
|
|
5256
|
+
font-size: clamp(14px, 1.2vw, 16px) !important;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
|
|
4986
5260
|
/* Paywall Desktop */
|
|
4987
5261
|
@media screen and (min-width: 1024px) {
|
|
4988
5262
|
.uvf-paywall-modal {
|
|
@@ -5058,6 +5332,132 @@ export class WebPlayer extends BasePlayer {
|
|
|
5058
5332
|
this.debugLog('Framework branding added');
|
|
5059
5333
|
}
|
|
5060
5334
|
|
|
5335
|
+
/**
|
|
5336
|
+
* Create navigation buttons (back/close) based on configuration
|
|
5337
|
+
*/
|
|
5338
|
+
private createNavigationButtons(container: HTMLElement): void {
|
|
5339
|
+
const navigationConfig = (this.config as any).navigation;
|
|
5340
|
+
if (!navigationConfig) return;
|
|
5341
|
+
|
|
5342
|
+
const { backButton, closeButton } = navigationConfig;
|
|
5343
|
+
|
|
5344
|
+
// Back button
|
|
5345
|
+
if (backButton?.enabled) {
|
|
5346
|
+
const backBtn = document.createElement('button');
|
|
5347
|
+
backBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5348
|
+
backBtn.id = 'uvf-back-btn';
|
|
5349
|
+
backBtn.title = backButton.title || 'Back';
|
|
5350
|
+
backBtn.setAttribute('aria-label', backButton.ariaLabel || 'Go back');
|
|
5351
|
+
|
|
5352
|
+
// Get icon based on config
|
|
5353
|
+
const backIcon = this.getNavigationIcon(backButton.icon || 'arrow', backButton.customIcon);
|
|
5354
|
+
backBtn.innerHTML = backIcon;
|
|
5355
|
+
|
|
5356
|
+
// Add click handler
|
|
5357
|
+
backBtn.addEventListener('click', async (e) => {
|
|
5358
|
+
e.preventDefault();
|
|
5359
|
+
e.stopPropagation();
|
|
5360
|
+
|
|
5361
|
+
if (backButton.onClick) {
|
|
5362
|
+
await backButton.onClick();
|
|
5363
|
+
} else if (backButton.href) {
|
|
5364
|
+
if (backButton.replace) {
|
|
5365
|
+
window.history.replaceState(null, '', backButton.href);
|
|
5366
|
+
} else {
|
|
5367
|
+
window.location.href = backButton.href;
|
|
5368
|
+
}
|
|
5369
|
+
} else {
|
|
5370
|
+
// Default: go back in history
|
|
5371
|
+
window.history.back();
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
this.emit('navigationBackClicked');
|
|
5375
|
+
});
|
|
5376
|
+
|
|
5377
|
+
container.appendChild(backBtn);
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
// Close button
|
|
5381
|
+
if (closeButton?.enabled) {
|
|
5382
|
+
const closeBtn = document.createElement('button');
|
|
5383
|
+
closeBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5384
|
+
closeBtn.id = 'uvf-close-btn';
|
|
5385
|
+
closeBtn.title = closeButton.title || 'Close';
|
|
5386
|
+
closeBtn.setAttribute('aria-label', closeButton.ariaLabel || 'Close player');
|
|
5387
|
+
|
|
5388
|
+
// Get icon based on config
|
|
5389
|
+
const closeIcon = this.getNavigationIcon(closeButton.icon || 'x', closeButton.customIcon);
|
|
5390
|
+
closeBtn.innerHTML = closeIcon;
|
|
5391
|
+
|
|
5392
|
+
// Add click handler
|
|
5393
|
+
closeBtn.addEventListener('click', async (e) => {
|
|
5394
|
+
e.preventDefault();
|
|
5395
|
+
e.stopPropagation();
|
|
5396
|
+
|
|
5397
|
+
if (closeButton.onClick) {
|
|
5398
|
+
await closeButton.onClick();
|
|
5399
|
+
} else {
|
|
5400
|
+
// Default behaviors
|
|
5401
|
+
if (closeButton.exitFullscreen && this.isFullscreen()) {
|
|
5402
|
+
await this.exitFullscreen();
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5405
|
+
if (closeButton.closeModal) {
|
|
5406
|
+
// Hide player or remove from DOM
|
|
5407
|
+
const playerWrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement;
|
|
5408
|
+
if (playerWrapper) {
|
|
5409
|
+
playerWrapper.style.display = 'none';
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
this.emit('navigationCloseClicked');
|
|
5415
|
+
});
|
|
5416
|
+
|
|
5417
|
+
container.appendChild(closeBtn);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
/**
|
|
5422
|
+
* Get navigation icon SVG based on type
|
|
5423
|
+
*/
|
|
5424
|
+
private getNavigationIcon(iconType: string, customIcon?: string): string {
|
|
5425
|
+
if (customIcon) {
|
|
5426
|
+
// If it's a URL, create img tag, otherwise assume it's SVG
|
|
5427
|
+
if (customIcon.startsWith('http') || customIcon.includes('.')) {
|
|
5428
|
+
return `<img src="${customIcon}" alt="" style="width: 20px; height: 20px;" />`;
|
|
5429
|
+
}
|
|
5430
|
+
return customIcon;
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
switch (iconType) {
|
|
5434
|
+
case 'arrow':
|
|
5435
|
+
return `<svg viewBox="0 0 24 24">
|
|
5436
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5437
|
+
</svg>`;
|
|
5438
|
+
|
|
5439
|
+
case 'chevron':
|
|
5440
|
+
return `<svg viewBox="0 0 24 24">
|
|
5441
|
+
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
|
|
5442
|
+
</svg>`;
|
|
5443
|
+
|
|
5444
|
+
case 'x':
|
|
5445
|
+
return `<svg viewBox="0 0 24 24">
|
|
5446
|
+
<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"/>
|
|
5447
|
+
</svg>`;
|
|
5448
|
+
|
|
5449
|
+
case 'close':
|
|
5450
|
+
return `<svg viewBox="0 0 24 24">
|
|
5451
|
+
<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"/>
|
|
5452
|
+
</svg>`;
|
|
5453
|
+
|
|
5454
|
+
default:
|
|
5455
|
+
return `<svg viewBox="0 0 24 24">
|
|
5456
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5457
|
+
</svg>`;
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5061
5461
|
private createCustomControls(container: HTMLElement): void {
|
|
5062
5462
|
// Add gradients
|
|
5063
5463
|
const topGradient = document.createElement('div');
|
|
@@ -5068,23 +5468,34 @@ export class WebPlayer extends BasePlayer {
|
|
|
5068
5468
|
controlsGradient.className = 'uvf-controls-gradient';
|
|
5069
5469
|
container.appendChild(controlsGradient);
|
|
5070
5470
|
|
|
5071
|
-
// Combined top bar:
|
|
5471
|
+
// Combined top bar: navigation buttons → title → controls
|
|
5072
5472
|
const topBar = document.createElement('div');
|
|
5073
5473
|
topBar.className = 'uvf-top-bar';
|
|
5074
5474
|
|
|
5075
|
-
//
|
|
5475
|
+
// Left side container for navigation + title
|
|
5476
|
+
const leftSide = document.createElement('div');
|
|
5477
|
+
leftSide.className = 'uvf-left-side';
|
|
5478
|
+
|
|
5479
|
+
// Navigation buttons (back/close)
|
|
5480
|
+
const navigationControls = document.createElement('div');
|
|
5481
|
+
navigationControls.className = 'uvf-navigation-controls';
|
|
5482
|
+
this.createNavigationButtons(navigationControls);
|
|
5483
|
+
leftSide.appendChild(navigationControls);
|
|
5484
|
+
|
|
5485
|
+
// Title bar (after navigation buttons)
|
|
5076
5486
|
const titleBar = document.createElement('div');
|
|
5077
5487
|
titleBar.className = 'uvf-title-bar';
|
|
5078
5488
|
titleBar.innerHTML = `
|
|
5079
5489
|
<div class="uvf-title-content">
|
|
5080
|
-
<img class="uvf-video-thumb" id="uvf-video-thumb" alt="thumbnail" style="display:none;" />
|
|
5081
5490
|
<div class="uvf-title-text">
|
|
5082
5491
|
<div class=\"uvf-video-title\" id=\"uvf-video-title\" style=\"display:none;\"></div>
|
|
5083
5492
|
<div class=\"uvf-video-subtitle\" id=\"uvf-video-description\" style=\"display:none;\"></div>
|
|
5084
5493
|
</div>
|
|
5085
5494
|
</div>
|
|
5086
5495
|
`;
|
|
5087
|
-
|
|
5496
|
+
leftSide.appendChild(titleBar);
|
|
5497
|
+
|
|
5498
|
+
topBar.appendChild(leftSide);
|
|
5088
5499
|
|
|
5089
5500
|
// Top controls (right side - Cast and Share buttons)
|
|
5090
5501
|
const topControls = document.createElement('div');
|
|
@@ -5931,11 +6342,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
5931
6342
|
break;
|
|
5932
6343
|
case 'ArrowLeft':
|
|
5933
6344
|
e.preventDefault();
|
|
6345
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5934
6346
|
this.seek(Math.max(0, this.video!.currentTime - 10));
|
|
5935
6347
|
shortcutText = '-10s';
|
|
5936
6348
|
break;
|
|
5937
6349
|
case 'ArrowRight':
|
|
5938
6350
|
e.preventDefault();
|
|
6351
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5939
6352
|
this.seek(Math.min(this.video!.duration, this.video!.currentTime + 10));
|
|
5940
6353
|
shortcutText = '+10s';
|
|
5941
6354
|
break;
|
|
@@ -7116,7 +7529,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
7116
7529
|
autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
|
|
7117
7530
|
onChapterChange: (chapter: Chapter | null) => {
|
|
7118
7531
|
this.debugLog('Core chapter changed:', chapter?.title || 'none');
|
|
7119
|
-
this.emit('
|
|
7532
|
+
this.emit('onChapterchange', chapter);
|
|
7120
7533
|
},
|
|
7121
7534
|
onSegmentEntered: (segment: ChapterSegment) => {
|
|
7122
7535
|
this.debugLog('Core segment entered:', segment.title);
|
|
@@ -8399,6 +8812,168 @@ export class WebPlayer extends BasePlayer {
|
|
|
8399
8812
|
}
|
|
8400
8813
|
}
|
|
8401
8814
|
|
|
8815
|
+
/**
|
|
8816
|
+
* Check if text is truncated and needs tooltip
|
|
8817
|
+
*/
|
|
8818
|
+
private isTextTruncated(element: HTMLElement): boolean {
|
|
8819
|
+
return element.scrollWidth > element.offsetWidth || element.scrollHeight > element.offsetHeight;
|
|
8820
|
+
}
|
|
8821
|
+
|
|
8822
|
+
/**
|
|
8823
|
+
* Show tooltip for truncated text
|
|
8824
|
+
*/
|
|
8825
|
+
private showTextTooltip(element: HTMLElement, text: string): void {
|
|
8826
|
+
// Remove existing tooltip
|
|
8827
|
+
const existingTooltip = element.querySelector('.uvf-text-tooltip');
|
|
8828
|
+
if (existingTooltip) {
|
|
8829
|
+
existingTooltip.remove();
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
// Create tooltip
|
|
8833
|
+
const tooltip = document.createElement('div');
|
|
8834
|
+
tooltip.className = 'uvf-text-tooltip';
|
|
8835
|
+
tooltip.textContent = text;
|
|
8836
|
+
|
|
8837
|
+
element.appendChild(tooltip);
|
|
8838
|
+
|
|
8839
|
+
// Show tooltip with delay
|
|
8840
|
+
setTimeout(() => {
|
|
8841
|
+
tooltip.classList.add('show');
|
|
8842
|
+
}, 100);
|
|
8843
|
+
}
|
|
8844
|
+
|
|
8845
|
+
/**
|
|
8846
|
+
* Hide tooltip
|
|
8847
|
+
*/
|
|
8848
|
+
private hideTextTooltip(element: HTMLElement): void {
|
|
8849
|
+
const tooltip = element.querySelector('.uvf-text-tooltip');
|
|
8850
|
+
if (tooltip) {
|
|
8851
|
+
tooltip.classList.remove('show');
|
|
8852
|
+
setTimeout(() => {
|
|
8853
|
+
if (tooltip.parentElement) {
|
|
8854
|
+
tooltip.remove();
|
|
8855
|
+
}
|
|
8856
|
+
}, 300);
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
|
|
8860
|
+
/**
|
|
8861
|
+
* Setup tooltip handlers for title and description
|
|
8862
|
+
*/
|
|
8863
|
+
private setupTextTooltips(): void {
|
|
8864
|
+
const titleElement = document.getElementById('uvf-video-title');
|
|
8865
|
+
const descElement = document.getElementById('uvf-video-description');
|
|
8866
|
+
|
|
8867
|
+
if (titleElement) {
|
|
8868
|
+
titleElement.addEventListener('mouseenter', () => {
|
|
8869
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8870
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8871
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8872
|
+
}
|
|
8873
|
+
});
|
|
8874
|
+
|
|
8875
|
+
titleElement.addEventListener('mouseleave', () => {
|
|
8876
|
+
this.hideTextTooltip(titleElement);
|
|
8877
|
+
});
|
|
8878
|
+
|
|
8879
|
+
// Touch support for mobile
|
|
8880
|
+
titleElement.addEventListener('touchstart', () => {
|
|
8881
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8882
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8883
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8884
|
+
// Auto-hide after 3 seconds on touch
|
|
8885
|
+
setTimeout(() => {
|
|
8886
|
+
this.hideTextTooltip(titleElement);
|
|
8887
|
+
}, 3000);
|
|
8888
|
+
}
|
|
8889
|
+
});
|
|
8890
|
+
}
|
|
8891
|
+
|
|
8892
|
+
if (descElement) {
|
|
8893
|
+
descElement.addEventListener('mouseenter', () => {
|
|
8894
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8895
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8896
|
+
this.showTextTooltip(descElement, descText);
|
|
8897
|
+
}
|
|
8898
|
+
});
|
|
8899
|
+
|
|
8900
|
+
descElement.addEventListener('mouseleave', () => {
|
|
8901
|
+
this.hideTextTooltip(descElement);
|
|
8902
|
+
});
|
|
8903
|
+
|
|
8904
|
+
// Touch support for mobile
|
|
8905
|
+
descElement.addEventListener('touchstart', () => {
|
|
8906
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8907
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8908
|
+
this.showTextTooltip(descElement, descText);
|
|
8909
|
+
// Auto-hide after 3 seconds on touch
|
|
8910
|
+
setTimeout(() => {
|
|
8911
|
+
this.hideTextTooltip(descElement);
|
|
8912
|
+
}, 3000);
|
|
8913
|
+
}
|
|
8914
|
+
});
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
|
|
8918
|
+
/**
|
|
8919
|
+
* Smart text truncation based on word count
|
|
8920
|
+
*/
|
|
8921
|
+
private smartTruncateText(text: string, maxWords: number = 12): { truncated: string, needsTooltip: boolean } {
|
|
8922
|
+
const words = text.split(' ');
|
|
8923
|
+
if (words.length <= maxWords) {
|
|
8924
|
+
return { truncated: text, needsTooltip: false };
|
|
8925
|
+
}
|
|
8926
|
+
|
|
8927
|
+
const truncated = words.slice(0, maxWords).join(' ') + '...';
|
|
8928
|
+
return { truncated, needsTooltip: true };
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8931
|
+
/**
|
|
8932
|
+
* Apply smart text display based on screen size and content length
|
|
8933
|
+
*/
|
|
8934
|
+
private applySmartTextDisplay(titleEl: HTMLElement | null, descEl: HTMLElement | null, titleText: string, descText: string): void {
|
|
8935
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
8936
|
+
const isMobile = window.innerWidth < 768;
|
|
8937
|
+
|
|
8938
|
+
if (titleEl && titleText) {
|
|
8939
|
+
const wordCount = titleText.split(' ').length;
|
|
8940
|
+
|
|
8941
|
+
if (isDesktop && wordCount > 8 && wordCount <= 15) {
|
|
8942
|
+
// Use multiline for moderately long titles on desktop
|
|
8943
|
+
titleEl.classList.add('multiline');
|
|
8944
|
+
titleEl.textContent = titleText;
|
|
8945
|
+
} else if (wordCount > 12) {
|
|
8946
|
+
// Smart truncation for very long titles
|
|
8947
|
+
const maxWords = isMobile ? 8 : isDesktop ? 12 : 10;
|
|
8948
|
+
const { truncated } = this.smartTruncateText(titleText, maxWords);
|
|
8949
|
+
titleEl.textContent = truncated;
|
|
8950
|
+
titleEl.classList.remove('multiline');
|
|
8951
|
+
} else {
|
|
8952
|
+
titleEl.textContent = titleText;
|
|
8953
|
+
titleEl.classList.remove('multiline');
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
|
|
8957
|
+
if (descEl && descText) {
|
|
8958
|
+
const wordCount = descText.split(' ').length;
|
|
8959
|
+
|
|
8960
|
+
if (isDesktop && wordCount > 15 && wordCount <= 25) {
|
|
8961
|
+
// Use multiline for moderately long descriptions on desktop
|
|
8962
|
+
descEl.classList.add('multiline');
|
|
8963
|
+
descEl.textContent = descText;
|
|
8964
|
+
} else if (wordCount > 20) {
|
|
8965
|
+
// Smart truncation for very long descriptions
|
|
8966
|
+
const maxWords = isMobile ? 12 : isDesktop ? 18 : 15;
|
|
8967
|
+
const { truncated } = this.smartTruncateText(descText, maxWords);
|
|
8968
|
+
descEl.textContent = truncated;
|
|
8969
|
+
descEl.classList.remove('multiline');
|
|
8970
|
+
} else {
|
|
8971
|
+
descEl.textContent = descText;
|
|
8972
|
+
descEl.classList.remove('multiline');
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
|
|
8402
8977
|
private updateMetadataUI(): void {
|
|
8403
8978
|
try {
|
|
8404
8979
|
const md = this.source?.metadata || ({} as any);
|
|
@@ -8411,34 +8986,33 @@ export class WebPlayer extends BasePlayer {
|
|
|
8411
8986
|
const descText = (md.description || '').toString().trim();
|
|
8412
8987
|
const thumbUrl = (md.thumbnailUrl || '').toString().trim();
|
|
8413
8988
|
|
|
8414
|
-
//
|
|
8989
|
+
// Apply smart text display with truncation and multiline support
|
|
8990
|
+
this.applySmartTextDisplay(titleEl, descEl, titleText, descText);
|
|
8991
|
+
|
|
8992
|
+
// Show/hide elements
|
|
8415
8993
|
if (titleEl) {
|
|
8416
|
-
titleEl.textContent = titleText;
|
|
8417
8994
|
titleEl.style.display = titleText ? 'block' : 'none';
|
|
8418
8995
|
}
|
|
8419
|
-
|
|
8420
|
-
// Description
|
|
8421
8996
|
if (descEl) {
|
|
8422
|
-
descEl.textContent = descText;
|
|
8423
8997
|
descEl.style.display = descText ? 'block' : 'none';
|
|
8424
8998
|
}
|
|
8425
8999
|
|
|
8426
|
-
// Thumbnail
|
|
9000
|
+
// Thumbnail (removed from layout but keeping for compatibility)
|
|
8427
9001
|
if (thumbEl) {
|
|
8428
|
-
|
|
8429
|
-
thumbEl.src = thumbUrl;
|
|
8430
|
-
thumbEl.style.display = 'block';
|
|
8431
|
-
} else {
|
|
8432
|
-
thumbEl.removeAttribute('src');
|
|
8433
|
-
thumbEl.style.display = 'none';
|
|
8434
|
-
}
|
|
9002
|
+
thumbEl.style.display = 'none'; // Always hidden in new layout
|
|
8435
9003
|
}
|
|
8436
9004
|
|
|
8437
9005
|
// Hide entire title bar if nothing to show
|
|
8438
|
-
const hasAny = !!(titleText || descText
|
|
9006
|
+
const hasAny = !!(titleText || descText);
|
|
8439
9007
|
if (titleBar) {
|
|
8440
9008
|
titleBar.style.display = hasAny ? '' : 'none';
|
|
8441
9009
|
}
|
|
9010
|
+
|
|
9011
|
+
// Setup tooltips for truncated text
|
|
9012
|
+
setTimeout(() => {
|
|
9013
|
+
this.setupTextTooltips();
|
|
9014
|
+
}, 100); // Small delay to ensure elements are rendered
|
|
9015
|
+
|
|
8442
9016
|
} catch (_) { /* ignore */ }
|
|
8443
9017
|
}
|
|
8444
9018
|
|