unified-video-framework 1.4.163 → 1.4.165

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,6 +51,9 @@ 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
+
54
57
  // Settings configuration
55
58
  private settingsConfig = {
56
59
  enabled: true, // Show settings button
@@ -4398,244 +4401,61 @@ export class WebPlayer extends BasePlayer {
4398
4401
  }
4399
4402
 
4400
4403
  /* Enhanced Responsive Media Queries with UX Best Practices */
4401
- /* Mobile Portrait Layout - CENTERED PLAYER with TOP/BOTTOM BLACK AREAS */
4404
+ /* Mobile devices (portrait) - Enhanced UX with Safe Areas */
4402
4405
  @media screen and (max-width: 767px) and (orientation: portrait) {
4403
- /* CENTERED LAYOUT: 25% top black + 50% player + 25% bottom black */
4404
4406
  .uvf-responsive-container {
4405
- display: flex;
4406
- flex-direction: column;
4407
- height: 100vh;
4408
- height: 100dvh;
4409
- background: #000;
4410
- overflow: hidden;
4411
- position: fixed;
4412
- top: 0;
4413
- left: 0;
4414
- width: 100vw;
4415
4407
  padding: 0;
4408
+ width: 100vw !important;
4409
+ height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4416
4410
  margin: 0;
4411
+ position: relative;
4412
+ overflow: hidden;
4417
4413
  }
4418
4414
 
4419
- /* TOP BLACK AREA - 25% of viewport - COMPLETELY EMPTY */
4420
- .uvf-responsive-container::before {
4421
- content: '';
4422
- flex: 0 0 25vh;
4423
- background: #000;
4424
- pointer-events: none;
4425
- }
4426
-
4427
- /* BOTTOM BLACK AREA - 25% of viewport - COMPLETELY EMPTY */
4428
- .uvf-responsive-container::after {
4429
- content: '';
4430
- flex: 0 0 25vh;
4431
- background: #000;
4432
- pointer-events: none;
4415
+ @supports (height: 100dvh) {
4416
+ .uvf-responsive-container {
4417
+ height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4418
+ }
4433
4419
  }
4434
4420
 
4435
- /* CENTERED VIDEO PLAYER WRAPPER - 50% of viewport */
4436
4421
  .uvf-responsive-container .uvf-player-wrapper {
4437
- flex: 1; /* Takes remaining 50% */
4438
- width: 100vw;
4439
- display: flex;
4440
- align-items: center;
4441
- justify-content: center;
4442
- background: #000;
4443
- position: relative;
4444
- overflow: hidden; /* CRITICAL: Ensure nothing extends beyond this area */
4422
+ width: 100vw !important;
4423
+ height: 100% !important;
4424
+ min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4445
4425
  }
4446
4426
 
4447
- /* Video container fills player wrapper */
4448
- .uvf-responsive-container .uvf-video-container {
4449
- width: 100%;
4450
- height: 100%;
4451
- position: relative;
4452
- background: #000;
4453
- border-radius: 0;
4454
- overflow: hidden;
4427
+ @supports (height: 100dvh) {
4428
+ .uvf-responsive-container .uvf-player-wrapper {
4429
+ min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4430
+ }
4455
4431
  }
4456
4432
 
4457
- /* Video element fills container */
4458
- .uvf-responsive-container .uvf-video {
4459
- width: 100%;
4460
- height: 100%;
4461
- object-fit: contain;
4462
- background: #000;
4433
+ .uvf-responsive-container .uvf-video-container {
4434
+ width: 100vw !important;
4435
+ height: 100% !important;
4436
+ aspect-ratio: unset !important;
4437
+ min-height: inherit;
4463
4438
  }
4464
4439
 
4465
- /* CONTROLS STRICTLY CONTAINED WITHIN VIDEO AREA - NEVER EXTEND TO BLACK AREAS */
4440
+ /* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
4466
4441
  .uvf-controls-bar {
4467
4442
  position: absolute !important;
4468
- /* Keep controls INSIDE video container with margins from all edges */
4469
- bottom: 20px !important; /* 20px margin from video bottom */
4470
- left: 16px !important; /* 16px margin from video left */
4471
- right: 16px !important; /* 16px margin from video right */
4472
- top: auto !important;
4473
- background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 60%, transparent 100%);
4474
- padding: 12px 16px;
4475
- border-radius: 12px;
4476
- z-index: 1000;
4477
- /* Ensure controls are visible and contained */
4478
- opacity: 1 !important;
4479
- visibility: visible !important;
4480
- display: flex !important;
4481
- flex-direction: column !important;
4482
- /* Hardware acceleration */
4483
- -webkit-transform: translate3d(0,0,0);
4484
- transform: translate3d(0,0,0);
4485
- /* CRITICAL: Prevent any overflow into black areas */
4486
- max-height: calc(100% - 40px); /* Leave 20px margin from top and bottom */
4487
- max-width: calc(100% - 32px); /* Leave 16px margin from left and right */
4488
- box-sizing: border-box;
4489
- /* Visual containment indicators */
4490
- border: 1px solid rgba(255, 255, 255, 0.1);
4491
- backdrop-filter: blur(10px);
4492
- contain: layout style paint; /* CSS containment */
4493
- }
4494
-
4495
- /* Force controls and all child elements to stay within video container */
4496
- .uvf-responsive-container .uvf-controls-bar {
4497
- opacity: 1 !important;
4498
- visibility: visible !important;
4499
- transform: translateY(0) !important;
4500
- pointer-events: auto !important;
4501
- /* Ensure no child elements extend beyond container */
4502
- contain: layout style paint;
4503
- }
4504
-
4505
- /* Ensure all child elements of controls stay within bounds */
4506
- .uvf-responsive-container .uvf-controls-bar * {
4507
- max-width: 100%;
4443
+ bottom: 0 !important;
4444
+ left: 0 !important;
4445
+ right: 0 !important;
4446
+ padding: 16px 12px;
4447
+ padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
4448
+ padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
4449
+ padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
4450
+ background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
4508
4451
  box-sizing: border-box;
4509
- }
4510
-
4511
- /* Progress section contained within controls */
4512
- .uvf-progress-section {
4513
- width: 100%;
4514
- margin-bottom: 8px;
4515
- opacity: 1 !important;
4516
- visibility: visible !important;
4517
- display: block !important;
4518
- }
4519
-
4520
- /* Progress bar styling */
4521
- .uvf-progress-bar-wrapper {
4522
- opacity: 1 !important;
4523
- visibility: visible !important;
4524
- }
4525
-
4526
- .uvf-progress-bar {
4527
- height: 4px;
4528
- background: rgba(255, 255, 255, 0.3);
4529
- border-radius: 2px;
4530
- }
4531
-
4532
- .uvf-progress-filled {
4533
- background: var(--uvf-accent-1, #8B5CF6);
4534
- height: 100%;
4535
- border-radius: 2px;
4536
- }
4537
-
4538
- /* Controls row alignment - ensure visibility */
4539
- .uvf-controls-row {
4540
- width: 100%;
4541
- display: flex !important;
4542
- align-items: center;
4543
- justify-content: flex-start;
4544
- gap: 12px;
4545
- opacity: 1 !important;
4546
- visibility: visible !important;
4547
- margin-top: 8px;
4548
- }
4549
-
4550
- /* Time display visibility */
4551
- .uvf-time-display {
4552
- color: #fff;
4553
- font-size: 12px;
4554
- font-weight: 500;
4555
- opacity: 1 !important;
4556
- visibility: visible !important;
4557
- display: block !important;
4558
- }
4559
-
4560
- /* Control buttons visibility */
4561
- .uvf-control-btn {
4562
- min-width: 44px;
4563
- min-height: 44px;
4564
- display: flex !important;
4565
- align-items: center;
4566
- justify-content: center;
4567
- border-radius: 50%;
4568
- background: rgba(255, 255, 255, 0.1);
4569
- backdrop-filter: blur(8px);
4570
- color: #fff;
4571
- opacity: 1 !important;
4572
- visibility: visible !important;
4573
- transition: all 0.2s ease;
4574
- }
4575
-
4576
- .uvf-control-btn:active {
4577
- transform: scale(0.95);
4578
- background: rgba(255, 255, 255, 0.2);
4579
- }
4580
-
4581
- /* Play/pause button prominence */
4582
- .uvf-control-btn.play-pause {
4583
- background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4584
- min-width: 52px;
4585
- min-height: 52px;
4586
- box-shadow: 0 2px 8px rgba(0,0,0,0.3);
4587
- }
4588
-
4589
- .uvf-control-btn.play-pause:active {
4590
- transform: scale(0.92);
4591
- box-shadow: 0 1px 4px rgba(0,0,0,0.4);
4592
- }
4593
-
4594
- .uvf-control-btn svg {
4595
- fill: #fff;
4596
- opacity: 1;
4597
- }
4598
-
4599
- /* Right controls */
4600
- .uvf-right-controls {
4601
- margin-left: auto;
4602
- display: flex !important;
4603
- align-items: center;
4604
- gap: 8px;
4605
- opacity: 1 !important;
4606
- visibility: visible !important;
4607
- }
4608
-
4609
- /* Center play button positioned within video */
4610
- .uvf-center-play-container {
4611
- position: absolute;
4612
- top: 50%;
4613
- left: 50%;
4614
- transform: translate(-50%, -50%);
4615
- z-index: 8;
4616
- pointer-events: none;
4617
- }
4618
-
4619
- .uvf-center-play-btn {
4620
- pointer-events: auto;
4621
- }
4622
-
4623
- /* Top controls within video */
4624
- .uvf-top-controls {
4625
- position: absolute;
4626
- top: calc(12px + var(--uvf-safe-area-top));
4627
- right: calc(16px + var(--uvf-safe-area-right));
4628
- z-index: 9;
4629
- }
4630
-
4631
- /* Title bar within video */
4632
- .uvf-title-bar {
4633
- position: absolute;
4634
- top: calc(12px + var(--uvf-safe-area-top));
4635
- left: calc(16px + var(--uvf-safe-area-left));
4636
- right: calc(80px + var(--uvf-safe-area-right));
4637
- z-index: 9;
4638
- padding: 8px 0;
4452
+ z-index: 1000 !important;
4453
+ /* iOS Safari specific fixes */
4454
+ transform: translateZ(0);
4455
+ -webkit-transform: translateZ(0);
4456
+ will-change: transform;
4457
+ /* Ensure proper stacking */
4458
+ isolation: isolate;
4639
4459
  }
4640
4460
 
4641
4461
  .uvf-progress-section {
@@ -6574,7 +6394,12 @@ export class WebPlayer extends BasePlayer {
6574
6394
 
6575
6395
  // Force visibility if menu is active, hide if not active
6576
6396
  if (settingsMenu) {
6577
- if (settingsMenu.classList.contains('active')) {
6397
+ const activating = settingsMenu.classList.contains('active');
6398
+ if (activating) {
6399
+ this.isSettingsOpen = true;
6400
+ // Keep controls visible and interactive while menu is open
6401
+ this.showControls();
6402
+ if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
6578
6403
  settingsMenu.style.display = 'block';
6579
6404
  settingsMenu.style.visibility = 'visible';
6580
6405
  settingsMenu.style.opacity = '1';
@@ -6590,10 +6415,13 @@ export class WebPlayer extends BasePlayer {
6590
6415
  settingsMenu.style.padding = '10px 0';
6591
6416
  this.debugLog('Applied fallback styles to show menu');
6592
6417
  } else {
6418
+ this.isSettingsOpen = false;
6593
6419
  settingsMenu.style.display = 'none';
6594
6420
  settingsMenu.style.visibility = 'hidden';
6595
6421
  settingsMenu.style.opacity = '0';
6596
6422
  this.debugLog('Applied fallback styles to hide menu');
6423
+ // After closing, allow auto-hide again if playing
6424
+ this.scheduleHideControls();
6597
6425
  }
6598
6426
  }
6599
6427
 
@@ -6633,6 +6461,19 @@ export class WebPlayer extends BasePlayer {
6633
6461
  }
6634
6462
  });
6635
6463
 
6464
+ // Keep controls active while interacting with settings menu
6465
+ if (settingsMenu) {
6466
+ const keepAlive = () => {
6467
+ if (!this.isSettingsOpen) return;
6468
+ this.showControls();
6469
+ if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
6470
+ };
6471
+ settingsMenu.addEventListener('mouseenter', keepAlive);
6472
+ settingsMenu.addEventListener('mousemove', keepAlive);
6473
+ settingsMenu.addEventListener('touchstart', keepAlive, { passive: true } as any);
6474
+ settingsMenu.addEventListener('touchmove', keepAlive, { passive: true } as any);
6475
+ }
6476
+
6636
6477
  // Add Escape key handler for settings menu
6637
6478
  document.addEventListener('keydown', (e) => {
6638
6479
  if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
@@ -7284,6 +7125,8 @@ export class WebPlayer extends BasePlayer {
7284
7125
 
7285
7126
  private hideControls(): void {
7286
7127
  if (!this.state.isPlaying) return;
7128
+ // Never hide controls while settings menu is open
7129
+ if (this.isSettingsOpen) return;
7287
7130
 
7288
7131
  const wrapper = this.container?.querySelector('.uvf-player-wrapper');
7289
7132
  if (wrapper) {
@@ -7294,12 +7137,14 @@ export class WebPlayer extends BasePlayer {
7294
7137
 
7295
7138
  private scheduleHideControls(): void {
7296
7139
  if (!this.state.isPlaying) return;
7140
+ // Do not schedule auto-hide while settings are open
7141
+ if (this.isSettingsOpen) return;
7297
7142
 
7298
7143
  if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
7299
7144
  // Use longer timeout in fullscreen for better UX
7300
7145
  const timeout = this.isFullscreen() ? 4000 : 3000;
7301
7146
  this.hideControlsTimeout = setTimeout(() => {
7302
- if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
7147
+ if (this.state.isPlaying && !this.controlsContainer?.matches(':hover') && !this.isSettingsOpen) {
7303
7148
  this.hideControls();
7304
7149
  }
7305
7150
  }, timeout);
@@ -8508,6 +8353,7 @@ export class WebPlayer extends BasePlayer {
8508
8353
  if (!settingsMenu) return;
8509
8354
 
8510
8355
  settingsMenu.classList.remove('active');
8356
+ this.isSettingsOpen = false;
8511
8357
 
8512
8358
  // Apply fallback styles to ensure menu is hidden
8513
8359
  settingsMenu.style.display = 'none';
@@ -8520,6 +8366,8 @@ export class WebPlayer extends BasePlayer {
8520
8366
  });
8521
8367
 
8522
8368
  this.debugLog('Settings menu hidden via hideSettingsMenu()');
8369
+ // Resume auto-hide if appropriate
8370
+ this.scheduleHideControls();
8523
8371
  }
8524
8372
 
8525
8373
  /**