unified-video-framework 1.4.146 → 1.4.148

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.
@@ -153,8 +153,9 @@ export class WebPlayer extends BasePlayer {
153
153
  this.video = document.createElement('video');
154
154
  this.video.className = 'uvf-video';
155
155
  this.video.controls = false; // We'll use custom controls
156
+ // For autoplay to work, video must be muted in most browsers
156
157
  this.video.autoplay = this.config.autoPlay ?? false;
157
- this.video.muted = this.config.muted ?? false;
158
+ this.video.muted = this.config.autoPlay ? true : (this.config.muted ?? false);
158
159
  this.video.loop = this.config.loop ?? false;
159
160
  this.video.playsInline = this.config.playsInline ?? true;
160
161
  this.video.preload = this.config.preload ?? 'metadata';
@@ -193,6 +194,7 @@ export class WebPlayer extends BasePlayer {
193
194
  this.setupKeyboardShortcuts();
194
195
  this.setupWatermark();
195
196
  this.setupFullscreenListeners();
197
+ this.setupUserInteractionTracking();
196
198
 
197
199
  // Initialize paywall controller if provided
198
200
  try {
@@ -518,7 +520,15 @@ export class WebPlayer extends BasePlayer {
518
520
 
519
521
  // Start playback if autoPlay is enabled
520
522
  if (this.config.autoPlay) {
521
- this.play();
523
+ // Attempt autoplay, but handle gracefully if blocked
524
+ this.play().catch(error => {
525
+ if (this.isAutoplayRestrictionError(error)) {
526
+ this.debugWarn('HLS autoplay blocked, showing play overlay');
527
+ this.showPlayOverlay();
528
+ } else {
529
+ this.debugError('HLS autoplay failed:', error);
530
+ }
531
+ });
522
532
  }
523
533
  });
524
534
 
@@ -681,6 +691,167 @@ export class WebPlayer extends BasePlayer {
681
691
  );
682
692
  }
683
693
 
694
+ private isAutoplayRestrictionError(err: any): boolean {
695
+ if (!err) return false;
696
+
697
+ const message = (err.message || '').toLowerCase();
698
+ const name = (err.name || '').toLowerCase();
699
+
700
+ // Common autoplay restriction error patterns
701
+ return (
702
+ name === 'notallowederror' ||
703
+ message.includes('user didn\'t interact') ||
704
+ message.includes('autoplay') ||
705
+ message.includes('gesture') ||
706
+ message.includes('user activation') ||
707
+ message.includes('play() failed') ||
708
+ message.includes('user interaction')
709
+ );
710
+ }
711
+
712
+ private showPlayOverlay(): void {
713
+ // Remove existing overlay
714
+ this.hidePlayOverlay();
715
+
716
+ const overlay = document.createElement('div');
717
+ overlay.id = 'uvf-play-overlay';
718
+ overlay.className = 'uvf-play-overlay';
719
+
720
+ const playButton = document.createElement('button');
721
+ playButton.className = 'uvf-play-button';
722
+ playButton.innerHTML = `
723
+ <svg viewBox="0 0 24 24" fill="currentColor">
724
+ <path d="M8 5v14l11-7z"/>
725
+ </svg>
726
+ `;
727
+
728
+ const message = document.createElement('div');
729
+ message.className = 'uvf-play-message';
730
+ message.textContent = 'Click to play';
731
+
732
+ overlay.appendChild(playButton);
733
+ overlay.appendChild(message);
734
+
735
+ // Add click handler
736
+ playButton.addEventListener('click', async (e) => {
737
+ e.stopPropagation();
738
+ this.lastUserInteraction = Date.now();
739
+
740
+ try {
741
+ await this.play();
742
+ } catch (error) {
743
+ this.debugError('Failed to play after user interaction:', error);
744
+ }
745
+ });
746
+
747
+ // Also allow clicking anywhere on overlay
748
+ overlay.addEventListener('click', async (e) => {
749
+ if (e.target === overlay) {
750
+ e.stopPropagation();
751
+ this.lastUserInteraction = Date.now();
752
+
753
+ try {
754
+ await this.play();
755
+ } catch (error) {
756
+ this.debugError('Failed to play after user interaction:', error);
757
+ }
758
+ }
759
+ });
760
+
761
+ // Add styles
762
+ const style = document.createElement('style');
763
+ style.textContent = `
764
+ .uvf-play-overlay {
765
+ position: absolute;
766
+ top: 0;
767
+ left: 0;
768
+ width: 100%;
769
+ height: 100%;
770
+ background: rgba(0, 0, 0, 0.7);
771
+ display: flex;
772
+ flex-direction: column;
773
+ justify-content: center;
774
+ align-items: center;
775
+ z-index: 1000;
776
+ cursor: pointer;
777
+ }
778
+
779
+ .uvf-play-button {
780
+ width: 80px;
781
+ height: 80px;
782
+ border-radius: 50%;
783
+ background: rgba(255, 255, 255, 0.9);
784
+ border: none;
785
+ color: #000;
786
+ cursor: pointer;
787
+ display: flex;
788
+ align-items: center;
789
+ justify-content: center;
790
+ transition: all 0.3s ease;
791
+ margin-bottom: 16px;
792
+ }
793
+
794
+ .uvf-play-button:hover {
795
+ background: #fff;
796
+ transform: scale(1.1);
797
+ }
798
+
799
+ .uvf-play-button svg {
800
+ width: 32px;
801
+ height: 32px;
802
+ margin-left: 4px;
803
+ }
804
+
805
+ .uvf-play-message {
806
+ color: white;
807
+ font-size: 16px;
808
+ font-weight: 500;
809
+ text-align: center;
810
+ opacity: 0.9;
811
+ }
812
+ `;
813
+
814
+ // Add to page if not already added
815
+ if (!document.getElementById('uvf-play-overlay-styles')) {
816
+ style.id = 'uvf-play-overlay-styles';
817
+ document.head.appendChild(style);
818
+ }
819
+
820
+ // Add to player
821
+ if (this.playerWrapper) {
822
+ this.playerWrapper.appendChild(overlay);
823
+ }
824
+ }
825
+
826
+ private hidePlayOverlay(): void {
827
+ const overlay = document.getElementById('uvf-play-overlay');
828
+ if (overlay) {
829
+ overlay.remove();
830
+ }
831
+ }
832
+
833
+ private setupUserInteractionTracking(): void {
834
+ // Track various user interactions to enable autoplay
835
+ const interactionEvents = ['click', 'mousedown', 'keydown', 'touchstart'];
836
+
837
+ const updateLastInteraction = () => {
838
+ this.lastUserInteraction = Date.now();
839
+ this.debugLog('User interaction detected at:', this.lastUserInteraction);
840
+ };
841
+
842
+ // Listen on document for global interactions
843
+ interactionEvents.forEach(eventType => {
844
+ document.addEventListener(eventType, updateLastInteraction, { passive: true });
845
+ });
846
+
847
+ // Also listen on player wrapper for more specific interactions
848
+ if (this.playerWrapper) {
849
+ interactionEvents.forEach(eventType => {
850
+ this.playerWrapper!.addEventListener(eventType, updateLastInteraction, { passive: true });
851
+ });
852
+ }
853
+ }
854
+
684
855
  async play(): Promise<void> {
685
856
  if (!this.video) throw new Error('Video element not initialized');
686
857
 
@@ -710,6 +881,8 @@ export class WebPlayer extends BasePlayer {
710
881
  this.video.pause();
711
882
  }
712
883
 
884
+ // Hide the play overlay if it exists
885
+ this.hidePlayOverlay();
713
886
  await super.play();
714
887
  } catch (err) {
715
888
  this._playPromise = null;
@@ -717,6 +890,14 @@ export class WebPlayer extends BasePlayer {
717
890
  // Benign: pause() raced play(); ignore the error.
718
891
  return;
719
892
  }
893
+
894
+ // Check if this is an autoplay restriction error
895
+ if (this.isAutoplayRestrictionError(err)) {
896
+ this.debugWarn('Autoplay blocked by browser policy - showing play overlay');
897
+ this.showPlayOverlay();
898
+ return; // Don't throw error for autoplay restrictions
899
+ }
900
+
720
901
  this.handleError({
721
902
  code: 'PLAY_ERROR',
722
903
  message: `Failed to start playbook: ${err}`,
@@ -1932,8 +2113,8 @@ export class WebPlayer extends BasePlayer {
1932
2113
 
1933
2114
  /* Center Play Button */
1934
2115
  .uvf-center-play-btn {
1935
- width: clamp(50px, 14vw, 96px);
1936
- height: clamp(50px, 14vw, 96px);
2116
+ width: clamp(40px, 8vw, 60px);
2117
+ height: clamp(40px, 8vw, 60px);
1937
2118
  background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
1938
2119
  border: 0;
1939
2120
  border-radius: 50%;
@@ -1945,13 +2126,13 @@ export class WebPlayer extends BasePlayer {
1945
2126
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
1946
2127
  opacity: 1;
1947
2128
  visibility: visible;
1948
- box-shadow: 0 8px 28px var(--uvf-accent-1-20);
2129
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
1949
2130
  }
1950
2131
 
1951
2132
  .uvf-center-play-btn:hover {
1952
- transform: scale(1.06);
2133
+ transform: scale(1.08);
1953
2134
  filter: saturate(1.08) brightness(1.05);
1954
- box-shadow: 0 12px 36px var(--uvf-accent-1-20);
2135
+ box-shadow: 0 6px 20px var(--uvf-accent-1-20);
1955
2136
  }
1956
2137
 
1957
2138
  .uvf-center-play-btn.hidden {
@@ -1963,11 +2144,11 @@ export class WebPlayer extends BasePlayer {
1963
2144
  }
1964
2145
 
1965
2146
  .uvf-center-play-btn svg {
1966
- width: clamp(26px, 4.5vw, 36px);
1967
- height: clamp(26px, 4.5vw, 36px);
2147
+ width: clamp(18px, 3vw, 24px);
2148
+ height: clamp(18px, 3vw, 24px);
1968
2149
  fill: #fff;
1969
- margin-left: 4px;
1970
- filter: drop-shadow(0 2px 6px rgba(0,0,0,0.35));
2150
+ margin-left: 2px;
2151
+ filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
1971
2152
  }
1972
2153
 
1973
2154
  /* Pulse animation for center play button when paused */
@@ -1977,15 +2158,15 @@ export class WebPlayer extends BasePlayer {
1977
2158
 
1978
2159
  @keyframes uvf-centerPlayPulse {
1979
2160
  0% {
1980
- box-shadow: 0 8px 28px var(--uvf-accent-1-20);
2161
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
1981
2162
  filter: saturate(1) brightness(1);
1982
2163
  }
1983
2164
  50% {
1984
- box-shadow: 0 12px 36px var(--uvf-accent-1-20), 0 0 40px rgba(255,0,0,0.1);
2165
+ box-shadow: 0 6px 20px var(--uvf-accent-1-20), 0 0 20px rgba(255,0,0,0.1);
1985
2166
  filter: saturate(1.05) brightness(1.02);
1986
2167
  }
1987
2168
  100% {
1988
- box-shadow: 0 8px 28px var(--uvf-accent-1-20);
2169
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
1989
2170
  filter: saturate(1) brightness(1);
1990
2171
  }
1991
2172
  }
@@ -2031,10 +2212,22 @@ export class WebPlayer extends BasePlayer {
2031
2212
  width: 100%;
2032
2213
  position: relative;
2033
2214
  cursor: pointer;
2034
- padding: 16px 0;
2215
+ padding: 6px 0;
2035
2216
  overflow: visible;
2036
2217
  }
2037
2218
 
2219
+ /* Extended touch area for better mobile UX without affecting visual spacing */
2220
+ .uvf-progress-bar-wrapper::before {
2221
+ content: '';
2222
+ position: absolute;
2223
+ top: -8px;
2224
+ bottom: -8px;
2225
+ left: 0;
2226
+ right: 0;
2227
+ cursor: pointer;
2228
+ z-index: 10;
2229
+ }
2230
+
2038
2231
  .uvf-progress-bar {
2039
2232
  width: 100%;
2040
2233
  height: 2px;
@@ -2146,7 +2339,7 @@ export class WebPlayer extends BasePlayer {
2146
2339
  /* Mobile responsive design with enhanced touch targets */
2147
2340
  @media (max-width: 768px) {
2148
2341
  .uvf-progress-bar-wrapper {
2149
- padding: 20px 0; /* Larger touch area */
2342
+ padding: 8px 0; /* Optimized touch area */
2150
2343
  }
2151
2344
 
2152
2345
  .uvf-progress-bar {
@@ -3214,13 +3407,13 @@ export class WebPlayer extends BasePlayer {
3214
3407
  }
3215
3408
 
3216
3409
  .uvf-player-wrapper.uvf-fullscreen .uvf-center-play-btn {
3217
- width: 80px;
3218
- height: 80px;
3410
+ width: 64px;
3411
+ height: 64px;
3219
3412
  }
3220
3413
 
3221
3414
  .uvf-player-wrapper.uvf-fullscreen .uvf-center-play-btn svg {
3222
- width: 35px;
3223
- height: 35px;
3415
+ width: 28px;
3416
+ height: 28px;
3224
3417
  }
3225
3418
 
3226
3419
  /* Ensure overlays remain visible in fullscreen with consistent spacing */
@@ -3819,7 +4012,6 @@ export class WebPlayer extends BasePlayer {
3819
4012
  height: 44px !important;
3820
4013
  min-width: 44px !important;
3821
4014
  min-height: 44px !important;
3822
- background: rgba(255,255,255,0.15) !important;
3823
4015
  backdrop-filter: blur(8px) !important;
3824
4016
  border-radius: 22px !important;
3825
4017
  align-items: center !important;
@@ -4085,22 +4277,22 @@ export class WebPlayer extends BasePlayer {
4085
4277
 
4086
4278
  /* Tablet center play button - consistent theming */
4087
4279
  .uvf-center-play-btn {
4088
- width: clamp(76px, 15vw, 88px);
4089
- height: clamp(76px, 15vw, 88px);
4280
+ width: clamp(48px, 10vw, 64px);
4281
+ height: clamp(48px, 10vw, 64px);
4090
4282
  background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4091
4283
  border: 0;
4092
- box-shadow: 0 8px 24px var(--uvf-accent-1-20);
4284
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
4093
4285
  }
4094
4286
 
4095
4287
  .uvf-center-play-btn:hover {
4096
- transform: scale(1.06);
4288
+ transform: scale(1.08);
4097
4289
  filter: saturate(1.08) brightness(1.05);
4098
- box-shadow: 0 12px 32px var(--uvf-accent-1-20);
4290
+ box-shadow: 0 6px 20px var(--uvf-accent-1-20);
4099
4291
  }
4100
4292
 
4101
4293
  .uvf-center-play-btn svg {
4102
- width: clamp(30px, 4.8vw, 34px);
4103
- height: clamp(30px, 4.8vw, 34px);
4294
+ width: clamp(20px, 3.5vw, 26px);
4295
+ height: clamp(20px, 3.5vw, 26px);
4104
4296
  }
4105
4297
 
4106
4298
  /* Tablet volume control - keep desktop functionality */
@@ -4249,8 +4441,8 @@ export class WebPlayer extends BasePlayer {
4249
4441
 
4250
4442
  /* Enhanced center play button with smooth transitions */
4251
4443
  .uvf-center-play-btn {
4252
- width: clamp(80px, 12vw, 96px);
4253
- height: clamp(80px, 12vw, 96px);
4444
+ width: clamp(56px, 10vw, 72px);
4445
+ height: clamp(56px, 10vw, 72px);
4254
4446
  background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4255
4447
  border: 0;
4256
4448
  border-radius: 50%;
@@ -4260,13 +4452,13 @@ export class WebPlayer extends BasePlayer {
4260
4452
  align-items: center;
4261
4453
  justify-content: center;
4262
4454
  cursor: pointer;
4263
- box-shadow: 0 8px 28px var(--uvf-accent-1-20);
4455
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
4264
4456
  }
4265
4457
 
4266
4458
  .uvf-center-play-btn:hover {
4267
- transform: scale(1.06);
4459
+ transform: scale(1.08);
4268
4460
  filter: saturate(1.08) brightness(1.05);
4269
- box-shadow: 0 12px 36px var(--uvf-accent-1-20);
4461
+ box-shadow: 0 6px 20px var(--uvf-accent-1-20);
4270
4462
  }
4271
4463
 
4272
4464
  .uvf-center-play-btn:active {
@@ -4275,11 +4467,11 @@ export class WebPlayer extends BasePlayer {
4275
4467
  }
4276
4468
 
4277
4469
  .uvf-center-play-btn svg {
4278
- width: clamp(32px, 5vw, 40px);
4279
- height: clamp(32px, 5vw, 40px);
4470
+ width: clamp(24px, 4vw, 30px);
4471
+ height: clamp(24px, 4vw, 30px);
4280
4472
  fill: #fff;
4281
- margin-left: 5px;
4282
- filter: drop-shadow(0 2px 6px rgba(0,0,0,0.35));
4473
+ margin-left: 3px;
4474
+ filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
4283
4475
  }
4284
4476
 
4285
4477
  /* Optional: Add subtle pulse animation on idle */
@@ -4360,23 +4552,23 @@ export class WebPlayer extends BasePlayer {
4360
4552
  }
4361
4553
 
4362
4554
  .uvf-center-play-btn {
4363
- width: clamp(90px, 14vw, 60px);
4364
- height: clamp(90px, 14vw, 60px);
4555
+ width: clamp(64px, 10vw, 76px);
4556
+ height: clamp(64px, 10vw, 76px);
4365
4557
  background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4366
4558
  border: 0;
4367
- box-shadow: 0 10px 32px var(--uvf-accent-1-20);
4559
+ box-shadow: 0 4px 16px var(--uvf-accent-1-20);
4368
4560
  }
4369
4561
 
4370
4562
  .uvf-center-play-btn:hover {
4371
- transform: scale(1.06);
4563
+ transform: scale(1.08);
4372
4564
  filter: saturate(1.08) brightness(1.05);
4373
- box-shadow: 0 16px 40px var(--uvf-accent-1-20);
4565
+ box-shadow: 0 6px 20px var(--uvf-accent-1-20);
4374
4566
  }
4375
4567
 
4376
4568
  .uvf-center-play-btn svg {
4377
- width: clamp(38px, 6vw, 44px);
4378
- height: clamp(38px, 6vw, 44px);
4379
- margin-left: 6px;
4569
+ width: clamp(28px, 4.5vw, 32px);
4570
+ height: clamp(28px, 4.5vw, 32px);
4571
+ margin-left: 3px;
4380
4572
  }
4381
4573
 
4382
4574
  .uvf-video-title {