unified-video-framework 1.4.176 → 1.4.177
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.
|
@@ -96,6 +96,23 @@ export class WebPlayer extends BasePlayer {
|
|
|
96
96
|
|
|
97
97
|
// Progress bar tooltip state
|
|
98
98
|
private showTimeTooltip: boolean = false;
|
|
99
|
+
|
|
100
|
+
// Advanced tap handling state
|
|
101
|
+
private tapStartTime: number = 0;
|
|
102
|
+
private tapStartX: number = 0;
|
|
103
|
+
private tapStartY: number = 0;
|
|
104
|
+
private lastTapTime: number = 0;
|
|
105
|
+
private lastTapX: number = 0;
|
|
106
|
+
private tapCount: number = 0;
|
|
107
|
+
private longPressTimer: NodeJS.Timeout | null = null;
|
|
108
|
+
private isLongPressing: boolean = false;
|
|
109
|
+
private longPressPlaybackRate: number = 1;
|
|
110
|
+
private tapResetTimer: NodeJS.Timeout | null = null;
|
|
111
|
+
private fastBackwardInterval: NodeJS.Timeout | null = null;
|
|
112
|
+
private handleSingleTap: () => void = () => {};
|
|
113
|
+
private handleDoubleTap: (tapX: number) => void = () => {};
|
|
114
|
+
private handleLongPress: (tapX: number) => void = () => {};
|
|
115
|
+
private handleLongPressEnd: () => void = () => {};
|
|
99
116
|
|
|
100
117
|
// Autoplay enhancement state
|
|
101
118
|
private autoplayCapabilities: {
|
|
@@ -5922,25 +5939,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
5922
5939
|
centerPlay?.addEventListener('click', () => this.togglePlayPause());
|
|
5923
5940
|
playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
|
|
5924
5941
|
|
|
5925
|
-
// Video click behavior
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
e.stopPropagation();
|
|
5930
|
-
const wrapper = this.container?.querySelector('.uvf-player-wrapper');
|
|
5931
|
-
if (wrapper?.classList.contains('controls-visible')) {
|
|
5932
|
-
this.hideControls();
|
|
5933
|
-
} else {
|
|
5934
|
-
this.showControls();
|
|
5935
|
-
if (this.state.isPlaying) {
|
|
5936
|
-
this.scheduleHideControls();
|
|
5937
|
-
}
|
|
5938
|
-
}
|
|
5939
|
-
} else {
|
|
5940
|
-
// Desktop: toggle play/pause
|
|
5942
|
+
// Video click behavior will be handled by the comprehensive tap system below
|
|
5943
|
+
// Desktop click for play/pause
|
|
5944
|
+
if (!this.isMobileDevice()) {
|
|
5945
|
+
this.video.addEventListener('click', (e) => {
|
|
5941
5946
|
this.togglePlayPause();
|
|
5942
|
-
}
|
|
5943
|
-
}
|
|
5947
|
+
});
|
|
5948
|
+
}
|
|
5944
5949
|
|
|
5945
5950
|
// Update play/pause icons
|
|
5946
5951
|
this.video.addEventListener('play', () => {
|
|
@@ -6538,22 +6543,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
6538
6543
|
this.lastUserInteraction = Date.now();
|
|
6539
6544
|
});
|
|
6540
6545
|
|
|
6541
|
-
//
|
|
6542
|
-
this.
|
|
6543
|
-
// Don't trigger if double-clicking on controls
|
|
6544
|
-
const target = e.target as HTMLElement;
|
|
6545
|
-
if (!target.closest('.uvf-controls')) {
|
|
6546
|
-
e.preventDefault();
|
|
6547
|
-
this.debugLog('Double-click detected - attempting fullscreen');
|
|
6548
|
-
|
|
6549
|
-
if (!document.fullscreenElement) {
|
|
6550
|
-
// Always use the fullscreen button for maximum reliability
|
|
6551
|
-
this.triggerFullscreenButton();
|
|
6552
|
-
} else {
|
|
6553
|
-
this.exitFullscreen();
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
});
|
|
6546
|
+
// Advanced tap handling system for mobile
|
|
6547
|
+
this.setupAdvancedTapHandling();
|
|
6557
6548
|
}
|
|
6558
6549
|
|
|
6559
6550
|
// Add to the video element
|
|
@@ -7106,6 +7097,241 @@ export class WebPlayer extends BasePlayer {
|
|
|
7106
7097
|
}
|
|
7107
7098
|
}, timeout);
|
|
7108
7099
|
}
|
|
7100
|
+
|
|
7101
|
+
/**
|
|
7102
|
+
* Setup advanced tap handling for mobile with:
|
|
7103
|
+
* - Single tap: toggle controls
|
|
7104
|
+
* - Double tap left: skip backward 10s
|
|
7105
|
+
* - Double tap right: skip forward 10s
|
|
7106
|
+
* - Long press left: 2x speed backward
|
|
7107
|
+
* - Long press right: 2x speed forward
|
|
7108
|
+
*/
|
|
7109
|
+
private setupAdvancedTapHandling(): void {
|
|
7110
|
+
if (!this.video || !this.playerWrapper) return;
|
|
7111
|
+
|
|
7112
|
+
const DOUBLE_TAP_DELAY = 300; // ms
|
|
7113
|
+
const LONG_PRESS_DELAY = 500; // ms
|
|
7114
|
+
const TAP_MOVEMENT_THRESHOLD = 10; // pixels
|
|
7115
|
+
const SKIP_SECONDS = 10;
|
|
7116
|
+
const FAST_PLAYBACK_RATE = 2;
|
|
7117
|
+
|
|
7118
|
+
const videoElement = this.video;
|
|
7119
|
+
const wrapper = this.playerWrapper;
|
|
7120
|
+
|
|
7121
|
+
// Touch start handler
|
|
7122
|
+
const handleTouchStart = (e: TouchEvent) => {
|
|
7123
|
+
// Ignore if touching controls
|
|
7124
|
+
const target = e.target as HTMLElement;
|
|
7125
|
+
if (target.closest('.uvf-controls')) {
|
|
7126
|
+
return;
|
|
7127
|
+
}
|
|
7128
|
+
|
|
7129
|
+
const touch = e.touches[0];
|
|
7130
|
+
this.tapStartTime = Date.now();
|
|
7131
|
+
this.tapStartX = touch.clientX;
|
|
7132
|
+
this.tapStartY = touch.clientY;
|
|
7133
|
+
|
|
7134
|
+
// Start long press timer
|
|
7135
|
+
this.longPressTimer = setTimeout(() => {
|
|
7136
|
+
this.isLongPressing = true;
|
|
7137
|
+
this.handleLongPress(this.tapStartX);
|
|
7138
|
+
}, LONG_PRESS_DELAY);
|
|
7139
|
+
};
|
|
7140
|
+
|
|
7141
|
+
// Touch move handler
|
|
7142
|
+
const handleTouchMove = (e: TouchEvent) => {
|
|
7143
|
+
const touch = e.touches[0];
|
|
7144
|
+
const deltaX = Math.abs(touch.clientX - this.tapStartX);
|
|
7145
|
+
const deltaY = Math.abs(touch.clientY - this.tapStartY);
|
|
7146
|
+
|
|
7147
|
+
// Cancel long press if moved too much
|
|
7148
|
+
if (deltaX > TAP_MOVEMENT_THRESHOLD || deltaY > TAP_MOVEMENT_THRESHOLD) {
|
|
7149
|
+
if (this.longPressTimer) {
|
|
7150
|
+
clearTimeout(this.longPressTimer);
|
|
7151
|
+
this.longPressTimer = null;
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
};
|
|
7155
|
+
|
|
7156
|
+
// Touch end handler
|
|
7157
|
+
const handleTouchEnd = (e: TouchEvent) => {
|
|
7158
|
+
// Clear long press timer
|
|
7159
|
+
if (this.longPressTimer) {
|
|
7160
|
+
clearTimeout(this.longPressTimer);
|
|
7161
|
+
this.longPressTimer = null;
|
|
7162
|
+
}
|
|
7163
|
+
|
|
7164
|
+
// Handle long press end
|
|
7165
|
+
if (this.isLongPressing) {
|
|
7166
|
+
this.handleLongPressEnd();
|
|
7167
|
+
this.isLongPressing = false;
|
|
7168
|
+
return;
|
|
7169
|
+
}
|
|
7170
|
+
|
|
7171
|
+
// Ignore if touching controls
|
|
7172
|
+
const target = e.target as HTMLElement;
|
|
7173
|
+
if (target.closest('.uvf-controls')) {
|
|
7174
|
+
return;
|
|
7175
|
+
}
|
|
7176
|
+
|
|
7177
|
+
const touch = e.changedTouches[0];
|
|
7178
|
+
const touchEndX = touch.clientX;
|
|
7179
|
+
const touchEndY = touch.clientY;
|
|
7180
|
+
const tapDuration = Date.now() - this.tapStartTime;
|
|
7181
|
+
|
|
7182
|
+
// Check if it was a tap (not a drag)
|
|
7183
|
+
const deltaX = Math.abs(touchEndX - this.tapStartX);
|
|
7184
|
+
const deltaY = Math.abs(touchEndY - this.tapStartY);
|
|
7185
|
+
|
|
7186
|
+
if (deltaX > TAP_MOVEMENT_THRESHOLD || deltaY > TAP_MOVEMENT_THRESHOLD) {
|
|
7187
|
+
// It was a drag, not a tap
|
|
7188
|
+
return;
|
|
7189
|
+
}
|
|
7190
|
+
|
|
7191
|
+
// Check if it was a quick tap (not a long press)
|
|
7192
|
+
if (tapDuration > LONG_PRESS_DELAY) {
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7195
|
+
|
|
7196
|
+
// Determine if this is a double tap
|
|
7197
|
+
const now = Date.now();
|
|
7198
|
+
const timeSinceLastTap = now - this.lastTapTime;
|
|
7199
|
+
|
|
7200
|
+
if (timeSinceLastTap < DOUBLE_TAP_DELAY && Math.abs(touchEndX - this.lastTapX) < 100) {
|
|
7201
|
+
// Double tap detected
|
|
7202
|
+
this.tapCount = 2;
|
|
7203
|
+
if (this.tapResetTimer) {
|
|
7204
|
+
clearTimeout(this.tapResetTimer);
|
|
7205
|
+
this.tapResetTimer = null;
|
|
7206
|
+
}
|
|
7207
|
+
this.handleDoubleTap(touchEndX);
|
|
7208
|
+
} else {
|
|
7209
|
+
// First tap or single tap
|
|
7210
|
+
this.tapCount = 1;
|
|
7211
|
+
this.lastTapTime = now;
|
|
7212
|
+
this.lastTapX = touchEndX;
|
|
7213
|
+
|
|
7214
|
+
// Wait to see if there's a second tap
|
|
7215
|
+
if (this.tapResetTimer) {
|
|
7216
|
+
clearTimeout(this.tapResetTimer);
|
|
7217
|
+
}
|
|
7218
|
+
this.tapResetTimer = setTimeout(() => {
|
|
7219
|
+
if (this.tapCount === 1) {
|
|
7220
|
+
// Single tap confirmed
|
|
7221
|
+
this.handleSingleTap();
|
|
7222
|
+
}
|
|
7223
|
+
this.tapCount = 0;
|
|
7224
|
+
}, DOUBLE_TAP_DELAY);
|
|
7225
|
+
}
|
|
7226
|
+
};
|
|
7227
|
+
|
|
7228
|
+
// Single tap: toggle controls
|
|
7229
|
+
const handleSingleTap = () => {
|
|
7230
|
+
this.debugLog('Single tap detected - toggling controls');
|
|
7231
|
+
const wrapper = this.container?.querySelector('.uvf-player-wrapper');
|
|
7232
|
+
if (wrapper?.classList.contains('controls-visible')) {
|
|
7233
|
+
this.hideControls();
|
|
7234
|
+
} else {
|
|
7235
|
+
this.showControls();
|
|
7236
|
+
if (this.state.isPlaying) {
|
|
7237
|
+
this.scheduleHideControls();
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
};
|
|
7241
|
+
|
|
7242
|
+
// Double tap: skip backward/forward based on screen side
|
|
7243
|
+
const handleDoubleTap = (tapX: number) => {
|
|
7244
|
+
if (!this.video || !wrapper) return;
|
|
7245
|
+
|
|
7246
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
7247
|
+
const tapPosition = tapX - wrapperRect.left;
|
|
7248
|
+
const wrapperWidth = wrapperRect.width;
|
|
7249
|
+
const isLeftSide = tapPosition < wrapperWidth / 2;
|
|
7250
|
+
|
|
7251
|
+
if (isLeftSide) {
|
|
7252
|
+
// Skip backward
|
|
7253
|
+
const newTime = Math.max(0, this.video.currentTime - SKIP_SECONDS);
|
|
7254
|
+
this.seek(newTime);
|
|
7255
|
+
this.showShortcutIndicator(`-${SKIP_SECONDS}s`);
|
|
7256
|
+
this.debugLog('Double tap left - skip backward');
|
|
7257
|
+
} else {
|
|
7258
|
+
// Skip forward
|
|
7259
|
+
const newTime = Math.min(this.video.duration, this.video.currentTime + SKIP_SECONDS);
|
|
7260
|
+
this.seek(newTime);
|
|
7261
|
+
this.showShortcutIndicator(`+${SKIP_SECONDS}s`);
|
|
7262
|
+
this.debugLog('Double tap right - skip forward');
|
|
7263
|
+
}
|
|
7264
|
+
};
|
|
7265
|
+
|
|
7266
|
+
// Long press: fast forward/rewind based on screen side
|
|
7267
|
+
const handleLongPress = (tapX: number) => {
|
|
7268
|
+
if (!this.video || !wrapper) return;
|
|
7269
|
+
|
|
7270
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
7271
|
+
const tapPosition = tapX - wrapperRect.left;
|
|
7272
|
+
const wrapperWidth = wrapperRect.width;
|
|
7273
|
+
const isLeftSide = tapPosition < wrapperWidth / 2;
|
|
7274
|
+
|
|
7275
|
+
// Save original playback rate
|
|
7276
|
+
this.longPressPlaybackRate = this.video.playbackRate;
|
|
7277
|
+
|
|
7278
|
+
if (isLeftSide) {
|
|
7279
|
+
// Fast backward by setting negative time interval
|
|
7280
|
+
this.showShortcutIndicator(`⏪ 2x`);
|
|
7281
|
+
this.debugLog('Long press left - fast backward');
|
|
7282
|
+
this.startFastBackward();
|
|
7283
|
+
} else {
|
|
7284
|
+
// Fast forward
|
|
7285
|
+
this.video.playbackRate = FAST_PLAYBACK_RATE;
|
|
7286
|
+
this.showShortcutIndicator(`⏩ 2x`);
|
|
7287
|
+
this.debugLog('Long press right - fast forward');
|
|
7288
|
+
}
|
|
7289
|
+
};
|
|
7290
|
+
|
|
7291
|
+
// Long press end: restore normal playback
|
|
7292
|
+
const handleLongPressEnd = () => {
|
|
7293
|
+
if (!this.video) return;
|
|
7294
|
+
|
|
7295
|
+
// Stop fast backward if active
|
|
7296
|
+
this.stopFastBackward();
|
|
7297
|
+
|
|
7298
|
+
// Restore original playback rate
|
|
7299
|
+
this.video.playbackRate = this.longPressPlaybackRate || 1;
|
|
7300
|
+
this.debugLog('Long press ended - restored playback rate');
|
|
7301
|
+
};
|
|
7302
|
+
|
|
7303
|
+
// Bind handlers
|
|
7304
|
+
this.handleSingleTap = handleSingleTap.bind(this);
|
|
7305
|
+
this.handleDoubleTap = handleDoubleTap.bind(this);
|
|
7306
|
+
this.handleLongPress = handleLongPress.bind(this);
|
|
7307
|
+
this.handleLongPressEnd = handleLongPressEnd.bind(this);
|
|
7308
|
+
|
|
7309
|
+
// Attach event listeners
|
|
7310
|
+
videoElement.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
7311
|
+
videoElement.addEventListener('touchmove', handleTouchMove, { passive: true });
|
|
7312
|
+
videoElement.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
7313
|
+
|
|
7314
|
+
this.debugLog('Advanced tap handling initialized');
|
|
7315
|
+
}
|
|
7316
|
+
|
|
7317
|
+
// Fast backward using interval-based seeking
|
|
7318
|
+
private startFastBackward(): void {
|
|
7319
|
+
if (!this.video || this.fastBackwardInterval) return;
|
|
7320
|
+
|
|
7321
|
+
this.fastBackwardInterval = setInterval(() => {
|
|
7322
|
+
if (this.video) {
|
|
7323
|
+
const newTime = Math.max(0, this.video.currentTime - 0.1); // Go back 0.1s every frame
|
|
7324
|
+
this.video.currentTime = newTime;
|
|
7325
|
+
}
|
|
7326
|
+
}, 50); // Update every 50ms for smooth backward motion
|
|
7327
|
+
}
|
|
7328
|
+
|
|
7329
|
+
private stopFastBackward(): void {
|
|
7330
|
+
if (this.fastBackwardInterval) {
|
|
7331
|
+
clearInterval(this.fastBackwardInterval);
|
|
7332
|
+
this.fastBackwardInterval = null;
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7109
7335
|
|
|
7110
7336
|
private isFullscreen(): boolean {
|
|
7111
7337
|
return !!(document.fullscreenElement ||
|