unified-video-framework 1.4.166 → 1.4.168

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 */
@@ -4302,162 +4251,35 @@ export class WebPlayer extends BasePlayer {
4302
4251
  }
4303
4252
  }
4304
4253
 
4305
- /* iOS Safari specific fixes - address bar handling and control positioning */
4254
+ /* iOS Safari specific fixes - fullscreen only */
4306
4255
  @supports (-webkit-appearance: none) {
4307
4256
  .uvf-player-wrapper.uvf-fullscreen,
4308
4257
  .uvf-video-container.uvf-fullscreen {
4309
4258
  height: -webkit-fill-available;
4310
4259
  min-height: -webkit-fill-available;
4311
4260
  }
4312
-
4313
- /* Handle iOS Safari's dynamic address bar */
4314
- @media screen and (max-width: 767px) {
4315
- .uvf-responsive-container {
4316
- height: -webkit-fill-available;
4317
- min-height: 100vh;
4318
- }
4319
-
4320
- .uvf-player-wrapper {
4321
- height: -webkit-fill-available;
4322
- min-height: 100vh;
4323
- /* Fix for iOS Safari control overlay positioning */
4324
- position: relative;
4325
- overflow: hidden;
4326
- }
4327
-
4328
- /* iOS Safari specific fixes for control positioning */
4329
- .uvf-controls-bar {
4330
- position: absolute !important;
4331
- bottom: 0 !important;
4332
- left: 0 !important;
4333
- right: 0 !important;
4334
- /* Ensure hardware acceleration */
4335
- -webkit-transform: translate3d(0,0,0);
4336
- transform: translate3d(0,0,0);
4337
- /* Prevent any webkit transforms that could cause positioning issues */
4338
- -webkit-perspective: 1000;
4339
- perspective: 1000;
4340
- }
4341
-
4342
- /* Ensure all control elements use hardware acceleration */
4343
- .uvf-control-btn,
4344
- .uvf-progress-bar,
4345
- .uvf-progress-section {
4346
- -webkit-transform: translateZ(0);
4347
- transform: translateZ(0);
4348
- }
4349
- }
4350
- }
4351
-
4352
- /* Android Chrome specific fixes */
4353
- @supports (display: -webkit-box) {
4354
- .uvf-responsive-container {
4355
- min-height: 100vh;
4356
- }
4357
-
4358
- /* Fix for Android Chrome's address bar behavior */
4359
- @media screen and (max-width: 767px) {
4360
- .uvf-video-container {
4361
- min-height: calc(100vh - 56px); /* Chrome mobile address bar height */
4362
- }
4363
- }
4364
4261
  }
4365
4262
 
4366
- /* Samsung Internet Browser fixes */
4367
- @media screen and (-webkit-min-device-pixel-ratio: 1) {
4368
- @media screen and (max-width: 767px) {
4369
- .uvf-responsive-container {
4370
- position: fixed;
4371
- top: 0;
4372
- left: 0;
4373
- width: 100vw;
4374
- height: 100vh;
4375
- }
4376
- }
4377
- }
4378
4263
 
4379
- /* Universal mobile fixes for all browsers */
4380
- @media screen and (max-width: 767px) {
4381
- html, body {
4382
- overflow-x: hidden;
4383
- }
4384
-
4385
- .uvf-player-wrapper {
4386
- /* Prevent scroll bounce on iOS */
4387
- -webkit-overflow-scrolling: touch;
4264
+ /* Enhanced Responsive Media Queries with UX Best Practices */
4265
+ /* Mobile devices (portrait) - Material You Design (25-50-25 Layout) */
4266
+ @media screen and (max-width: 767px) and (orientation: portrait) {
4267
+ .uvf-responsive-container {
4268
+ padding: 0;
4269
+ width: 100vw !important;
4270
+ height: 100vh;
4271
+ height: 100dvh;
4272
+ margin: 0;
4273
+ position: fixed;
4274
+ top: 0;
4275
+ left: 0;
4388
4276
  overflow: hidden;
4389
-
4390
- /* Prevent zoom on double tap */
4391
- touch-action: manipulation;
4392
- }
4393
-
4394
- .uvf-video {
4395
- /* Prevent video from being selectable */
4396
- -webkit-user-select: none;
4397
- -moz-user-select: none;
4398
- -ms-user-select: none;
4399
- user-select: none;
4400
-
4401
- /* Ensure hardware acceleration */
4402
- -webkit-transform: translateZ(0);
4403
- transform: translateZ(0);
4404
4277
  }
4405
4278
 
4406
- /* Fix for controls being cut off by virtual keyboard */
4407
- .uvf-controls-bar {
4408
- position: absolute !important;
4409
- bottom: 0 !important;
4410
- left: 0 !important;
4411
- right: 0 !important;
4412
- /* Remove fixed positioning that causes issues on iOS Safari */
4413
- z-index: 1000 !important;
4414
- transform: translateZ(0); /* Force hardware acceleration */
4415
- }
4416
-
4417
- /* Ensure controls stay above virtual keyboards */
4418
- @supports (bottom: env(keyboard-inset-height)) {
4419
- .uvf-controls-bar {
4420
- bottom: max(0px, env(keyboard-inset-height, 0)) !important;
4421
- padding-bottom: calc(16px + max(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0))) !important;
4422
- }
4423
- }
4424
-
4425
- /* Hide PiP button on mobile - not supported on most mobile browsers */
4426
- #uvf-pip-btn {
4427
- display: none !important;
4428
- }
4429
-
4430
- /* Mobile fullscreen enhancements */
4431
- .uvf-player-wrapper.uvf-fullscreen {
4432
- /* Ensure fullscreen covers entire viewport on mobile */
4433
- position: fixed !important;
4434
- top: 0 !important;
4435
- left: 0 !important;
4279
+ .uvf-responsive-container .uvf-player-wrapper {
4436
4280
  width: 100vw !important;
4437
- height: 100vh !important;
4438
- z-index: 2147483647 !important;
4439
- background: #000 !important;
4440
- }
4441
-
4442
- /* iOS Safari specific fullscreen fixes */
4443
- @supports (-webkit-appearance: none) {
4444
- .uvf-player-wrapper.uvf-fullscreen {
4445
- /* Use viewport units that work better with iOS Safari */
4446
- height: -webkit-fill-available !important;
4447
- }
4448
- }
4449
- }
4450
-
4451
- /* Enhanced Responsive Media Queries with UX Best Practices */
4452
-
4453
- /* Material You Mobile Portrait Layout - 25% Black + 50% Video + 25% Black */
4454
- @media screen and (max-width: 767px) and (orientation: portrait) {
4455
- /* Enable Material You mode with class flag */
4456
- .uvf-player-wrapper.uvf-material-you-mobile {
4457
- /* Full viewport height layout */
4458
4281
  height: 100vh;
4459
4282
  height: 100dvh;
4460
- width: 100vw;
4461
4283
  position: fixed;
4462
4284
  top: 0;
4463
4285
  left: 0;
@@ -4467,8 +4289,8 @@ export class WebPlayer extends BasePlayer {
4467
4289
  overflow: hidden;
4468
4290
  }
4469
4291
 
4470
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video-container {
4471
- /* Video occupies middle 50% */
4292
+ /* Video container occupies middle 50% */
4293
+ .uvf-responsive-container .uvf-video-container {
4472
4294
  height: 50vh;
4473
4295
  height: 50dvh;
4474
4296
  width: 100vw;
@@ -4476,22 +4298,20 @@ export class WebPlayer extends BasePlayer {
4476
4298
  margin-top: 25vh;
4477
4299
  margin-top: 25dvh;
4478
4300
  aspect-ratio: unset !important;
4479
- /* Match existing theme background */
4480
4301
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
4481
- /* Material Design elevation */
4482
4302
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
4483
4303
  0 4px 16px rgba(0, 0, 0, 0.3),
4484
4304
  0 2px 8px rgba(0, 0, 0, 0.2);
4485
4305
  }
4486
4306
 
4487
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video {
4307
+ .uvf-video {
4488
4308
  width: 100%;
4489
4309
  height: 100%;
4490
4310
  object-fit: contain;
4491
4311
  }
4492
4312
 
4493
- /* Top black section (25%) - Tap zone overlay */
4494
- .uvf-player-wrapper.uvf-material-you-mobile::before {
4313
+ /* Top black section (25%) - Tap zone */
4314
+ .uvf-player-wrapper::before {
4495
4315
  content: '';
4496
4316
  position: absolute;
4497
4317
  top: 0;
@@ -4505,8 +4325,8 @@ export class WebPlayer extends BasePlayer {
4505
4325
  touch-action: manipulation;
4506
4326
  }
4507
4327
 
4508
- /* Bottom black section (25%) - Surface container */
4509
- .uvf-player-wrapper.uvf-material-you-mobile::after {
4328
+ /* Bottom black section (25%) - Controls area */
4329
+ .uvf-player-wrapper::after {
4510
4330
  content: '';
4511
4331
  position: absolute;
4512
4332
  bottom: 0;
@@ -4523,7 +4343,7 @@ export class WebPlayer extends BasePlayer {
4523
4343
  }
4524
4344
 
4525
4345
  /* Material surface container for controls */
4526
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar {
4346
+ .uvf-controls-bar {
4527
4347
  position: absolute;
4528
4348
  bottom: 0;
4529
4349
  left: 0;
@@ -4538,13 +4358,12 @@ export class WebPlayer extends BasePlayer {
4538
4358
  display: flex;
4539
4359
  flex-direction: column;
4540
4360
  justify-content: flex-end;
4541
- /* Material Design surface with tint */
4542
4361
  backdrop-filter: blur(24px);
4543
4362
  -webkit-backdrop-filter: blur(24px);
4544
4363
  }
4545
4364
 
4546
4365
  /* Material surface tint overlay */
4547
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar::before {
4366
+ .uvf-controls-bar::before {
4548
4367
  content: '';
4549
4368
  position: absolute;
4550
4369
  inset: 0;
@@ -4555,87 +4374,53 @@ export class WebPlayer extends BasePlayer {
4555
4374
  }
4556
4375
 
4557
4376
  /* Progress bar with chapter markers */
4558
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-section {
4377
+ .uvf-progress-section {
4559
4378
  margin-bottom: 12px;
4560
4379
  position: relative;
4561
4380
  }
4562
4381
 
4563
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar-wrapper {
4382
+ .uvf-progress-bar-wrapper {
4564
4383
  padding: 12px 0;
4565
4384
  position: relative;
4566
4385
  }
4567
4386
 
4568
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar {
4387
+ .uvf-progress-bar {
4569
4388
  height: 4px;
4570
4389
  background: rgba(255, 255, 255, 0.2);
4571
4390
  border-radius: 4px;
4572
4391
  position: relative;
4573
4392
  overflow: visible;
4574
- /* Material elevation */
4575
4393
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
4576
4394
  }
4577
4395
 
4578
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-filled {
4396
+ .uvf-progress-filled {
4579
4397
  background: var(--uvf-accent-1, #ff0000);
4580
4398
  box-shadow: 0 0 8px var(--uvf-accent-1, #ff0000);
4581
4399
  }
4582
4400
 
4583
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle {
4401
+ .uvf-progress-handle {
4584
4402
  width: 20px;
4585
4403
  height: 20px;
4586
4404
  background: var(--uvf-accent-1, #ff0000);
4587
- /* Material Design state layer */
4588
4405
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3),
4589
4406
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4590
4407
  transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
4591
4408
  }
4592
4409
 
4593
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle:active {
4410
+ .uvf-progress-handle:active {
4594
4411
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4),
4595
4412
  0 0 0 12px rgba(255, 0, 0, 0.15);
4596
4413
  transform: translate(-50%, -50%) scale(1.2);
4597
4414
  }
4598
4415
 
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
4416
  /* Material Design control buttons */
4631
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn {
4417
+ .uvf-control-btn {
4632
4418
  width: 48px;
4633
4419
  height: 48px;
4634
4420
  min-width: 48px;
4635
4421
  min-height: 48px;
4636
4422
  background: rgba(255, 255, 255, 0.12);
4637
4423
  border-radius: 24px;
4638
- /* Material elevation level 1 */
4639
4424
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12),
4640
4425
  0 1px 2px rgba(0, 0, 0, 0.24);
4641
4426
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -4644,7 +4429,7 @@ export class WebPlayer extends BasePlayer {
4644
4429
  }
4645
4430
 
4646
4431
  /* Material ripple effect */
4647
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn::before {
4432
+ .uvf-control-btn::before {
4648
4433
  content: '';
4649
4434
  position: absolute;
4650
4435
  inset: 0;
@@ -4654,24 +4439,22 @@ export class WebPlayer extends BasePlayer {
4654
4439
  transition: opacity 0.2s ease;
4655
4440
  }
4656
4441
 
4657
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active::before {
4442
+ .uvf-control-btn:active::before {
4658
4443
  opacity: 1;
4659
4444
  }
4660
4445
 
4661
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active {
4662
- /* Material elevation level 2 */
4446
+ .uvf-control-btn:active {
4663
4447
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16),
4664
4448
  0 3px 6px rgba(0, 0, 0, 0.23);
4665
4449
  transform: scale(0.95);
4666
4450
  }
4667
4451
 
4668
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause {
4452
+ .uvf-control-btn.play-pause {
4669
4453
  width: 56px;
4670
4454
  height: 56px;
4671
4455
  min-width: 56px;
4672
4456
  min-height: 56px;
4673
4457
  border-radius: 28px;
4674
- /* Material elevated button */
4675
4458
  background: linear-gradient(135deg,
4676
4459
  var(--uvf-accent-1, #ff0000),
4677
4460
  var(--uvf-accent-2, #ff4d4f));
@@ -4680,21 +4463,31 @@ export class WebPlayer extends BasePlayer {
4680
4463
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4681
4464
  }
4682
4465
 
4683
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause:active {
4466
+ .uvf-control-btn.play-pause:active {
4684
4467
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25),
4685
4468
  0 4px 8px rgba(0, 0, 0, 0.20),
4686
4469
  0 0 0 8px rgba(255, 0, 0, 0.12);
4687
4470
  }
4688
4471
 
4472
+ .uvf-control-btn svg {
4473
+ width: 20px;
4474
+ height: 20px;
4475
+ }
4476
+
4477
+ .uvf-control-btn.play-pause svg {
4478
+ width: 24px;
4479
+ height: 24px;
4480
+ }
4481
+
4689
4482
  /* Controls row with Material spacing */
4690
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-row {
4483
+ .uvf-controls-row {
4691
4484
  gap: 16px;
4692
4485
  padding: 0;
4693
4486
  align-items: center;
4694
4487
  }
4695
4488
 
4696
4489
  /* Time display with Material surface */
4697
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-time-display {
4490
+ .uvf-time-display {
4698
4491
  background: rgba(255, 255, 255, 0.1);
4699
4492
  backdrop-filter: blur(8px);
4700
4493
  border-radius: 16px;
@@ -4702,101 +4495,20 @@ export class WebPlayer extends BasePlayer {
4702
4495
  font-size: 13px;
4703
4496
  font-weight: 500;
4704
4497
  font-feature-settings: 'tnum';
4705
- /* Material elevation */
4706
4498
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
4707
4499
  }
4708
4500
 
4709
- /* Double-tap overlay indicators */
4710
- .uvf-doubletap-indicator {
4711
- position: absolute;
4712
- top: 50%;
4713
- transform: translateY(-50%);
4714
- width: 80px;
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 {
4501
+ /* Hide desktop elements */
4502
+ .uvf-top-controls,
4503
+ .uvf-title-bar,
4504
+ .uvf-volume-control,
4505
+ #uvf-skip-back,
4506
+ #uvf-skip-forward {
4795
4507
  display: none !important;
4796
4508
  }
4797
4509
 
4798
4510
  /* Optimize settings button for Material You */
4799
- .uvf-player-wrapper.uvf-material-you-mobile #uvf-settings-btn {
4511
+ #uvf-settings-btn {
4800
4512
  width: 48px !important;
4801
4513
  height: 48px !important;
4802
4514
  min-width: 48px !important;
@@ -4804,405 +4516,6 @@ export class WebPlayer extends BasePlayer {
4804
4516
  border-radius: 24px !important;
4805
4517
  }
4806
4518
  }
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
4519
  }
5207
4520
 
5208
4521
  #uvf-settings-btn svg {
@@ -6130,26 +5443,6 @@ export class WebPlayer extends BasePlayer {
6130
5443
  shortcutIndicator.className = 'uvf-shortcut-indicator';
6131
5444
  shortcutIndicator.id = 'uvf-shortcut-indicator';
6132
5445
  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
5446
 
6154
5447
  // Create controls bar
6155
5448
  const controlsBar = document.createElement('div');
@@ -6443,9 +5736,7 @@ export class WebPlayer extends BasePlayer {
6443
5736
  // Play/Pause
6444
5737
  centerPlay?.addEventListener('click', () => this.togglePlayPause());
6445
5738
  playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
6446
-
6447
- // Material You touch gestures for mobile
6448
- this.setupMaterialYouGestures(wrapper);
5739
+ this.video.addEventListener('click', () => this.togglePlayPause());
6449
5740
 
6450
5741
  // Update play/pause icons
6451
5742
  this.video.addEventListener('play', () => {
@@ -6820,12 +6111,7 @@ export class WebPlayer extends BasePlayer {
6820
6111
 
6821
6112
  // Force visibility if menu is active, hide if not active
6822
6113
  if (settingsMenu) {
6823
- const activating = settingsMenu.classList.contains('active');
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);
6114
+ if (settingsMenu.classList.contains('active')) {
6829
6115
  settingsMenu.style.display = 'block';
6830
6116
  settingsMenu.style.visibility = 'visible';
6831
6117
  settingsMenu.style.opacity = '1';
@@ -6841,13 +6127,10 @@ export class WebPlayer extends BasePlayer {
6841
6127
  settingsMenu.style.padding = '10px 0';
6842
6128
  this.debugLog('Applied fallback styles to show menu');
6843
6129
  } else {
6844
- this.isSettingsOpen = false;
6845
6130
  settingsMenu.style.display = 'none';
6846
6131
  settingsMenu.style.visibility = 'hidden';
6847
6132
  settingsMenu.style.opacity = '0';
6848
6133
  this.debugLog('Applied fallback styles to hide menu');
6849
- // After closing, allow auto-hide again if playing
6850
- this.scheduleHideControls();
6851
6134
  }
6852
6135
  }
6853
6136
 
@@ -6887,19 +6170,6 @@ export class WebPlayer extends BasePlayer {
6887
6170
  }
6888
6171
  });
6889
6172
 
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
6173
  // Add Escape key handler for settings menu
6904
6174
  document.addEventListener('keydown', (e) => {
6905
6175
  if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
@@ -7411,267 +6681,6 @@ export class WebPlayer extends BasePlayer {
7411
6681
  }
7412
6682
  }
7413
6683
 
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
6684
  /**
7676
6685
  * Detect if user is on a mobile device
7677
6686
  */
@@ -7812,8 +6821,6 @@ export class WebPlayer extends BasePlayer {
7812
6821
 
7813
6822
  private hideControls(): void {
7814
6823
  if (!this.state.isPlaying) return;
7815
- // Never hide controls while settings menu is open
7816
- if (this.isSettingsOpen) return;
7817
6824
 
7818
6825
  const wrapper = this.container?.querySelector('.uvf-player-wrapper');
7819
6826
  if (wrapper) {
@@ -7824,14 +6831,12 @@ export class WebPlayer extends BasePlayer {
7824
6831
 
7825
6832
  private scheduleHideControls(): void {
7826
6833
  if (!this.state.isPlaying) return;
7827
- // Do not schedule auto-hide while settings are open
7828
- if (this.isSettingsOpen) return;
7829
6834
 
7830
6835
  if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
7831
6836
  // Use longer timeout in fullscreen for better UX
7832
6837
  const timeout = this.isFullscreen() ? 4000 : 3000;
7833
6838
  this.hideControlsTimeout = setTimeout(() => {
7834
- if (this.state.isPlaying && !this.controlsContainer?.matches(':hover') && !this.isSettingsOpen) {
6839
+ if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
7835
6840
  this.hideControls();
7836
6841
  }
7837
6842
  }, timeout);
@@ -9040,7 +8045,6 @@ export class WebPlayer extends BasePlayer {
9040
8045
  if (!settingsMenu) return;
9041
8046
 
9042
8047
  settingsMenu.classList.remove('active');
9043
- this.isSettingsOpen = false;
9044
8048
 
9045
8049
  // Apply fallback styles to ensure menu is hidden
9046
8050
  settingsMenu.style.display = 'none';
@@ -9053,8 +8057,6 @@ export class WebPlayer extends BasePlayer {
9053
8057
  });
9054
8058
 
9055
8059
  this.debugLog('Settings menu hidden via hideSettingsMenu()');
9056
- // Resume auto-hide if appropriate
9057
- this.scheduleHideControls();
9058
8060
  }
9059
8061
 
9060
8062
  /**