unified-video-framework 1.4.166 → 1.4.167
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.
|
@@ -51,9 +51,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
51
51
|
private currentPlaybackRate = 1;
|
|
52
52
|
private isDragging: boolean = false;
|
|
53
53
|
|
|
54
|
-
// Settings menu state guard to keep controls interactive while open
|
|
55
|
-
private isSettingsOpen: boolean = false;
|
|
56
|
-
|
|
57
54
|
// Settings configuration
|
|
58
55
|
private settingsConfig = {
|
|
59
56
|
enabled: true, // Show settings button
|
|
@@ -100,20 +97,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
100
97
|
// Progress bar tooltip state
|
|
101
98
|
private showTimeTooltip: boolean = false;
|
|
102
99
|
|
|
103
|
-
// Material You touch gesture state
|
|
104
|
-
private lastTapTime: number = 0;
|
|
105
|
-
private tapCount: number = 0;
|
|
106
|
-
private tapTimeout: NodeJS.Timeout | null = null;
|
|
107
|
-
private longPressTimer: NodeJS.Timeout | null = null;
|
|
108
|
-
private longPressActive: boolean = false;
|
|
109
|
-
private longPressStartTime: number = 0;
|
|
110
|
-
private originalPlaybackRate: number = 1;
|
|
111
|
-
|
|
112
|
-
// Material You dynamic theming - matches existing theme
|
|
113
|
-
private dominantColor: string = '#ff0000'; // Primary accent from theme
|
|
114
|
-
private accentColor: string = '#ff4d4f'; // Secondary accent from theme
|
|
115
|
-
private surfaceTint: string = 'rgba(255, 0, 0, 0.08)'; // Surface tint for Material containers
|
|
116
|
-
|
|
117
100
|
// Autoplay enhancement state
|
|
118
101
|
private autoplayCapabilities: {
|
|
119
102
|
canAutoplay: boolean;
|
|
@@ -359,35 +342,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
359
342
|
|
|
360
343
|
// Initialize metadata UI to hidden/empty by default
|
|
361
344
|
this.updateMetadataUI();
|
|
362
|
-
|
|
363
|
-
// Enable Material You mobile layout on mobile portrait devices
|
|
364
|
-
this.enableMaterialYouMobileIfNeeded();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Enable Material You mobile layout if on mobile portrait device
|
|
369
|
-
*/
|
|
370
|
-
private enableMaterialYouMobileIfNeeded(): void {
|
|
371
|
-
// Check if mobile and portrait
|
|
372
|
-
const isMobile = this.isMobileDevice();
|
|
373
|
-
const isPortrait = window.innerHeight > window.innerWidth;
|
|
374
|
-
|
|
375
|
-
if (isMobile && isPortrait && this.playerWrapper) {
|
|
376
|
-
this.debugLog('Enabling Material You mobile layout');
|
|
377
|
-
this.playerWrapper.classList.add('uvf-material-you-mobile');
|
|
378
|
-
|
|
379
|
-
// Listen for orientation changes
|
|
380
|
-
window.addEventListener('resize', () => {
|
|
381
|
-
const isNowPortrait = window.innerHeight > window.innerWidth;
|
|
382
|
-
if (this.playerWrapper) {
|
|
383
|
-
if (isMobile && isNowPortrait) {
|
|
384
|
-
this.playerWrapper.classList.add('uvf-material-you-mobile');
|
|
385
|
-
} else {
|
|
386
|
-
this.playerWrapper.classList.remove('uvf-material-you-mobile');
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
345
|
}
|
|
392
346
|
|
|
393
347
|
private setupVideoEventListeners(): void {
|
|
@@ -480,9 +434,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
480
434
|
// Update time display immediately when metadata loads
|
|
481
435
|
this.updateTimeDisplay();
|
|
482
436
|
|
|
483
|
-
// Render chapter markers if enabled
|
|
484
|
-
this.renderChapterMarkersOnProgressBar();
|
|
485
|
-
|
|
486
437
|
this.emit('onLoadedMetadata', {
|
|
487
438
|
duration: this.video.duration || 0,
|
|
488
439
|
width: this.video.videoWidth || 0,
|
|
@@ -2392,8 +2343,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
2392
2343
|
--uvf-scrollbar-thumb-hover-start: rgba(255,0,0,0.5);
|
|
2393
2344
|
--uvf-scrollbar-thumb-hover-end: rgba(255,0,0,0.6);
|
|
2394
2345
|
--uvf-firefox-scrollbar-color: rgba(255,255,255,0.25);
|
|
2395
|
-
/* Material You surface tint */
|
|
2396
|
-
--uvf-surface-tint: rgba(255, 0, 0, 0.08);
|
|
2397
2346
|
}
|
|
2398
2347
|
|
|
2399
2348
|
/* Player focus styles for better UX */
|
|
@@ -4449,15 +4398,24 @@ export class WebPlayer extends BasePlayer {
|
|
|
4449
4398
|
}
|
|
4450
4399
|
|
|
4451
4400
|
/* Enhanced Responsive Media Queries with UX Best Practices */
|
|
4452
|
-
|
|
4453
|
-
/* Material You Mobile Portrait Layout - 25% Black + 50% Video + 25% Black */
|
|
4401
|
+
/* Mobile devices (portrait) - Material You Design (25-50-25 Layout) */
|
|
4454
4402
|
@media screen and (max-width: 767px) and (orientation: portrait) {
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4403
|
+
.uvf-responsive-container {
|
|
4404
|
+
padding: 0;
|
|
4405
|
+
width: 100vw !important;
|
|
4406
|
+
height: 100vh;
|
|
4407
|
+
height: 100dvh;
|
|
4408
|
+
margin: 0;
|
|
4409
|
+
position: fixed;
|
|
4410
|
+
top: 0;
|
|
4411
|
+
left: 0;
|
|
4412
|
+
overflow: hidden;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
.uvf-responsive-container .uvf-player-wrapper {
|
|
4416
|
+
width: 100vw !important;
|
|
4458
4417
|
height: 100vh;
|
|
4459
4418
|
height: 100dvh;
|
|
4460
|
-
width: 100vw;
|
|
4461
4419
|
position: fixed;
|
|
4462
4420
|
top: 0;
|
|
4463
4421
|
left: 0;
|
|
@@ -4467,8 +4425,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
4467
4425
|
overflow: hidden;
|
|
4468
4426
|
}
|
|
4469
4427
|
|
|
4470
|
-
|
|
4471
|
-
|
|
4428
|
+
/* Video container occupies middle 50% */
|
|
4429
|
+
.uvf-responsive-container .uvf-video-container {
|
|
4472
4430
|
height: 50vh;
|
|
4473
4431
|
height: 50dvh;
|
|
4474
4432
|
width: 100vw;
|
|
@@ -4476,22 +4434,20 @@ export class WebPlayer extends BasePlayer {
|
|
|
4476
4434
|
margin-top: 25vh;
|
|
4477
4435
|
margin-top: 25dvh;
|
|
4478
4436
|
aspect-ratio: unset !important;
|
|
4479
|
-
/* Match existing theme background */
|
|
4480
4437
|
background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
|
|
4481
|
-
/* Material Design elevation */
|
|
4482
4438
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
|
4483
4439
|
0 4px 16px rgba(0, 0, 0, 0.3),
|
|
4484
4440
|
0 2px 8px rgba(0, 0, 0, 0.2);
|
|
4485
4441
|
}
|
|
4486
4442
|
|
|
4487
|
-
.uvf-
|
|
4443
|
+
.uvf-video {
|
|
4488
4444
|
width: 100%;
|
|
4489
4445
|
height: 100%;
|
|
4490
4446
|
object-fit: contain;
|
|
4491
4447
|
}
|
|
4492
4448
|
|
|
4493
|
-
/* Top black section (25%) - Tap zone
|
|
4494
|
-
.uvf-player-wrapper
|
|
4449
|
+
/* Top black section (25%) - Tap zone */
|
|
4450
|
+
.uvf-player-wrapper::before {
|
|
4495
4451
|
content: '';
|
|
4496
4452
|
position: absolute;
|
|
4497
4453
|
top: 0;
|
|
@@ -4505,8 +4461,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
4505
4461
|
touch-action: manipulation;
|
|
4506
4462
|
}
|
|
4507
4463
|
|
|
4508
|
-
/* Bottom black section (25%) -
|
|
4509
|
-
.uvf-player-wrapper
|
|
4464
|
+
/* Bottom black section (25%) - Controls area */
|
|
4465
|
+
.uvf-player-wrapper::after {
|
|
4510
4466
|
content: '';
|
|
4511
4467
|
position: absolute;
|
|
4512
4468
|
bottom: 0;
|
|
@@ -4523,7 +4479,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4523
4479
|
}
|
|
4524
4480
|
|
|
4525
4481
|
/* Material surface container for controls */
|
|
4526
|
-
.uvf-
|
|
4482
|
+
.uvf-controls-bar {
|
|
4527
4483
|
position: absolute;
|
|
4528
4484
|
bottom: 0;
|
|
4529
4485
|
left: 0;
|
|
@@ -4538,13 +4494,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
4538
4494
|
display: flex;
|
|
4539
4495
|
flex-direction: column;
|
|
4540
4496
|
justify-content: flex-end;
|
|
4541
|
-
/* Material Design surface with tint */
|
|
4542
4497
|
backdrop-filter: blur(24px);
|
|
4543
4498
|
-webkit-backdrop-filter: blur(24px);
|
|
4544
4499
|
}
|
|
4545
4500
|
|
|
4546
4501
|
/* Material surface tint overlay */
|
|
4547
|
-
.uvf-
|
|
4502
|
+
.uvf-controls-bar::before {
|
|
4548
4503
|
content: '';
|
|
4549
4504
|
position: absolute;
|
|
4550
4505
|
inset: 0;
|
|
@@ -4555,87 +4510,53 @@ export class WebPlayer extends BasePlayer {
|
|
|
4555
4510
|
}
|
|
4556
4511
|
|
|
4557
4512
|
/* Progress bar with chapter markers */
|
|
4558
|
-
.uvf-
|
|
4513
|
+
.uvf-progress-section {
|
|
4559
4514
|
margin-bottom: 12px;
|
|
4560
4515
|
position: relative;
|
|
4561
4516
|
}
|
|
4562
4517
|
|
|
4563
|
-
.uvf-
|
|
4518
|
+
.uvf-progress-bar-wrapper {
|
|
4564
4519
|
padding: 12px 0;
|
|
4565
4520
|
position: relative;
|
|
4566
4521
|
}
|
|
4567
4522
|
|
|
4568
|
-
.uvf-
|
|
4523
|
+
.uvf-progress-bar {
|
|
4569
4524
|
height: 4px;
|
|
4570
4525
|
background: rgba(255, 255, 255, 0.2);
|
|
4571
4526
|
border-radius: 4px;
|
|
4572
4527
|
position: relative;
|
|
4573
4528
|
overflow: visible;
|
|
4574
|
-
/* Material elevation */
|
|
4575
4529
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
4576
4530
|
}
|
|
4577
4531
|
|
|
4578
|
-
.uvf-
|
|
4532
|
+
.uvf-progress-filled {
|
|
4579
4533
|
background: var(--uvf-accent-1, #ff0000);
|
|
4580
4534
|
box-shadow: 0 0 8px var(--uvf-accent-1, #ff0000);
|
|
4581
4535
|
}
|
|
4582
4536
|
|
|
4583
|
-
.uvf-
|
|
4537
|
+
.uvf-progress-handle {
|
|
4584
4538
|
width: 20px;
|
|
4585
4539
|
height: 20px;
|
|
4586
4540
|
background: var(--uvf-accent-1, #ff0000);
|
|
4587
|
-
/* Material Design state layer */
|
|
4588
4541
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3),
|
|
4589
4542
|
0 0 0 0 var(--uvf-accent-1, #ff0000);
|
|
4590
4543
|
transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4591
4544
|
}
|
|
4592
4545
|
|
|
4593
|
-
.uvf-
|
|
4546
|
+
.uvf-progress-handle:active {
|
|
4594
4547
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4),
|
|
4595
4548
|
0 0 0 12px rgba(255, 0, 0, 0.15);
|
|
4596
4549
|
transform: translate(-50%, -50%) scale(1.2);
|
|
4597
4550
|
}
|
|
4598
4551
|
|
|
4599
|
-
/* Chapter markers on progress bar */
|
|
4600
|
-
.uvf-chapter-marker {
|
|
4601
|
-
position: absolute;
|
|
4602
|
-
top: 0;
|
|
4603
|
-
height: 100%;
|
|
4604
|
-
width: 3px;
|
|
4605
|
-
background: rgba(255, 255, 255, 0.4);
|
|
4606
|
-
border-radius: 2px;
|
|
4607
|
-
transform: translateX(-50%);
|
|
4608
|
-
pointer-events: none;
|
|
4609
|
-
z-index: 1;
|
|
4610
|
-
transition: all 0.2s ease;
|
|
4611
|
-
}
|
|
4612
|
-
|
|
4613
|
-
.uvf-chapter-marker.intro {
|
|
4614
|
-
background: #4CAF50;
|
|
4615
|
-
}
|
|
4616
|
-
|
|
4617
|
-
.uvf-chapter-marker.recap {
|
|
4618
|
-
background: #FFC107;
|
|
4619
|
-
}
|
|
4620
|
-
|
|
4621
|
-
.uvf-chapter-marker.credits {
|
|
4622
|
-
background: #9C27B0;
|
|
4623
|
-
}
|
|
4624
|
-
|
|
4625
|
-
.uvf-progress-bar-wrapper:hover .uvf-chapter-marker {
|
|
4626
|
-
width: 4px;
|
|
4627
|
-
opacity: 1;
|
|
4628
|
-
}
|
|
4629
|
-
|
|
4630
4552
|
/* Material Design control buttons */
|
|
4631
|
-
.uvf-
|
|
4553
|
+
.uvf-control-btn {
|
|
4632
4554
|
width: 48px;
|
|
4633
4555
|
height: 48px;
|
|
4634
4556
|
min-width: 48px;
|
|
4635
4557
|
min-height: 48px;
|
|
4636
4558
|
background: rgba(255, 255, 255, 0.12);
|
|
4637
4559
|
border-radius: 24px;
|
|
4638
|
-
/* Material elevation level 1 */
|
|
4639
4560
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12),
|
|
4640
4561
|
0 1px 2px rgba(0, 0, 0, 0.24);
|
|
4641
4562
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -4644,7 +4565,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4644
4565
|
}
|
|
4645
4566
|
|
|
4646
4567
|
/* Material ripple effect */
|
|
4647
|
-
.uvf-
|
|
4568
|
+
.uvf-control-btn::before {
|
|
4648
4569
|
content: '';
|
|
4649
4570
|
position: absolute;
|
|
4650
4571
|
inset: 0;
|
|
@@ -4654,24 +4575,22 @@ export class WebPlayer extends BasePlayer {
|
|
|
4654
4575
|
transition: opacity 0.2s ease;
|
|
4655
4576
|
}
|
|
4656
4577
|
|
|
4657
|
-
.uvf-
|
|
4578
|
+
.uvf-control-btn:active::before {
|
|
4658
4579
|
opacity: 1;
|
|
4659
4580
|
}
|
|
4660
4581
|
|
|
4661
|
-
.uvf-
|
|
4662
|
-
/* Material elevation level 2 */
|
|
4582
|
+
.uvf-control-btn:active {
|
|
4663
4583
|
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16),
|
|
4664
4584
|
0 3px 6px rgba(0, 0, 0, 0.23);
|
|
4665
4585
|
transform: scale(0.95);
|
|
4666
4586
|
}
|
|
4667
4587
|
|
|
4668
|
-
.uvf-
|
|
4588
|
+
.uvf-control-btn.play-pause {
|
|
4669
4589
|
width: 56px;
|
|
4670
4590
|
height: 56px;
|
|
4671
4591
|
min-width: 56px;
|
|
4672
4592
|
min-height: 56px;
|
|
4673
4593
|
border-radius: 28px;
|
|
4674
|
-
/* Material elevated button */
|
|
4675
4594
|
background: linear-gradient(135deg,
|
|
4676
4595
|
var(--uvf-accent-1, #ff0000),
|
|
4677
4596
|
var(--uvf-accent-2, #ff4d4f));
|
|
@@ -4680,21 +4599,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4680
4599
|
0 0 0 0 var(--uvf-accent-1, #ff0000);
|
|
4681
4600
|
}
|
|
4682
4601
|
|
|
4683
|
-
.uvf-
|
|
4602
|
+
.uvf-control-btn.play-pause:active {
|
|
4684
4603
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25),
|
|
4685
4604
|
0 4px 8px rgba(0, 0, 0, 0.20),
|
|
4686
4605
|
0 0 0 8px rgba(255, 0, 0, 0.12);
|
|
4687
4606
|
}
|
|
4688
4607
|
|
|
4608
|
+
.uvf-control-btn svg {
|
|
4609
|
+
width: 20px;
|
|
4610
|
+
height: 20px;
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
.uvf-control-btn.play-pause svg {
|
|
4614
|
+
width: 24px;
|
|
4615
|
+
height: 24px;
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4689
4618
|
/* Controls row with Material spacing */
|
|
4690
|
-
.uvf-
|
|
4619
|
+
.uvf-controls-row {
|
|
4691
4620
|
gap: 16px;
|
|
4692
4621
|
padding: 0;
|
|
4693
4622
|
align-items: center;
|
|
4694
4623
|
}
|
|
4695
4624
|
|
|
4696
4625
|
/* Time display with Material surface */
|
|
4697
|
-
.uvf-
|
|
4626
|
+
.uvf-time-display {
|
|
4698
4627
|
background: rgba(255, 255, 255, 0.1);
|
|
4699
4628
|
backdrop-filter: blur(8px);
|
|
4700
4629
|
border-radius: 16px;
|
|
@@ -4702,101 +4631,20 @@ export class WebPlayer extends BasePlayer {
|
|
|
4702
4631
|
font-size: 13px;
|
|
4703
4632
|
font-weight: 500;
|
|
4704
4633
|
font-feature-settings: 'tnum';
|
|
4705
|
-
/* Material elevation */
|
|
4706
4634
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
4707
4635
|
}
|
|
4708
4636
|
|
|
4709
|
-
/*
|
|
4710
|
-
.uvf-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
height: 80px;
|
|
4716
|
-
background: rgba(255, 255, 255, 0.2);
|
|
4717
|
-
backdrop-filter: blur(10px);
|
|
4718
|
-
border-radius: 40px;
|
|
4719
|
-
display: flex;
|
|
4720
|
-
align-items: center;
|
|
4721
|
-
justify-content: center;
|
|
4722
|
-
pointer-events: none;
|
|
4723
|
-
opacity: 0;
|
|
4724
|
-
z-index: 100;
|
|
4725
|
-
transition: opacity 0.3s ease;
|
|
4726
|
-
/* Material elevation level 3 */
|
|
4727
|
-
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19),
|
|
4728
|
-
0 6px 6px rgba(0, 0, 0, 0.23);
|
|
4729
|
-
}
|
|
4730
|
-
|
|
4731
|
-
.uvf-doubletap-indicator.left {
|
|
4732
|
-
left: 15%;
|
|
4733
|
-
}
|
|
4734
|
-
|
|
4735
|
-
.uvf-doubletap-indicator.right {
|
|
4736
|
-
right: 15%;
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
|
-
.uvf-doubletap-indicator.active {
|
|
4740
|
-
opacity: 1;
|
|
4741
|
-
animation: doubletap-pulse 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4742
|
-
}
|
|
4743
|
-
|
|
4744
|
-
@keyframes doubletap-pulse {
|
|
4745
|
-
0% {
|
|
4746
|
-
transform: translateY(-50%) scale(0.8);
|
|
4747
|
-
opacity: 0;
|
|
4748
|
-
}
|
|
4749
|
-
50% {
|
|
4750
|
-
transform: translateY(-50%) scale(1.1);
|
|
4751
|
-
opacity: 1;
|
|
4752
|
-
}
|
|
4753
|
-
100% {
|
|
4754
|
-
transform: translateY(-50%) scale(1);
|
|
4755
|
-
opacity: 1;
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
.uvf-doubletap-indicator svg {
|
|
4760
|
-
width: 40px;
|
|
4761
|
-
height: 40px;
|
|
4762
|
-
fill: #fff;
|
|
4763
|
-
}
|
|
4764
|
-
|
|
4765
|
-
/* Long-press 2x speed indicator */
|
|
4766
|
-
.uvf-longpress-indicator {
|
|
4767
|
-
position: absolute;
|
|
4768
|
-
top: 50%;
|
|
4769
|
-
left: 50%;
|
|
4770
|
-
transform: translate(-50%, -50%);
|
|
4771
|
-
background: rgba(0, 0, 0, 0.8);
|
|
4772
|
-
backdrop-filter: blur(16px);
|
|
4773
|
-
padding: 16px 24px;
|
|
4774
|
-
border-radius: 24px;
|
|
4775
|
-
color: #fff;
|
|
4776
|
-
font-size: 18px;
|
|
4777
|
-
font-weight: 600;
|
|
4778
|
-
pointer-events: none;
|
|
4779
|
-
opacity: 0;
|
|
4780
|
-
z-index: 100;
|
|
4781
|
-
transition: opacity 0.2s ease;
|
|
4782
|
-
/* Material elevation level 4 */
|
|
4783
|
-
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
|
|
4784
|
-
0 10px 10px rgba(0, 0, 0, 0.22);
|
|
4785
|
-
}
|
|
4786
|
-
|
|
4787
|
-
.uvf-longpress-indicator.active {
|
|
4788
|
-
opacity: 1;
|
|
4789
|
-
}
|
|
4790
|
-
|
|
4791
|
-
/* Hide desktop elements in Material You mode */
|
|
4792
|
-
.uvf-player-wrapper.uvf-material-you-mobile .uvf-top-controls,
|
|
4793
|
-
.uvf-player-wrapper.uvf-material-you-mobile .uvf-title-bar,
|
|
4794
|
-
.uvf-player-wrapper.uvf-material-you-mobile .uvf-volume-control {
|
|
4637
|
+
/* Hide desktop elements */
|
|
4638
|
+
.uvf-top-controls,
|
|
4639
|
+
.uvf-title-bar,
|
|
4640
|
+
.uvf-volume-control,
|
|
4641
|
+
#uvf-skip-back,
|
|
4642
|
+
#uvf-skip-forward {
|
|
4795
4643
|
display: none !important;
|
|
4796
4644
|
}
|
|
4797
4645
|
|
|
4798
4646
|
/* Optimize settings button for Material You */
|
|
4799
|
-
|
|
4647
|
+
#uvf-settings-btn {
|
|
4800
4648
|
width: 48px !important;
|
|
4801
4649
|
height: 48px !important;
|
|
4802
4650
|
min-width: 48px !important;
|
|
@@ -4804,405 +4652,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4804
4652
|
border-radius: 24px !important;
|
|
4805
4653
|
}
|
|
4806
4654
|
}
|
|
4807
|
-
|
|
4808
|
-
/* Mobile devices (portrait) - Enhanced UX with Safe Areas */
|
|
4809
|
-
@media screen and (max-width: 767px) and (orientation: portrait) {
|
|
4810
|
-
.uvf-responsive-container {
|
|
4811
|
-
padding: 0;
|
|
4812
|
-
width: 100vw !important;
|
|
4813
|
-
height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
|
|
4814
|
-
margin: 0;
|
|
4815
|
-
position: relative;
|
|
4816
|
-
overflow: hidden;
|
|
4817
|
-
}
|
|
4818
|
-
|
|
4819
|
-
@supports (height: 100dvh) {
|
|
4820
|
-
.uvf-responsive-container {
|
|
4821
|
-
height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
|
|
4822
|
-
}
|
|
4823
|
-
}
|
|
4824
|
-
|
|
4825
|
-
.uvf-responsive-container .uvf-player-wrapper {
|
|
4826
|
-
width: 100vw !important;
|
|
4827
|
-
height: 100% !important;
|
|
4828
|
-
min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
|
|
4829
|
-
}
|
|
4830
|
-
|
|
4831
|
-
@supports (height: 100dvh) {
|
|
4832
|
-
.uvf-responsive-container .uvf-player-wrapper {
|
|
4833
|
-
min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
|
|
4834
|
-
}
|
|
4835
|
-
}
|
|
4836
|
-
|
|
4837
|
-
.uvf-responsive-container .uvf-video-container {
|
|
4838
|
-
width: 100vw !important;
|
|
4839
|
-
height: 100% !important;
|
|
4840
|
-
aspect-ratio: unset !important;
|
|
4841
|
-
min-height: inherit;
|
|
4842
|
-
}
|
|
4843
|
-
|
|
4844
|
-
/* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
|
|
4845
|
-
.uvf-controls-bar {
|
|
4846
|
-
position: absolute !important;
|
|
4847
|
-
bottom: 0 !important;
|
|
4848
|
-
left: 0 !important;
|
|
4849
|
-
right: 0 !important;
|
|
4850
|
-
padding: 16px 12px;
|
|
4851
|
-
padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
|
|
4852
|
-
padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
|
|
4853
|
-
padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
|
|
4854
|
-
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
|
|
4855
|
-
box-sizing: border-box;
|
|
4856
|
-
z-index: 1000 !important;
|
|
4857
|
-
/* iOS Safari specific fixes */
|
|
4858
|
-
transform: translateZ(0);
|
|
4859
|
-
-webkit-transform: translateZ(0);
|
|
4860
|
-
will-change: transform;
|
|
4861
|
-
/* Ensure proper stacking */
|
|
4862
|
-
isolation: isolate;
|
|
4863
|
-
}
|
|
4864
|
-
|
|
4865
|
-
.uvf-progress-section {
|
|
4866
|
-
margin-bottom: 16px;
|
|
4867
|
-
}
|
|
4868
|
-
|
|
4869
|
-
/* Mobile-first responsive controls layout */
|
|
4870
|
-
.uvf-controls-row {
|
|
4871
|
-
gap: 8px;
|
|
4872
|
-
flex-wrap: nowrap;
|
|
4873
|
-
align-items: center;
|
|
4874
|
-
justify-content: space-between;
|
|
4875
|
-
position: relative;
|
|
4876
|
-
width: 100%;
|
|
4877
|
-
}
|
|
4878
|
-
|
|
4879
|
-
/* Left side controls group */
|
|
4880
|
-
.uvf-left-controls {
|
|
4881
|
-
display: flex;
|
|
4882
|
-
align-items: center;
|
|
4883
|
-
gap: 8px;
|
|
4884
|
-
flex-shrink: 0;
|
|
4885
|
-
}
|
|
4886
|
-
|
|
4887
|
-
/* Center controls group */
|
|
4888
|
-
.uvf-center-controls {
|
|
4889
|
-
display: flex;
|
|
4890
|
-
align-items: center;
|
|
4891
|
-
gap: 8px;
|
|
4892
|
-
flex: 1;
|
|
4893
|
-
justify-content: center;
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
|
-
/* Mobile control groups reordering */
|
|
4897
|
-
.uvf-controls-row .uvf-control-btn.play-pause,
|
|
4898
|
-
.uvf-controls-row #uvf-skip-back,
|
|
4899
|
-
.uvf-controls-row #uvf-skip-forward {
|
|
4900
|
-
order: 1;
|
|
4901
|
-
}
|
|
4902
|
-
|
|
4903
|
-
.uvf-controls-row .uvf-volume-control {
|
|
4904
|
-
order: 2;
|
|
4905
|
-
}
|
|
4906
|
-
|
|
4907
|
-
.uvf-controls-row .uvf-time-display:not(.uvf-above-seekbar) {
|
|
4908
|
-
order: 3;
|
|
4909
|
-
margin-left: auto;
|
|
4910
|
-
margin-right: 8px;
|
|
4911
|
-
}
|
|
4912
|
-
|
|
4913
|
-
.uvf-controls-row .uvf-right-controls {
|
|
4914
|
-
order: 4;
|
|
4915
|
-
margin-left: 0;
|
|
4916
|
-
}
|
|
4917
|
-
|
|
4918
|
-
/* Touch-friendly control sizing (minimum 44px touch target) */
|
|
4919
|
-
.uvf-control-btn {
|
|
4920
|
-
width: 44px;
|
|
4921
|
-
height: 44px;
|
|
4922
|
-
min-width: 44px;
|
|
4923
|
-
min-height: 44px;
|
|
4924
|
-
border-radius: 22px;
|
|
4925
|
-
background: rgba(255,255,255,0.15);
|
|
4926
|
-
backdrop-filter: blur(8px);
|
|
4927
|
-
}
|
|
4928
|
-
|
|
4929
|
-
.uvf-control-btn.play-pause {
|
|
4930
|
-
width: 52px;
|
|
4931
|
-
height: 52px;
|
|
4932
|
-
min-width: 52px;
|
|
4933
|
-
min-height: 52px;
|
|
4934
|
-
border-radius: 26px;
|
|
4935
|
-
background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
|
|
4936
|
-
box-shadow: 0 4px 12px rgba(var(--uvf-accent-1), 0.3);
|
|
4937
|
-
}
|
|
4938
|
-
|
|
4939
|
-
.uvf-control-btn svg {
|
|
4940
|
-
width: 20px;
|
|
4941
|
-
height: 20px;
|
|
4942
|
-
}
|
|
4943
|
-
|
|
4944
|
-
.uvf-control-btn.play-pause svg {
|
|
4945
|
-
width: 24px;
|
|
4946
|
-
height: 24px;
|
|
4947
|
-
}
|
|
4948
|
-
|
|
4949
|
-
/* Skip buttons with clear visual hierarchy */
|
|
4950
|
-
#uvf-skip-back,
|
|
4951
|
-
#uvf-skip-forward {
|
|
4952
|
-
background: rgba(255,255,255,0.12);
|
|
4953
|
-
}
|
|
4954
|
-
|
|
4955
|
-
#uvf-skip-back svg,
|
|
4956
|
-
#uvf-skip-forward svg {
|
|
4957
|
-
width: 22px;
|
|
4958
|
-
height: 22px;
|
|
4959
|
-
}
|
|
4960
|
-
|
|
4961
|
-
/* Mobile time display - compact but readable */
|
|
4962
|
-
.uvf-time-display:not(.uvf-above-seekbar) {
|
|
4963
|
-
font-size: 12px;
|
|
4964
|
-
font-weight: 600;
|
|
4965
|
-
padding: 0 6px;
|
|
4966
|
-
text-align: center;
|
|
4967
|
-
background: rgba(0,0,0,0.3);
|
|
4968
|
-
border-radius: 12px;
|
|
4969
|
-
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
|
4970
|
-
flex-shrink: 0;
|
|
4971
|
-
}
|
|
4972
|
-
|
|
4973
|
-
/* Above-seekbar time display for mobile */
|
|
4974
|
-
.uvf-time-display.uvf-above-seekbar {
|
|
4975
|
-
font-size: 12px !important;
|
|
4976
|
-
font-weight: 500 !important;
|
|
4977
|
-
padding: 3px 6px !important;
|
|
4978
|
-
background: rgba(0,0,0,0.4) !important;
|
|
4979
|
-
border-radius: 10px !important;
|
|
4980
|
-
text-shadow: 0 1px 2px rgba(0,0,0,0.8) !important;
|
|
4981
|
-
backdrop-filter: blur(4px) !important;
|
|
4982
|
-
border: 1px solid rgba(255,255,255,0.1) !important;
|
|
4983
|
-
}
|
|
4984
|
-
|
|
4985
|
-
/* Simplified volume control for mobile */
|
|
4986
|
-
.uvf-volume-control {
|
|
4987
|
-
order: 3;
|
|
4988
|
-
position: relative;
|
|
4989
|
-
}
|
|
4990
|
-
|
|
4991
|
-
/* Hide volume panel on mobile - use device controls */
|
|
4992
|
-
.uvf-volume-panel {
|
|
4993
|
-
display: none;
|
|
4994
|
-
}
|
|
4995
|
-
|
|
4996
|
-
/* Mobile volume button as simple mute toggle */
|
|
4997
|
-
.uvf-volume-control .uvf-control-btn {
|
|
4998
|
-
width: 44px;
|
|
4999
|
-
height: 44px;
|
|
5000
|
-
}
|
|
5001
|
-
|
|
5002
|
-
/* Compact right controls for mobile */
|
|
5003
|
-
.uvf-right-controls {
|
|
5004
|
-
gap: 6px;
|
|
5005
|
-
display: flex;
|
|
5006
|
-
align-items: center;
|
|
5007
|
-
flex-shrink: 0;
|
|
5008
|
-
position: relative;
|
|
5009
|
-
z-index: 10;
|
|
5010
|
-
}
|
|
5011
|
-
|
|
5012
|
-
/* Ensure settings container is visible */
|
|
5013
|
-
.uvf-right-controls > div[style*="position: relative"],
|
|
5014
|
-
.uvf-settings-container {
|
|
5015
|
-
display: flex !important;
|
|
5016
|
-
position: relative !important;
|
|
5017
|
-
align-items: center !important;
|
|
5018
|
-
justify-content: center !important;
|
|
5019
|
-
min-width: 44px !important;
|
|
5020
|
-
min-height: 44px !important;
|
|
5021
|
-
}
|
|
5022
|
-
|
|
5023
|
-
/* Remove quality badge completely - not essential */
|
|
5024
|
-
.uvf-quality-badge {
|
|
5025
|
-
display: none !important;
|
|
5026
|
-
}
|
|
5027
|
-
|
|
5028
|
-
/* Settings menu - hidden by default, accessible via menu */
|
|
5029
|
-
.uvf-settings-menu {
|
|
5030
|
-
min-width: 160px;
|
|
5031
|
-
bottom: 60px;
|
|
5032
|
-
right: 12px;
|
|
5033
|
-
font-size: 14px;
|
|
5034
|
-
max-height: 50vh;
|
|
5035
|
-
}
|
|
5036
|
-
|
|
5037
|
-
.uvf-settings-option {
|
|
5038
|
-
padding: 12px 16px;
|
|
5039
|
-
font-size: 14px;
|
|
5040
|
-
min-height: 44px;
|
|
5041
|
-
display: flex;
|
|
5042
|
-
align-items: center;
|
|
5043
|
-
}
|
|
5044
|
-
|
|
5045
|
-
.uvf-settings-option:hover {
|
|
5046
|
-
background: rgba(255,255,255,0.15);
|
|
5047
|
-
padding-left: 20px;
|
|
5048
|
-
}
|
|
5049
|
-
|
|
5050
|
-
/* Simplified settings - hide complex options */
|
|
5051
|
-
.uvf-settings-group:first-child .uvf-settings-option[data-speed="0.5"],
|
|
5052
|
-
.uvf-settings-group:first-child .uvf-settings-option[data-speed="0.75"],
|
|
5053
|
-
.uvf-settings-group:first-child .uvf-settings-option[data-speed="2"] {
|
|
5054
|
-
display: none;
|
|
5055
|
-
}
|
|
5056
|
-
}
|
|
5057
|
-
|
|
5058
|
-
/* Enhanced top controls for mobile with safe area support */
|
|
5059
|
-
.uvf-top-controls {
|
|
5060
|
-
position: absolute;
|
|
5061
|
-
top: calc(12px + var(--uvf-safe-area-top));
|
|
5062
|
-
right: calc(12px + var(--uvf-safe-area-right));
|
|
5063
|
-
display: flex;
|
|
5064
|
-
align-items: center;
|
|
5065
|
-
gap: 8px;
|
|
5066
|
-
z-index: 10;
|
|
5067
|
-
}
|
|
5068
|
-
|
|
5069
|
-
/* Touch-friendly top buttons */
|
|
5070
|
-
.uvf-top-btn {
|
|
5071
|
-
width: 44px;
|
|
5072
|
-
height: 44px;
|
|
5073
|
-
min-width: 44px;
|
|
5074
|
-
min-height: 44px;
|
|
5075
|
-
background: rgba(0,0,0,0.7);
|
|
5076
|
-
backdrop-filter: blur(10px);
|
|
5077
|
-
border: 1px solid rgba(255,255,255,0.2);
|
|
5078
|
-
}
|
|
5079
|
-
|
|
5080
|
-
.uvf-top-btn svg {
|
|
5081
|
-
width: 20px;
|
|
5082
|
-
height: 20px;
|
|
5083
|
-
}
|
|
5084
|
-
|
|
5085
|
-
/* Share button - keep visible on all devices */
|
|
5086
|
-
#uvf-share-btn {
|
|
5087
|
-
display: flex;
|
|
5088
|
-
}
|
|
5089
|
-
|
|
5090
|
-
/* Enhanced title bar for mobile with safe area support */
|
|
5091
|
-
.uvf-title-bar {
|
|
5092
|
-
padding: 12px;
|
|
5093
|
-
padding-top: calc(12px + var(--uvf-safe-area-top));
|
|
5094
|
-
padding-left: calc(12px + var(--uvf-safe-area-left));
|
|
5095
|
-
padding-right: calc(12px + var(--uvf-safe-area-right));
|
|
5096
|
-
}
|
|
5097
|
-
|
|
5098
|
-
.uvf-video-title {
|
|
5099
|
-
font-size: 16px;
|
|
5100
|
-
font-weight: 700;
|
|
5101
|
-
line-height: 1.2;
|
|
5102
|
-
}
|
|
5103
|
-
|
|
5104
|
-
.uvf-video-subtitle {
|
|
5105
|
-
font-size: 12px;
|
|
5106
|
-
margin-top: 4px;
|
|
5107
|
-
opacity: 0.8;
|
|
5108
|
-
}
|
|
5109
|
-
|
|
5110
|
-
.uvf-video-thumb {
|
|
5111
|
-
width: 48px;
|
|
5112
|
-
height: 48px;
|
|
5113
|
-
border-radius: 6px;
|
|
5114
|
-
}
|
|
5115
|
-
|
|
5116
|
-
/* Touch-optimized center play button - uses same themed style as desktop */
|
|
5117
|
-
.uvf-center-play-btn {
|
|
5118
|
-
width: clamp(72px, 18vw, 96px);
|
|
5119
|
-
height: clamp(72px, 18vw, 96px);
|
|
5120
|
-
background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
|
|
5121
|
-
border: 0;
|
|
5122
|
-
box-shadow: 0 10px 30px var(--uvf-accent-1-20);
|
|
5123
|
-
}
|
|
5124
|
-
|
|
5125
|
-
.uvf-center-play-btn:hover {
|
|
5126
|
-
transform: scale(1.06);
|
|
5127
|
-
filter: saturate(1.08) brightness(1.05);
|
|
5128
|
-
box-shadow: 0 14px 36px var(--uvf-accent-1-20);
|
|
5129
|
-
}
|
|
5130
|
-
|
|
5131
|
-
.uvf-center-play-btn svg {
|
|
5132
|
-
width: clamp(28px, 5.2vw, 38px);
|
|
5133
|
-
height: clamp(28px, 5.2vw, 38px);
|
|
5134
|
-
margin-left: 4px;
|
|
5135
|
-
}
|
|
5136
|
-
|
|
5137
|
-
/* Enhanced progress bar for touch */
|
|
5138
|
-
.uvf-progress-bar {
|
|
5139
|
-
height: 3px;
|
|
5140
|
-
margin-bottom: 12px;
|
|
5141
|
-
border-radius: 4px;
|
|
5142
|
-
background: rgba(255,255,255,0.15);
|
|
5143
|
-
position: relative;
|
|
5144
|
-
}
|
|
5145
|
-
|
|
5146
|
-
/* Larger touch target for progress bar */
|
|
5147
|
-
.uvf-progress-bar-wrapper::before {
|
|
5148
|
-
content: '';
|
|
5149
|
-
position: absolute;
|
|
5150
|
-
top: -10px;
|
|
5151
|
-
left: 0;
|
|
5152
|
-
right: 0;
|
|
5153
|
-
bottom: -10px;
|
|
5154
|
-
z-index: 1;
|
|
5155
|
-
}
|
|
5156
|
-
|
|
5157
|
-
.uvf-progress-handle {
|
|
5158
|
-
width: 18px;
|
|
5159
|
-
height: 18px;
|
|
5160
|
-
background: #fff;
|
|
5161
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
5162
|
-
}
|
|
5163
|
-
|
|
5164
|
-
.uvf-progress-bar-wrapper:active .uvf-progress-handle {
|
|
5165
|
-
transform: translate(-50%, -50%) scale(1.2);
|
|
5166
|
-
}
|
|
5167
|
-
|
|
5168
|
-
/* Mobile accessibility improvements */
|
|
5169
|
-
.uvf-control-btn,
|
|
5170
|
-
.uvf-top-btn {
|
|
5171
|
-
position: relative;
|
|
5172
|
-
overflow: visible;
|
|
5173
|
-
}
|
|
5174
|
-
|
|
5175
|
-
/* Enhanced focus states for mobile */
|
|
5176
|
-
.uvf-control-btn:focus,
|
|
5177
|
-
.uvf-top-btn:focus {
|
|
5178
|
-
outline: 2px solid var(--uvf-accent-1);
|
|
5179
|
-
outline-offset: 2px;
|
|
5180
|
-
}
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
/* Show PiP on all devices - modern mobile browsers support it well */
|
|
5184
|
-
#uvf-pip-btn {
|
|
5185
|
-
display: block;
|
|
5186
|
-
background: rgba(255,255,255,0.12);
|
|
5187
|
-
border: 1px solid rgba(255,255,255,0.1);
|
|
5188
|
-
}
|
|
5189
|
-
|
|
5190
|
-
/* Essential controls in right section - Settings, PiP, and Fullscreen only */
|
|
5191
|
-
.uvf-right-controls > *:not(#uvf-settings-btn):not(#uvf-fullscreen-btn):not(#uvf-pip-btn) {
|
|
5192
|
-
display: none;
|
|
5193
|
-
}
|
|
5194
|
-
|
|
5195
|
-
/* Ensure settings button is always visible and properly sized on mobile */
|
|
5196
|
-
#uvf-settings-btn {
|
|
5197
|
-
display: flex !important;
|
|
5198
|
-
width: 44px !important;
|
|
5199
|
-
height: 44px !important;
|
|
5200
|
-
min-width: 44px !important;
|
|
5201
|
-
min-height: 44px !important;
|
|
5202
|
-
backdrop-filter: blur(8px) !important;
|
|
5203
|
-
border-radius: 22px !important;
|
|
5204
|
-
align-items: center !important;
|
|
5205
|
-
justify-content: center !important;
|
|
5206
4655
|
}
|
|
5207
4656
|
|
|
5208
4657
|
#uvf-settings-btn svg {
|
|
@@ -6130,26 +5579,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
6130
5579
|
shortcutIndicator.className = 'uvf-shortcut-indicator';
|
|
6131
5580
|
shortcutIndicator.id = 'uvf-shortcut-indicator';
|
|
6132
5581
|
container.appendChild(shortcutIndicator);
|
|
6133
|
-
|
|
6134
|
-
// Add Material You double-tap indicators
|
|
6135
|
-
const doubleTapLeft = document.createElement('div');
|
|
6136
|
-
doubleTapLeft.className = 'uvf-doubletap-indicator left';
|
|
6137
|
-
doubleTapLeft.id = 'uvf-doubletap-left';
|
|
6138
|
-
doubleTapLeft.innerHTML = '<svg viewBox="0 0 24 24"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/></svg><div style="margin-top:4px;font-size:14px;font-weight:600;">-10s</div>';
|
|
6139
|
-
container.appendChild(doubleTapLeft);
|
|
6140
|
-
|
|
6141
|
-
const doubleTapRight = document.createElement('div');
|
|
6142
|
-
doubleTapRight.className = 'uvf-doubletap-indicator right';
|
|
6143
|
-
doubleTapRight.id = 'uvf-doubletap-right';
|
|
6144
|
-
doubleTapRight.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/></svg><div style="margin-top:4px;font-size:14px;font-weight:600;">+10s</div>';
|
|
6145
|
-
container.appendChild(doubleTapRight);
|
|
6146
|
-
|
|
6147
|
-
// Add Material You long-press indicator
|
|
6148
|
-
const longPressIndicator = document.createElement('div');
|
|
6149
|
-
longPressIndicator.className = 'uvf-longpress-indicator';
|
|
6150
|
-
longPressIndicator.id = 'uvf-longpress-indicator';
|
|
6151
|
-
longPressIndicator.textContent = '2x';
|
|
6152
|
-
container.appendChild(longPressIndicator);
|
|
6153
5582
|
|
|
6154
5583
|
// Create controls bar
|
|
6155
5584
|
const controlsBar = document.createElement('div');
|
|
@@ -6443,9 +5872,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
6443
5872
|
// Play/Pause
|
|
6444
5873
|
centerPlay?.addEventListener('click', () => this.togglePlayPause());
|
|
6445
5874
|
playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
|
|
6446
|
-
|
|
6447
|
-
// Material You touch gestures for mobile
|
|
6448
|
-
this.setupMaterialYouGestures(wrapper);
|
|
5875
|
+
this.video.addEventListener('click', () => this.togglePlayPause());
|
|
6449
5876
|
|
|
6450
5877
|
// Update play/pause icons
|
|
6451
5878
|
this.video.addEventListener('play', () => {
|
|
@@ -6820,12 +6247,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
6820
6247
|
|
|
6821
6248
|
// Force visibility if menu is active, hide if not active
|
|
6822
6249
|
if (settingsMenu) {
|
|
6823
|
-
|
|
6824
|
-
if (activating) {
|
|
6825
|
-
this.isSettingsOpen = true;
|
|
6826
|
-
// Keep controls visible and interactive while menu is open
|
|
6827
|
-
this.showControls();
|
|
6828
|
-
if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
|
|
6250
|
+
if (settingsMenu.classList.contains('active')) {
|
|
6829
6251
|
settingsMenu.style.display = 'block';
|
|
6830
6252
|
settingsMenu.style.visibility = 'visible';
|
|
6831
6253
|
settingsMenu.style.opacity = '1';
|
|
@@ -6841,13 +6263,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
6841
6263
|
settingsMenu.style.padding = '10px 0';
|
|
6842
6264
|
this.debugLog('Applied fallback styles to show menu');
|
|
6843
6265
|
} else {
|
|
6844
|
-
this.isSettingsOpen = false;
|
|
6845
6266
|
settingsMenu.style.display = 'none';
|
|
6846
6267
|
settingsMenu.style.visibility = 'hidden';
|
|
6847
6268
|
settingsMenu.style.opacity = '0';
|
|
6848
6269
|
this.debugLog('Applied fallback styles to hide menu');
|
|
6849
|
-
// After closing, allow auto-hide again if playing
|
|
6850
|
-
this.scheduleHideControls();
|
|
6851
6270
|
}
|
|
6852
6271
|
}
|
|
6853
6272
|
|
|
@@ -6887,19 +6306,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
6887
6306
|
}
|
|
6888
6307
|
});
|
|
6889
6308
|
|
|
6890
|
-
// Keep controls active while interacting with settings menu
|
|
6891
|
-
if (settingsMenu) {
|
|
6892
|
-
const keepAlive = () => {
|
|
6893
|
-
if (!this.isSettingsOpen) return;
|
|
6894
|
-
this.showControls();
|
|
6895
|
-
if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
|
|
6896
|
-
};
|
|
6897
|
-
settingsMenu.addEventListener('mouseenter', keepAlive);
|
|
6898
|
-
settingsMenu.addEventListener('mousemove', keepAlive);
|
|
6899
|
-
settingsMenu.addEventListener('touchstart', keepAlive, { passive: true } as any);
|
|
6900
|
-
settingsMenu.addEventListener('touchmove', keepAlive, { passive: true } as any);
|
|
6901
|
-
}
|
|
6902
|
-
|
|
6903
6309
|
// Add Escape key handler for settings menu
|
|
6904
6310
|
document.addEventListener('keydown', (e) => {
|
|
6905
6311
|
if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
|
|
@@ -7411,267 +6817,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
7411
6817
|
}
|
|
7412
6818
|
}
|
|
7413
6819
|
|
|
7414
|
-
/**
|
|
7415
|
-
* Setup Material You touch gestures for mobile
|
|
7416
|
-
*/
|
|
7417
|
-
private setupMaterialYouGestures(wrapper: HTMLElement | null): void {
|
|
7418
|
-
if (!wrapper || !this.video) return;
|
|
7419
|
-
|
|
7420
|
-
const doubleTapLeft = document.getElementById('uvf-doubletap-left');
|
|
7421
|
-
const doubleTapRight = document.getElementById('uvf-doubletap-right');
|
|
7422
|
-
const longPressIndicator = document.getElementById('uvf-longpress-indicator');
|
|
7423
|
-
|
|
7424
|
-
// Single tap, double tap, and long press handling
|
|
7425
|
-
const videoElement = this.video;
|
|
7426
|
-
const videoContainer = wrapper.querySelector('.uvf-video-container');
|
|
7427
|
-
|
|
7428
|
-
if (!videoContainer) return;
|
|
7429
|
-
|
|
7430
|
-
// Touch start for long-press detection
|
|
7431
|
-
videoContainer.addEventListener('touchstart', (e: Event) => {
|
|
7432
|
-
const touchEvent = e as TouchEvent;
|
|
7433
|
-
const touch = touchEvent.touches[0];
|
|
7434
|
-
|
|
7435
|
-
if (!touch || !videoElement) return;
|
|
7436
|
-
|
|
7437
|
-
this.longPressStartTime = Date.now();
|
|
7438
|
-
|
|
7439
|
-
// Start long-press timer
|
|
7440
|
-
this.longPressTimer = setTimeout(() => {
|
|
7441
|
-
this.longPressActive = true;
|
|
7442
|
-
this.originalPlaybackRate = videoElement.playbackRate;
|
|
7443
|
-
videoElement.playbackRate = 2.0;
|
|
7444
|
-
|
|
7445
|
-
// Show indicator
|
|
7446
|
-
if (longPressIndicator) {
|
|
7447
|
-
longPressIndicator.classList.add('active');
|
|
7448
|
-
}
|
|
7449
|
-
}, 500); // 500ms for long press
|
|
7450
|
-
}, { passive: true });
|
|
7451
|
-
|
|
7452
|
-
// Touch end to handle tap/double-tap and stop long-press
|
|
7453
|
-
videoContainer.addEventListener('touchend', (e: Event) => {
|
|
7454
|
-
const touchEvent = e as TouchEvent;
|
|
7455
|
-
const touch = touchEvent.changedTouches[0];
|
|
7456
|
-
|
|
7457
|
-
if (!touch || !videoElement) return;
|
|
7458
|
-
|
|
7459
|
-
// Clear long-press timer
|
|
7460
|
-
if (this.longPressTimer) {
|
|
7461
|
-
clearTimeout(this.longPressTimer);
|
|
7462
|
-
this.longPressTimer = null;
|
|
7463
|
-
}
|
|
7464
|
-
|
|
7465
|
-
// If long-press was active, restore playback rate
|
|
7466
|
-
if (this.longPressActive) {
|
|
7467
|
-
videoElement.playbackRate = this.originalPlaybackRate;
|
|
7468
|
-
this.longPressActive = false;
|
|
7469
|
-
|
|
7470
|
-
if (longPressIndicator) {
|
|
7471
|
-
longPressIndicator.classList.remove('active');
|
|
7472
|
-
}
|
|
7473
|
-
return; // Don't process as tap
|
|
7474
|
-
}
|
|
7475
|
-
|
|
7476
|
-
// Tap detection
|
|
7477
|
-
const now = Date.now();
|
|
7478
|
-
const timeSinceLastTap = now - this.lastTapTime;
|
|
7479
|
-
|
|
7480
|
-
if (timeSinceLastTap < 300) { // Double tap detected (within 300ms)
|
|
7481
|
-
// Clear single tap timeout
|
|
7482
|
-
if (this.tapTimeout) {
|
|
7483
|
-
clearTimeout(this.tapTimeout);
|
|
7484
|
-
this.tapTimeout = null;
|
|
7485
|
-
}
|
|
7486
|
-
|
|
7487
|
-
// Determine if left or right side
|
|
7488
|
-
const rect = videoContainer.getBoundingClientRect();
|
|
7489
|
-
const x = touch.clientX - rect.left;
|
|
7490
|
-
const isLeftSide = x < rect.width / 2;
|
|
7491
|
-
|
|
7492
|
-
if (isLeftSide) {
|
|
7493
|
-
// Skip backward 10s
|
|
7494
|
-
this.seek(videoElement.currentTime - 10);
|
|
7495
|
-
|
|
7496
|
-
// Show indicator
|
|
7497
|
-
if (doubleTapLeft) {
|
|
7498
|
-
doubleTapLeft.classList.add('active');
|
|
7499
|
-
setTimeout(() => {
|
|
7500
|
-
doubleTapLeft.classList.remove('active');
|
|
7501
|
-
}, 400);
|
|
7502
|
-
}
|
|
7503
|
-
} else {
|
|
7504
|
-
// Skip forward 10s
|
|
7505
|
-
this.seek(videoElement.currentTime + 10);
|
|
7506
|
-
|
|
7507
|
-
// Show indicator
|
|
7508
|
-
if (doubleTapRight) {
|
|
7509
|
-
doubleTapRight.classList.add('active');
|
|
7510
|
-
setTimeout(() => {
|
|
7511
|
-
doubleTapRight.classList.remove('active');
|
|
7512
|
-
}, 400);
|
|
7513
|
-
}
|
|
7514
|
-
}
|
|
7515
|
-
|
|
7516
|
-
this.tapCount = 0;
|
|
7517
|
-
this.lastTapTime = 0;
|
|
7518
|
-
} else {
|
|
7519
|
-
// First tap
|
|
7520
|
-
this.tapCount = 1;
|
|
7521
|
-
this.lastTapTime = now;
|
|
7522
|
-
|
|
7523
|
-
// Wait for potential second tap
|
|
7524
|
-
this.tapTimeout = setTimeout(() => {
|
|
7525
|
-
if (this.tapCount === 1) {
|
|
7526
|
-
// Single tap - toggle overlay controls visibility
|
|
7527
|
-
this.toggleControls();
|
|
7528
|
-
}
|
|
7529
|
-
this.tapCount = 0;
|
|
7530
|
-
}, 300); // Wait 300ms for double tap
|
|
7531
|
-
}
|
|
7532
|
-
}, { passive: true });
|
|
7533
|
-
|
|
7534
|
-
// Touch move/cancel to reset long-press
|
|
7535
|
-
videoContainer.addEventListener('touchmove', () => {
|
|
7536
|
-
if (this.longPressTimer) {
|
|
7537
|
-
clearTimeout(this.longPressTimer);
|
|
7538
|
-
this.longPressTimer = null;
|
|
7539
|
-
}
|
|
7540
|
-
}, { passive: true });
|
|
7541
|
-
|
|
7542
|
-
videoContainer.addEventListener('touchcancel', () => {
|
|
7543
|
-
if (this.longPressTimer) {
|
|
7544
|
-
clearTimeout(this.longPressTimer);
|
|
7545
|
-
this.longPressTimer = null;
|
|
7546
|
-
}
|
|
7547
|
-
|
|
7548
|
-
if (this.longPressActive && videoElement) {
|
|
7549
|
-
videoElement.playbackRate = this.originalPlaybackRate;
|
|
7550
|
-
this.longPressActive = false;
|
|
7551
|
-
|
|
7552
|
-
if (longPressIndicator) {
|
|
7553
|
-
longPressIndicator.classList.remove('active');
|
|
7554
|
-
}
|
|
7555
|
-
}
|
|
7556
|
-
}, { passive: true });
|
|
7557
|
-
}
|
|
7558
|
-
|
|
7559
|
-
/**
|
|
7560
|
-
* Toggle controls visibility
|
|
7561
|
-
*/
|
|
7562
|
-
private toggleControls(): void {
|
|
7563
|
-
const wrapper = this.container?.querySelector('.uvf-player-wrapper');
|
|
7564
|
-
if (wrapper) {
|
|
7565
|
-
if (wrapper.classList.contains('controls-visible')) {
|
|
7566
|
-
this.hideControls();
|
|
7567
|
-
} else {
|
|
7568
|
-
this.showControls();
|
|
7569
|
-
// Auto-hide after delay if playing
|
|
7570
|
-
if (this.state.isPlaying) {
|
|
7571
|
-
this.scheduleHideControls();
|
|
7572
|
-
}
|
|
7573
|
-
}
|
|
7574
|
-
}
|
|
7575
|
-
}
|
|
7576
|
-
|
|
7577
|
-
/**
|
|
7578
|
-
* Apply dynamic color theming based on video content (Material You)
|
|
7579
|
-
* This can be enhanced to extract colors from video frames
|
|
7580
|
-
*/
|
|
7581
|
-
private applyDynamicTheming(primaryColor?: string): void {
|
|
7582
|
-
if (!this.playerWrapper) return;
|
|
7583
|
-
|
|
7584
|
-
const color = primaryColor || this.dominantColor;
|
|
7585
|
-
const rgb = this.hexToRgb(color);
|
|
7586
|
-
|
|
7587
|
-
if (rgb) {
|
|
7588
|
-
// Update CSS variables for Material You theming
|
|
7589
|
-
this.playerWrapper.style.setProperty('--uvf-accent-1', color);
|
|
7590
|
-
this.playerWrapper.style.setProperty('--uvf-accent-2', this.lightenColor(color, 10));
|
|
7591
|
-
this.playerWrapper.style.setProperty('--uvf-surface-tint', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.08)`);
|
|
7592
|
-
|
|
7593
|
-
this.debugLog(`Applied dynamic theming with color: ${color}`);
|
|
7594
|
-
}
|
|
7595
|
-
}
|
|
7596
|
-
|
|
7597
|
-
/**
|
|
7598
|
-
* Convert hex color to RGB
|
|
7599
|
-
*/
|
|
7600
|
-
private hexToRgb(hex: string): { r: number, g: number, b: number } | null {
|
|
7601
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
7602
|
-
return result ? {
|
|
7603
|
-
r: parseInt(result[1], 16),
|
|
7604
|
-
g: parseInt(result[2], 16),
|
|
7605
|
-
b: parseInt(result[3], 16)
|
|
7606
|
-
} : null;
|
|
7607
|
-
}
|
|
7608
|
-
|
|
7609
|
-
/**
|
|
7610
|
-
* Lighten a hex color by a percentage
|
|
7611
|
-
*/
|
|
7612
|
-
private lightenColor(hex: string, percent: number): string {
|
|
7613
|
-
const rgb = this.hexToRgb(hex);
|
|
7614
|
-
if (!rgb) return hex;
|
|
7615
|
-
|
|
7616
|
-
const amount = Math.floor(255 * (percent / 100));
|
|
7617
|
-
const r = Math.min(255, rgb.r + amount);
|
|
7618
|
-
const g = Math.min(255, rgb.g + amount);
|
|
7619
|
-
const b = Math.min(255, rgb.b + amount);
|
|
7620
|
-
|
|
7621
|
-
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
7622
|
-
}
|
|
7623
|
-
|
|
7624
|
-
/**
|
|
7625
|
-
* Render chapter markers on progress bar for Material You layout
|
|
7626
|
-
*/
|
|
7627
|
-
private renderChapterMarkersOnProgressBar(): void {
|
|
7628
|
-
if (!this.video || !this.chapterConfig.enabled || !this.chapterConfig.showChapterMarkers) {
|
|
7629
|
-
return;
|
|
7630
|
-
}
|
|
7631
|
-
|
|
7632
|
-
const progressBar = document.querySelector('.uvf-progress-bar');
|
|
7633
|
-
if (!progressBar) return;
|
|
7634
|
-
|
|
7635
|
-
// Remove existing markers
|
|
7636
|
-
const existingMarkers = progressBar.querySelectorAll('.uvf-chapter-marker');
|
|
7637
|
-
existingMarkers.forEach(marker => marker.remove());
|
|
7638
|
-
|
|
7639
|
-
// Get chapter data
|
|
7640
|
-
const chapters = this.chapterConfig.data;
|
|
7641
|
-
if (!chapters || !Array.isArray(chapters) || chapters.length === 0) {
|
|
7642
|
-
return;
|
|
7643
|
-
}
|
|
7644
|
-
|
|
7645
|
-
const duration = this.video.duration;
|
|
7646
|
-
if (!duration || duration === 0) return;
|
|
7647
|
-
|
|
7648
|
-
// Create marker for each chapter
|
|
7649
|
-
chapters.forEach((chapter: any) => {
|
|
7650
|
-
if (!chapter.startTime && chapter.startTime !== 0) return;
|
|
7651
|
-
|
|
7652
|
-
const marker = document.createElement('div');
|
|
7653
|
-
marker.className = 'uvf-chapter-marker';
|
|
7654
|
-
|
|
7655
|
-
// Add chapter type class if available
|
|
7656
|
-
if (chapter.type) {
|
|
7657
|
-
marker.classList.add(chapter.type.toLowerCase());
|
|
7658
|
-
}
|
|
7659
|
-
|
|
7660
|
-
// Position based on startTime
|
|
7661
|
-
const percent = (chapter.startTime / duration) * 100;
|
|
7662
|
-
marker.style.left = `${percent}%`;
|
|
7663
|
-
|
|
7664
|
-
// Add title attribute for tooltip
|
|
7665
|
-
if (chapter.title) {
|
|
7666
|
-
marker.setAttribute('title', chapter.title);
|
|
7667
|
-
}
|
|
7668
|
-
|
|
7669
|
-
progressBar.appendChild(marker);
|
|
7670
|
-
});
|
|
7671
|
-
|
|
7672
|
-
this.debugLog(`Rendered ${chapters.length} chapter markers on progress bar`);
|
|
7673
|
-
}
|
|
7674
|
-
|
|
7675
6820
|
/**
|
|
7676
6821
|
* Detect if user is on a mobile device
|
|
7677
6822
|
*/
|
|
@@ -7812,8 +6957,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
7812
6957
|
|
|
7813
6958
|
private hideControls(): void {
|
|
7814
6959
|
if (!this.state.isPlaying) return;
|
|
7815
|
-
// Never hide controls while settings menu is open
|
|
7816
|
-
if (this.isSettingsOpen) return;
|
|
7817
6960
|
|
|
7818
6961
|
const wrapper = this.container?.querySelector('.uvf-player-wrapper');
|
|
7819
6962
|
if (wrapper) {
|
|
@@ -7824,14 +6967,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
7824
6967
|
|
|
7825
6968
|
private scheduleHideControls(): void {
|
|
7826
6969
|
if (!this.state.isPlaying) return;
|
|
7827
|
-
// Do not schedule auto-hide while settings are open
|
|
7828
|
-
if (this.isSettingsOpen) return;
|
|
7829
6970
|
|
|
7830
6971
|
if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
|
|
7831
6972
|
// Use longer timeout in fullscreen for better UX
|
|
7832
6973
|
const timeout = this.isFullscreen() ? 4000 : 3000;
|
|
7833
6974
|
this.hideControlsTimeout = setTimeout(() => {
|
|
7834
|
-
if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')
|
|
6975
|
+
if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
|
|
7835
6976
|
this.hideControls();
|
|
7836
6977
|
}
|
|
7837
6978
|
}, timeout);
|
|
@@ -9040,7 +8181,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
9040
8181
|
if (!settingsMenu) return;
|
|
9041
8182
|
|
|
9042
8183
|
settingsMenu.classList.remove('active');
|
|
9043
|
-
this.isSettingsOpen = false;
|
|
9044
8184
|
|
|
9045
8185
|
// Apply fallback styles to ensure menu is hidden
|
|
9046
8186
|
settingsMenu.style.display = 'none';
|
|
@@ -9053,8 +8193,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
9053
8193
|
});
|
|
9054
8194
|
|
|
9055
8195
|
this.debugLog('Settings menu hidden via hideSettingsMenu()');
|
|
9056
|
-
// Resume auto-hide if appropriate
|
|
9057
|
-
this.scheduleHideControls();
|
|
9058
8196
|
}
|
|
9059
8197
|
|
|
9060
8198
|
/**
|