unified-video-framework 1.4.175 → 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: {
|
|
@@ -1351,11 +1368,15 @@ export class WebPlayer extends BasePlayer {
|
|
|
1351
1368
|
await (this.video as any).webkitEnterFullscreen();
|
|
1352
1369
|
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1353
1370
|
this.emit('onFullscreenChanged', true);
|
|
1371
|
+
// Lock to landscape orientation
|
|
1372
|
+
await this.lockOrientationLandscape();
|
|
1354
1373
|
return;
|
|
1355
1374
|
} else if ((this.video as any).webkitRequestFullscreen) {
|
|
1356
1375
|
await (this.video as any).webkitRequestFullscreen();
|
|
1357
1376
|
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1358
1377
|
this.emit('onFullscreenChanged', true);
|
|
1378
|
+
// Lock to landscape orientation
|
|
1379
|
+
await this.lockOrientationLandscape();
|
|
1359
1380
|
return;
|
|
1360
1381
|
}
|
|
1361
1382
|
} catch (iosError) {
|
|
@@ -1421,12 +1442,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
1421
1442
|
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1422
1443
|
this.emit('onFullscreenChanged', true);
|
|
1423
1444
|
|
|
1424
|
-
//
|
|
1425
|
-
|
|
1426
|
-
setTimeout(() => {
|
|
1427
|
-
this.showShortcutIndicator('Rotate device to landscape for best experience');
|
|
1428
|
-
}, 1000);
|
|
1429
|
-
}
|
|
1445
|
+
// Lock to landscape orientation on mobile devices
|
|
1446
|
+
await this.lockOrientationLandscape();
|
|
1430
1447
|
} else {
|
|
1431
1448
|
this.debugWarn('All fullscreen methods failed');
|
|
1432
1449
|
|
|
@@ -1457,6 +1474,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
1457
1474
|
this.playerWrapper.classList.remove('uvf-fullscreen');
|
|
1458
1475
|
}
|
|
1459
1476
|
this.emit('onFullscreenChanged', false);
|
|
1477
|
+
// Unlock orientation
|
|
1478
|
+
await this.unlockOrientation();
|
|
1460
1479
|
return;
|
|
1461
1480
|
}
|
|
1462
1481
|
} catch (iosError) {
|
|
@@ -1510,6 +1529,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
1510
1529
|
this.playerWrapper.classList.remove('uvf-fullscreen');
|
|
1511
1530
|
}
|
|
1512
1531
|
this.emit('onFullscreenChanged', false);
|
|
1532
|
+
// Unlock orientation
|
|
1533
|
+
await this.unlockOrientation();
|
|
1513
1534
|
} else {
|
|
1514
1535
|
this.debugWarn('All exit fullscreen methods failed');
|
|
1515
1536
|
// Still remove the class to keep UI consistent
|
|
@@ -5918,25 +5939,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
5918
5939
|
centerPlay?.addEventListener('click', () => this.togglePlayPause());
|
|
5919
5940
|
playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
|
|
5920
5941
|
|
|
5921
|
-
// Video click behavior
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
e.stopPropagation();
|
|
5926
|
-
const wrapper = this.container?.querySelector('.uvf-player-wrapper');
|
|
5927
|
-
if (wrapper?.classList.contains('controls-visible')) {
|
|
5928
|
-
this.hideControls();
|
|
5929
|
-
} else {
|
|
5930
|
-
this.showControls();
|
|
5931
|
-
if (this.state.isPlaying) {
|
|
5932
|
-
this.scheduleHideControls();
|
|
5933
|
-
}
|
|
5934
|
-
}
|
|
5935
|
-
} else {
|
|
5936
|
-
// 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) => {
|
|
5937
5946
|
this.togglePlayPause();
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5947
|
+
});
|
|
5948
|
+
}
|
|
5940
5949
|
|
|
5941
5950
|
// Update play/pause icons
|
|
5942
5951
|
this.video.addEventListener('play', () => {
|
|
@@ -6534,22 +6543,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
6534
6543
|
this.lastUserInteraction = Date.now();
|
|
6535
6544
|
});
|
|
6536
6545
|
|
|
6537
|
-
//
|
|
6538
|
-
this.
|
|
6539
|
-
// Don't trigger if double-clicking on controls
|
|
6540
|
-
const target = e.target as HTMLElement;
|
|
6541
|
-
if (!target.closest('.uvf-controls')) {
|
|
6542
|
-
e.preventDefault();
|
|
6543
|
-
this.debugLog('Double-click detected - attempting fullscreen');
|
|
6544
|
-
|
|
6545
|
-
if (!document.fullscreenElement) {
|
|
6546
|
-
// Always use the fullscreen button for maximum reliability
|
|
6547
|
-
this.triggerFullscreenButton();
|
|
6548
|
-
} else {
|
|
6549
|
-
this.exitFullscreen();
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
|
-
});
|
|
6546
|
+
// Advanced tap handling system for mobile
|
|
6547
|
+
this.setupAdvancedTapHandling();
|
|
6553
6548
|
}
|
|
6554
6549
|
|
|
6555
6550
|
// Add to the video element
|
|
@@ -6933,6 +6928,67 @@ export class WebPlayer extends BasePlayer {
|
|
|
6933
6928
|
);
|
|
6934
6929
|
}
|
|
6935
6930
|
|
|
6931
|
+
/**
|
|
6932
|
+
* Lock screen orientation to landscape when entering fullscreen
|
|
6933
|
+
*/
|
|
6934
|
+
private async lockOrientationLandscape(): Promise<void> {
|
|
6935
|
+
try {
|
|
6936
|
+
// Only attempt orientation lock on mobile devices
|
|
6937
|
+
if (!this.isMobileDevice()) {
|
|
6938
|
+
this.debugLog('Skipping orientation lock - not a mobile device');
|
|
6939
|
+
return;
|
|
6940
|
+
}
|
|
6941
|
+
|
|
6942
|
+
// Check if Screen Orientation API is supported
|
|
6943
|
+
const screenOrientation = screen.orientation as any;
|
|
6944
|
+
if (screenOrientation && typeof screenOrientation.lock === 'function') {
|
|
6945
|
+
try {
|
|
6946
|
+
// Try to lock to landscape orientation
|
|
6947
|
+
await screenOrientation.lock('landscape');
|
|
6948
|
+
this.debugLog('Screen orientation locked to landscape');
|
|
6949
|
+
} catch (error) {
|
|
6950
|
+
this.debugWarn('Failed to lock orientation to landscape:', (error as Error).message);
|
|
6951
|
+
// Some browsers require fullscreen to be active before locking orientation
|
|
6952
|
+
// If it fails, we'll just show a message
|
|
6953
|
+
if (this.isAndroidDevice()) {
|
|
6954
|
+
this.showShortcutIndicator('Please rotate device to landscape');
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
} else {
|
|
6958
|
+
// Fallback for older browsers or iOS (which doesn't support orientation lock)
|
|
6959
|
+
this.debugLog('Screen Orientation API not supported');
|
|
6960
|
+
if (this.isMobileDevice()) {
|
|
6961
|
+
// Show a subtle hint for devices that don't support orientation lock
|
|
6962
|
+
this.showShortcutIndicator('Rotate device to landscape for best experience');
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
} catch (error) {
|
|
6966
|
+
this.debugWarn('Orientation lock error:', (error as Error).message);
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
|
|
6970
|
+
/**
|
|
6971
|
+
* Unlock screen orientation when exiting fullscreen
|
|
6972
|
+
*/
|
|
6973
|
+
private async unlockOrientation(): Promise<void> {
|
|
6974
|
+
try {
|
|
6975
|
+
// Check if Screen Orientation API is supported
|
|
6976
|
+
const screenOrientation = screen.orientation as any;
|
|
6977
|
+
if (screenOrientation && typeof screenOrientation.unlock === 'function') {
|
|
6978
|
+
try {
|
|
6979
|
+
screenOrientation.unlock();
|
|
6980
|
+
this.debugLog('Screen orientation unlocked');
|
|
6981
|
+
} catch (error) {
|
|
6982
|
+
this.debugWarn('Failed to unlock orientation:', (error as Error).message);
|
|
6983
|
+
}
|
|
6984
|
+
} else {
|
|
6985
|
+
this.debugLog('Screen Orientation API not supported for unlock');
|
|
6986
|
+
}
|
|
6987
|
+
} catch (error) {
|
|
6988
|
+
this.debugWarn('Orientation unlock error:', (error as Error).message);
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
|
|
6936
6992
|
private handleVolumeChange(e: MouseEvent): void {
|
|
6937
6993
|
const slider = document.getElementById('uvf-volume-slider');
|
|
6938
6994
|
if (!slider) return;
|
|
@@ -7041,6 +7097,241 @@ export class WebPlayer extends BasePlayer {
|
|
|
7041
7097
|
}
|
|
7042
7098
|
}, timeout);
|
|
7043
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
|
+
}
|
|
7044
7335
|
|
|
7045
7336
|
private isFullscreen(): boolean {
|
|
7046
7337
|
return !!(document.fullscreenElement ||
|