unified-video-framework 1.4.147 → 1.4.149
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.
|
@@ -83,6 +83,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
83
83
|
// Fullscreen fallback tracking
|
|
84
84
|
private hasTriedButtonFallback: boolean = false;
|
|
85
85
|
private lastUserInteraction: number = 0;
|
|
86
|
+
|
|
87
|
+
// Progress bar tooltip state
|
|
88
|
+
private showTimeTooltip: boolean = false;
|
|
86
89
|
|
|
87
90
|
// Debug logging helper
|
|
88
91
|
private debugLog(message: string, ...args: any[]): void {
|
|
@@ -153,8 +156,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
153
156
|
this.video = document.createElement('video');
|
|
154
157
|
this.video.className = 'uvf-video';
|
|
155
158
|
this.video.controls = false; // We'll use custom controls
|
|
159
|
+
// For autoplay to work, video must be muted in most browsers
|
|
156
160
|
this.video.autoplay = this.config.autoPlay ?? false;
|
|
157
|
-
this.video.muted = this.config.muted ?? false;
|
|
161
|
+
this.video.muted = this.config.autoPlay ? true : (this.config.muted ?? false);
|
|
158
162
|
this.video.loop = this.config.loop ?? false;
|
|
159
163
|
this.video.playsInline = this.config.playsInline ?? true;
|
|
160
164
|
this.video.preload = this.config.preload ?? 'metadata';
|
|
@@ -193,6 +197,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
193
197
|
this.setupKeyboardShortcuts();
|
|
194
198
|
this.setupWatermark();
|
|
195
199
|
this.setupFullscreenListeners();
|
|
200
|
+
this.setupUserInteractionTracking();
|
|
196
201
|
|
|
197
202
|
// Initialize paywall controller if provided
|
|
198
203
|
try {
|
|
@@ -518,7 +523,15 @@ export class WebPlayer extends BasePlayer {
|
|
|
518
523
|
|
|
519
524
|
// Start playback if autoPlay is enabled
|
|
520
525
|
if (this.config.autoPlay) {
|
|
521
|
-
|
|
526
|
+
// Attempt autoplay, but handle gracefully if blocked
|
|
527
|
+
this.play().catch(error => {
|
|
528
|
+
if (this.isAutoplayRestrictionError(error)) {
|
|
529
|
+
this.debugWarn('HLS autoplay blocked, showing play overlay');
|
|
530
|
+
this.showPlayOverlay();
|
|
531
|
+
} else {
|
|
532
|
+
this.debugError('HLS autoplay failed:', error);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
522
535
|
}
|
|
523
536
|
});
|
|
524
537
|
|
|
@@ -681,6 +694,190 @@ export class WebPlayer extends BasePlayer {
|
|
|
681
694
|
);
|
|
682
695
|
}
|
|
683
696
|
|
|
697
|
+
private isAutoplayRestrictionError(err: any): boolean {
|
|
698
|
+
if (!err) return false;
|
|
699
|
+
|
|
700
|
+
const message = (err.message || '').toLowerCase();
|
|
701
|
+
const name = (err.name || '').toLowerCase();
|
|
702
|
+
|
|
703
|
+
// Common autoplay restriction error patterns
|
|
704
|
+
return (
|
|
705
|
+
name === 'notallowederror' ||
|
|
706
|
+
message.includes('user didn\'t interact') ||
|
|
707
|
+
message.includes('autoplay') ||
|
|
708
|
+
message.includes('gesture') ||
|
|
709
|
+
message.includes('user activation') ||
|
|
710
|
+
message.includes('play() failed') ||
|
|
711
|
+
message.includes('user interaction')
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private showPlayOverlay(): void {
|
|
716
|
+
// Remove existing overlay
|
|
717
|
+
this.hidePlayOverlay();
|
|
718
|
+
|
|
719
|
+
const overlay = document.createElement('div');
|
|
720
|
+
overlay.id = 'uvf-play-overlay';
|
|
721
|
+
overlay.className = 'uvf-play-overlay';
|
|
722
|
+
|
|
723
|
+
const playButton = document.createElement('button');
|
|
724
|
+
playButton.className = 'uvf-play-button';
|
|
725
|
+
playButton.innerHTML = `
|
|
726
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
727
|
+
<path d="M8 5v14l11-7z"/>
|
|
728
|
+
</svg>
|
|
729
|
+
`;
|
|
730
|
+
|
|
731
|
+
const message = document.createElement('div');
|
|
732
|
+
message.className = 'uvf-play-message';
|
|
733
|
+
message.textContent = 'Click to play';
|
|
734
|
+
|
|
735
|
+
overlay.appendChild(playButton);
|
|
736
|
+
overlay.appendChild(message);
|
|
737
|
+
|
|
738
|
+
// Add click handler
|
|
739
|
+
playButton.addEventListener('click', async (e) => {
|
|
740
|
+
e.stopPropagation();
|
|
741
|
+
this.lastUserInteraction = Date.now();
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
await this.play();
|
|
745
|
+
} catch (error) {
|
|
746
|
+
this.debugError('Failed to play after user interaction:', error);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// Also allow clicking anywhere on overlay
|
|
751
|
+
overlay.addEventListener('click', async (e) => {
|
|
752
|
+
if (e.target === overlay) {
|
|
753
|
+
e.stopPropagation();
|
|
754
|
+
this.lastUserInteraction = Date.now();
|
|
755
|
+
|
|
756
|
+
try {
|
|
757
|
+
await this.play();
|
|
758
|
+
} catch (error) {
|
|
759
|
+
this.debugError('Failed to play after user interaction:', error);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// Add styles
|
|
765
|
+
const style = document.createElement('style');
|
|
766
|
+
style.textContent = `
|
|
767
|
+
.uvf-play-overlay {
|
|
768
|
+
position: absolute;
|
|
769
|
+
top: 0;
|
|
770
|
+
left: 0;
|
|
771
|
+
width: 100%;
|
|
772
|
+
height: 100%;
|
|
773
|
+
background: rgba(0, 0, 0, 0.7);
|
|
774
|
+
display: flex;
|
|
775
|
+
flex-direction: column;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
align-items: center;
|
|
778
|
+
z-index: 1000;
|
|
779
|
+
cursor: pointer;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.uvf-play-button {
|
|
783
|
+
width: 80px;
|
|
784
|
+
height: 80px;
|
|
785
|
+
border-radius: 50%;
|
|
786
|
+
background: rgba(255, 255, 255, 0.9);
|
|
787
|
+
border: none;
|
|
788
|
+
color: #000;
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
display: flex;
|
|
791
|
+
align-items: center;
|
|
792
|
+
justify-content: center;
|
|
793
|
+
transition: all 0.3s ease;
|
|
794
|
+
margin-bottom: 16px;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.uvf-play-button:hover {
|
|
798
|
+
background: #fff;
|
|
799
|
+
transform: scale(1.1);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.uvf-play-button svg {
|
|
803
|
+
width: 32px;
|
|
804
|
+
height: 32px;
|
|
805
|
+
margin-left: 4px;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.uvf-play-message {
|
|
809
|
+
color: white;
|
|
810
|
+
font-size: 16px;
|
|
811
|
+
font-weight: 500;
|
|
812
|
+
text-align: center;
|
|
813
|
+
opacity: 0.9;
|
|
814
|
+
}
|
|
815
|
+
`;
|
|
816
|
+
|
|
817
|
+
// Add to page if not already added
|
|
818
|
+
if (!document.getElementById('uvf-play-overlay-styles')) {
|
|
819
|
+
style.id = 'uvf-play-overlay-styles';
|
|
820
|
+
document.head.appendChild(style);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Add to player
|
|
824
|
+
if (this.playerWrapper) {
|
|
825
|
+
this.playerWrapper.appendChild(overlay);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private hidePlayOverlay(): void {
|
|
830
|
+
const overlay = document.getElementById('uvf-play-overlay');
|
|
831
|
+
if (overlay) {
|
|
832
|
+
overlay.remove();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private updateTimeTooltip(e: MouseEvent): void {
|
|
837
|
+
const progressBar = document.getElementById('uvf-progress-bar');
|
|
838
|
+
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
839
|
+
if (!progressBar || !tooltip || !this.video) return;
|
|
840
|
+
|
|
841
|
+
const rect = progressBar.getBoundingClientRect();
|
|
842
|
+
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
|
843
|
+
const percent = (x / rect.width);
|
|
844
|
+
const time = percent * this.video.duration;
|
|
845
|
+
|
|
846
|
+
// Update tooltip content and position
|
|
847
|
+
tooltip.textContent = this.formatTime(time);
|
|
848
|
+
tooltip.style.left = `${x}px`;
|
|
849
|
+
tooltip.classList.add('visible');
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private hideTimeTooltip(): void {
|
|
853
|
+
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
854
|
+
if (tooltip) {
|
|
855
|
+
tooltip.classList.remove('visible');
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private setupUserInteractionTracking(): void {
|
|
860
|
+
// Track various user interactions to enable autoplay
|
|
861
|
+
const interactionEvents = ['click', 'mousedown', 'keydown', 'touchstart'];
|
|
862
|
+
|
|
863
|
+
const updateLastInteraction = () => {
|
|
864
|
+
this.lastUserInteraction = Date.now();
|
|
865
|
+
this.debugLog('User interaction detected at:', this.lastUserInteraction);
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// Listen on document for global interactions
|
|
869
|
+
interactionEvents.forEach(eventType => {
|
|
870
|
+
document.addEventListener(eventType, updateLastInteraction, { passive: true });
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// Also listen on player wrapper for more specific interactions
|
|
874
|
+
if (this.playerWrapper) {
|
|
875
|
+
interactionEvents.forEach(eventType => {
|
|
876
|
+
this.playerWrapper!.addEventListener(eventType, updateLastInteraction, { passive: true });
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
684
881
|
async play(): Promise<void> {
|
|
685
882
|
if (!this.video) throw new Error('Video element not initialized');
|
|
686
883
|
|
|
@@ -710,6 +907,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
710
907
|
this.video.pause();
|
|
711
908
|
}
|
|
712
909
|
|
|
910
|
+
// Hide the play overlay if it exists
|
|
911
|
+
this.hidePlayOverlay();
|
|
713
912
|
await super.play();
|
|
714
913
|
} catch (err) {
|
|
715
914
|
this._playPromise = null;
|
|
@@ -717,6 +916,14 @@ export class WebPlayer extends BasePlayer {
|
|
|
717
916
|
// Benign: pause() raced play(); ignore the error.
|
|
718
917
|
return;
|
|
719
918
|
}
|
|
919
|
+
|
|
920
|
+
// Check if this is an autoplay restriction error
|
|
921
|
+
if (this.isAutoplayRestrictionError(err)) {
|
|
922
|
+
this.debugWarn('Autoplay blocked by browser policy - showing play overlay');
|
|
923
|
+
this.showPlayOverlay();
|
|
924
|
+
return; // Don't throw error for autoplay restrictions
|
|
925
|
+
}
|
|
926
|
+
|
|
720
927
|
this.handleError({
|
|
721
928
|
code: 'PLAY_ERROR',
|
|
722
929
|
message: `Failed to start playbook: ${err}`,
|
|
@@ -2031,10 +2238,22 @@ export class WebPlayer extends BasePlayer {
|
|
|
2031
2238
|
width: 100%;
|
|
2032
2239
|
position: relative;
|
|
2033
2240
|
cursor: pointer;
|
|
2034
|
-
padding:
|
|
2241
|
+
padding: 6px 0;
|
|
2035
2242
|
overflow: visible;
|
|
2036
2243
|
}
|
|
2037
2244
|
|
|
2245
|
+
/* Extended touch area for better mobile UX without affecting visual spacing */
|
|
2246
|
+
.uvf-progress-bar-wrapper::before {
|
|
2247
|
+
content: '';
|
|
2248
|
+
position: absolute;
|
|
2249
|
+
top: -8px;
|
|
2250
|
+
bottom: -8px;
|
|
2251
|
+
left: 0;
|
|
2252
|
+
right: 0;
|
|
2253
|
+
cursor: pointer;
|
|
2254
|
+
z-index: 10;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2038
2257
|
.uvf-progress-bar {
|
|
2039
2258
|
width: 100%;
|
|
2040
2259
|
height: 2px;
|
|
@@ -2115,30 +2334,102 @@ export class WebPlayer extends BasePlayer {
|
|
|
2115
2334
|
left: 0;
|
|
2116
2335
|
height: 100%;
|
|
2117
2336
|
background: linear-gradient(90deg,
|
|
2118
|
-
#ff4500 0%,
|
|
2119
|
-
#ff5722 25%,
|
|
2120
|
-
#ff6b35 50%,
|
|
2121
|
-
#ff7043 75%,
|
|
2122
|
-
#ff8c69 100%
|
|
2337
|
+
var(--uvf-accent-1, #ff4500) 0%,
|
|
2338
|
+
var(--uvf-accent-1, #ff5722) 25%,
|
|
2339
|
+
var(--uvf-accent-2, #ff6b35) 50%,
|
|
2340
|
+
var(--uvf-accent-2, #ff7043) 75%,
|
|
2341
|
+
var(--uvf-accent-2, #ff8c69) 100%
|
|
2123
2342
|
);
|
|
2124
2343
|
border-radius: 4px;
|
|
2125
2344
|
pointer-events: none;
|
|
2126
2345
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
2127
2346
|
z-index: 2;
|
|
2128
|
-
box-shadow: 0 0 12px rgba(255, 87, 34, 0.3);
|
|
2347
|
+
box-shadow: 0 0 12px var(--uvf-accent-1-20, rgba(255, 87, 34, 0.3));
|
|
2129
2348
|
}
|
|
2130
2349
|
|
|
2131
2350
|
.uvf-progress-bar-wrapper:hover .uvf-progress-filled {
|
|
2132
2351
|
border-radius: 6px;
|
|
2133
2352
|
background: linear-gradient(90deg,
|
|
2134
|
-
#ff4500 0%,
|
|
2135
|
-
#ff5722 20%,
|
|
2136
|
-
#ff6b35 40%,
|
|
2137
|
-
#ff7043 60%,
|
|
2138
|
-
#ff8c69 80%,
|
|
2139
|
-
#ffa500 100%
|
|
2353
|
+
var(--uvf-accent-1, #ff4500) 0%,
|
|
2354
|
+
var(--uvf-accent-1, #ff5722) 20%,
|
|
2355
|
+
var(--uvf-accent-2, #ff6b35) 40%,
|
|
2356
|
+
var(--uvf-accent-2, #ff7043) 60%,
|
|
2357
|
+
var(--uvf-accent-2, #ff8c69) 80%,
|
|
2358
|
+
var(--uvf-accent-2, #ffa500) 100%
|
|
2140
2359
|
);
|
|
2141
|
-
box-shadow: 0 0 20px rgba(255, 87, 34, 0.5);
|
|
2360
|
+
box-shadow: 0 0 20px var(--uvf-accent-1-20, rgba(255, 87, 34, 0.5));
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/* Progress Bar Handle/Thumb */
|
|
2364
|
+
.uvf-progress-handle {
|
|
2365
|
+
position: absolute;
|
|
2366
|
+
top: 50%;
|
|
2367
|
+
left: 0;
|
|
2368
|
+
width: 14px;
|
|
2369
|
+
height: 14px;
|
|
2370
|
+
background: #fff;
|
|
2371
|
+
border: 2px solid var(--uvf-accent-1, #ff5722);
|
|
2372
|
+
border-radius: 50%;
|
|
2373
|
+
transform: translate(-50%, -50%);
|
|
2374
|
+
cursor: grab;
|
|
2375
|
+
opacity: 0;
|
|
2376
|
+
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
2377
|
+
z-index: 3;
|
|
2378
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.uvf-progress-bar-wrapper:hover .uvf-progress-handle {
|
|
2382
|
+
opacity: 1;
|
|
2383
|
+
transform: translate(-50%, -50%) scale(1);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.uvf-progress-handle:hover {
|
|
2387
|
+
transform: translate(-50%, -50%) scale(1.2);
|
|
2388
|
+
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.4);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
.uvf-progress-handle:active,
|
|
2392
|
+
.uvf-progress-handle.dragging {
|
|
2393
|
+
cursor: grabbing;
|
|
2394
|
+
transform: translate(-50%, -50%) scale(1.3);
|
|
2395
|
+
box-shadow: 0 4px 16px rgba(255, 87, 34, 0.4);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/* Time Tooltip */
|
|
2399
|
+
.uvf-time-tooltip {
|
|
2400
|
+
position: absolute;
|
|
2401
|
+
bottom: 100%;
|
|
2402
|
+
left: 0;
|
|
2403
|
+
margin-bottom: 8px;
|
|
2404
|
+
padding: 4px 8px;
|
|
2405
|
+
background: rgba(0, 0, 0, 0.8);
|
|
2406
|
+
color: #fff;
|
|
2407
|
+
font-size: 12px;
|
|
2408
|
+
font-weight: 500;
|
|
2409
|
+
border-radius: 4px;
|
|
2410
|
+
white-space: nowrap;
|
|
2411
|
+
opacity: 0;
|
|
2412
|
+
transform: translateX(-50%) translateY(4px);
|
|
2413
|
+
transition: all 0.2s ease;
|
|
2414
|
+
pointer-events: none;
|
|
2415
|
+
z-index: 20;
|
|
2416
|
+
backdrop-filter: blur(4px);
|
|
2417
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
.uvf-time-tooltip::after {
|
|
2421
|
+
content: '';
|
|
2422
|
+
position: absolute;
|
|
2423
|
+
top: 100%;
|
|
2424
|
+
left: 50%;
|
|
2425
|
+
transform: translateX(-50%);
|
|
2426
|
+
border: 4px solid transparent;
|
|
2427
|
+
border-top-color: rgba(0, 0, 0, 0.8);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
.uvf-time-tooltip.visible {
|
|
2431
|
+
opacity: 1;
|
|
2432
|
+
transform: translateX(-50%) translateY(0);
|
|
2142
2433
|
}
|
|
2143
2434
|
|
|
2144
2435
|
|
|
@@ -2146,7 +2437,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
2146
2437
|
/* Mobile responsive design with enhanced touch targets */
|
|
2147
2438
|
@media (max-width: 768px) {
|
|
2148
2439
|
.uvf-progress-bar-wrapper {
|
|
2149
|
-
padding:
|
|
2440
|
+
padding: 8px 0; /* Optimized touch area */
|
|
2150
2441
|
}
|
|
2151
2442
|
|
|
2152
2443
|
.uvf-progress-bar {
|
|
@@ -4788,7 +5079,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
4788
5079
|
<div class="uvf-progress-bar">
|
|
4789
5080
|
<div class="uvf-progress-buffered" id="uvf-progress-buffered"></div>
|
|
4790
5081
|
<div class="uvf-progress-filled" id="uvf-progress-filled"></div>
|
|
5082
|
+
<div class="uvf-progress-handle" id="uvf-progress-handle"></div>
|
|
4791
5083
|
</div>
|
|
5084
|
+
<div class="uvf-time-tooltip" id="uvf-time-tooltip">00:00</div>
|
|
4792
5085
|
`;
|
|
4793
5086
|
progressSection.appendChild(progressBar);
|
|
4794
5087
|
|
|
@@ -5145,7 +5438,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
5145
5438
|
});
|
|
5146
5439
|
|
|
5147
5440
|
|
|
5148
|
-
// Progress bar
|
|
5441
|
+
// Progress bar interactions
|
|
5149
5442
|
progressBar?.addEventListener('click', (e) => {
|
|
5150
5443
|
this.handleProgressChange(e as MouseEvent);
|
|
5151
5444
|
});
|
|
@@ -5155,7 +5448,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
5155
5448
|
this.handleProgressChange(e as MouseEvent);
|
|
5156
5449
|
});
|
|
5157
5450
|
|
|
5158
|
-
//
|
|
5451
|
+
// Hover tooltip functionality
|
|
5452
|
+
progressBar?.addEventListener('mouseenter', () => {
|
|
5453
|
+
this.showTimeTooltip = true;
|
|
5454
|
+
});
|
|
5455
|
+
|
|
5456
|
+
progressBar?.addEventListener('mouseleave', () => {
|
|
5457
|
+
this.showTimeTooltip = false;
|
|
5458
|
+
this.hideTimeTooltip();
|
|
5459
|
+
});
|
|
5460
|
+
|
|
5461
|
+
progressBar?.addEventListener('mousemove', (e) => {
|
|
5462
|
+
if (!this.isDragging && this.showTimeTooltip) {
|
|
5463
|
+
this.updateTimeTooltip(e as MouseEvent);
|
|
5464
|
+
}
|
|
5465
|
+
});
|
|
5466
|
+
|
|
5467
|
+
// Touch support for mobile devices
|
|
5468
|
+
progressBar?.addEventListener('touchstart', (e) => {
|
|
5469
|
+
e.preventDefault(); // Prevent scrolling
|
|
5470
|
+
this.isDragging = true;
|
|
5471
|
+
const touch = e.touches[0];
|
|
5472
|
+
this.handleProgressChange(touch);
|
|
5473
|
+
}, { passive: false });
|
|
5474
|
+
|
|
5475
|
+
// Global mouse and touch events for enhanced dragging
|
|
5159
5476
|
document.addEventListener('mousemove', (e) => {
|
|
5160
5477
|
if (this.isVolumeSliding) {
|
|
5161
5478
|
this.handleVolumeChange(e);
|
|
@@ -5165,6 +5482,14 @@ export class WebPlayer extends BasePlayer {
|
|
|
5165
5482
|
}
|
|
5166
5483
|
});
|
|
5167
5484
|
|
|
5485
|
+
document.addEventListener('touchmove', (e) => {
|
|
5486
|
+
if (this.isDragging && progressBar) {
|
|
5487
|
+
e.preventDefault(); // Prevent scrolling
|
|
5488
|
+
const touch = e.touches[0];
|
|
5489
|
+
this.handleProgressChange(touch);
|
|
5490
|
+
}
|
|
5491
|
+
}, { passive: false });
|
|
5492
|
+
|
|
5168
5493
|
document.addEventListener('mouseup', () => {
|
|
5169
5494
|
if (this.isVolumeSliding) {
|
|
5170
5495
|
this.isVolumeSliding = false;
|
|
@@ -5177,16 +5502,34 @@ export class WebPlayer extends BasePlayer {
|
|
|
5177
5502
|
|
|
5178
5503
|
if (this.isDragging) {
|
|
5179
5504
|
this.isDragging = false;
|
|
5505
|
+
// Remove dragging class from handle
|
|
5506
|
+
const handle = document.getElementById('uvf-progress-handle');
|
|
5507
|
+
handle?.classList.remove('dragging');
|
|
5508
|
+
}
|
|
5509
|
+
});
|
|
5510
|
+
|
|
5511
|
+
document.addEventListener('touchend', () => {
|
|
5512
|
+
if (this.isDragging) {
|
|
5513
|
+
this.isDragging = false;
|
|
5514
|
+
// Remove dragging class from handle
|
|
5515
|
+
const handle = document.getElementById('uvf-progress-handle');
|
|
5516
|
+
handle?.classList.remove('dragging');
|
|
5180
5517
|
}
|
|
5181
5518
|
});
|
|
5182
5519
|
|
|
5183
5520
|
// Update progress bar
|
|
5184
5521
|
this.video.addEventListener('timeupdate', () => {
|
|
5185
5522
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
5523
|
+
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
5186
5524
|
|
|
5187
5525
|
if (this.video && progressFilled) {
|
|
5188
5526
|
const percent = (this.video.currentTime / this.video.duration) * 100;
|
|
5189
5527
|
progressFilled.style.width = percent + '%';
|
|
5528
|
+
|
|
5529
|
+
// Update handle position (only when not dragging)
|
|
5530
|
+
if (progressHandle && !this.isDragging) {
|
|
5531
|
+
progressHandle.style.left = percent + '%';
|
|
5532
|
+
}
|
|
5190
5533
|
}
|
|
5191
5534
|
|
|
5192
5535
|
// Update time display using the dedicated method
|
|
@@ -5933,7 +6276,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
5933
6276
|
}
|
|
5934
6277
|
}
|
|
5935
6278
|
|
|
5936
|
-
private handleProgressChange(e: MouseEvent): void {
|
|
6279
|
+
private handleProgressChange(e: MouseEvent | Touch): void {
|
|
5937
6280
|
const progressBar = document.getElementById('uvf-progress-bar');
|
|
5938
6281
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
5939
6282
|
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
@@ -5950,6 +6293,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
5950
6293
|
}
|
|
5951
6294
|
if (progressHandle) {
|
|
5952
6295
|
progressHandle.style.left = percent + '%';
|
|
6296
|
+
// Add dragging class for visual feedback
|
|
6297
|
+
if (this.isDragging) {
|
|
6298
|
+
progressHandle.classList.add('dragging');
|
|
6299
|
+
} else {
|
|
6300
|
+
progressHandle.classList.remove('dragging');
|
|
6301
|
+
}
|
|
5953
6302
|
}
|
|
5954
6303
|
|
|
5955
6304
|
this.seek(time);
|