fix: Make seekbar more touch-friendly on Android

This commit is contained in:
Yiannis Christodoulou 2025-09-28 18:02:56 +03:00
parent 57616c6b81
commit b04ad2344c
2 changed files with 164 additions and 53 deletions

View File

@ -23,6 +23,37 @@ html {
visibility: hidden !important;
}
/* Simple fix: Move seekbar up by 10px on touch devices */
.video-js .vjs-progress-control {
bottom: 56px !important; /* Move up 10px from original 46px */
}
/* Make seekbar more touch-friendly on Android */
.video-js .vjs-progress-holder {
touch-action: pan-x !important;
-webkit-touch-callout: none !important;
-webkit-user-select: none !important;
user-select: none !important;
}
.video-js .vjs-seek-bar {
touch-action: pan-x !important;
-webkit-touch-callout: none !important;
-webkit-user-select: none !important;
user-select: none !important;
}
/* Prevent big play button from interfering with seekbar on touch devices */
.video-js .vjs-big-play-button {
pointer-events: auto !important;
z-index: 1 !important; /* Lower than seekbar */
}
.video-js .vjs-progress-control {
z-index: 10 !important; /* Higher than big play button */
pointer-events: auto !important;
}
/* Exception: Allow intentional touch-activated tooltips */
.video-js .vjs-autoplay-toggle.touch-active::after {
opacity: 1 !important;
@ -744,6 +775,7 @@ html {
.vjs-slider-horizontal {
top: -5px;
}
.video-js .vjs-spacer-control {
flex: 1 !important;
min-width: 1px !important;
@ -1364,56 +1396,6 @@ button.vjs-button > .vjs-icon-placeholder:before {
width: auto;
opacity: 1;
}
/* Disable all tooltips on touch devices */
.video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
.video-js .vjs-play-control:hover::after,
.video-js .vjs-mute-control:hover::after,
.video-js .vjs-volume-panel:hover::after,
.video-js .vjs-fullscreen-control:hover::after,
.video-js .vjs-picture-in-picture-control:hover::after,
.video-js .vjs-settings-control:hover::after,
.video-js .vjs-chapters-control:hover::after,
.video-js .vjs-autoplay-toggle:hover::after,
.video-js .vjs-next-video-control:hover::after,
.video-js .vjs-remaining-time:hover::after {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable Video.js native control text tooltips on touch devices */
.video-js button.vjs-button:hover span.vjs-control-text {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter marker tooltips on touch devices */
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter floating tooltips on touch devices */
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Exception: Allow touch-activated autoplay tooltip on touch devices */
.video-js .vjs-autoplay-toggle.touch-active::after {
opacity: 1 !important;
visibility: visible !important;
}
}
@media (min-width: 1200px) {
@ -1535,6 +1517,11 @@ button.vjs-button > .vjs-icon-placeholder:before {
}
@media (max-width: 767px) {
/* Move seekbar up by 10px on mobile to prevent accidental button touches */
.video-js .vjs-progress-control {
bottom: 56px !important; /* Move up 10px from original 46px */
}
.vjs-related-vdeo-item:nth-child(n + 5) {
display: none;
}
@ -1773,6 +1760,11 @@ button.vjs-button > .vjs-icon-placeholder:before {
}
@media (max-width: 480px) {
/* Move seekbar up by 10px on small mobile to prevent accidental button touches */
.video-js .vjs-progress-control {
bottom: 56px !important; /* Move up 10px from original 46px */
}
.video-container {
padding: 0 10px;
}

View File

@ -1047,7 +1047,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
},
// other
useRoundedCorners: false,
useRoundedCorners: true,
isPlayList: true,
previewSprite: {
url: 'https://deic.mediacms.io/media/original/thumbnails/user/thorkild/2ca18fadeef8475eae513c12cc0830d3.19990812hd_1920_1080_30fps.mp4sprites.jpg',
@ -1998,14 +1998,27 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
touchStartTime = Date.now();
const touch = e.touches[0];
touchStartPos = { x: touch.clientX, y: touch.clientY };
// Check if touch is in seekbar area
const progressControl = playerRef.current
.getChild('controlBar')
?.getChild('progressControl');
if (progressControl && progressControl.el()) {
const progressRect = progressControl.el().getBoundingClientRect();
const isInSeekbarArea =
touch.clientY >= progressRect.top && touch.clientY <= progressRect.bottom;
if (isInSeekbarArea) {
playerRef.current.seekbarTouching = true;
}
}
};
const handleTouchEnd = (e) => {
const touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
// Only handle if it's a quick tap
if (touchDuration < 500) {
// Only handle if it's a quick tap and we're not touching the seekbar
if (touchDuration < 500 && !playerRef.current.seekbarTouching) {
const touch = e.changedTouches[0];
const touchEndPos = { x: touch.clientX, y: touch.clientY };
const distance = Math.sqrt(
@ -2025,6 +2038,13 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}
}
}
// Always clear seekbar touching flag at the end
setTimeout(() => {
if (playerRef.current) {
playerRef.current.seekbarTouching = false;
}
}, 50);
};
videoEl.addEventListener('touchstart', handleTouchStart, { passive: true });
@ -2506,6 +2526,100 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Store components reference for potential cleanup
// BEGIN: Fix Android seekbar touch functionality
if (isTouchDevice) {
setTimeout(() => {
const progressControl = playerRef.current
.getChild('controlBar')
?.getChild('progressControl');
const seekBar = progressControl?.getChild('seekBar');
if (seekBar && seekBar.el()) {
const seekBarEl = seekBar.el();
const progressHolder = seekBarEl.querySelector('.vjs-progress-holder');
if (progressHolder) {
let isDragging = false;
const handleTouchStart = (e) => {
isDragging = true;
e.preventDefault();
e.stopPropagation(); // Prevent event from reaching video element
playerRef.current.userActive(true);
// Mark that we're interacting with seekbar to prevent play/pause
playerRef.current.seekbarTouching = true;
// Temporarily disable big play button
const bigPlayButton = playerRef.current.getChild('bigPlayButton');
if (bigPlayButton && bigPlayButton.el()) {
bigPlayButton.el().style.pointerEvents = 'none';
bigPlayButton.el().style.touchAction = 'none';
}
};
const handleTouchMove = (e) => {
if (!isDragging) return;
e.preventDefault();
e.stopPropagation(); // Prevent event from reaching video element
const touch = e.touches[0];
const rect = progressHolder.getBoundingClientRect();
const percentage = Math.max(
0,
Math.min(1, (touch.clientX - rect.left) / rect.width)
);
const duration = playerRef.current.duration();
if (duration && !isNaN(duration)) {
const newTime = percentage * duration;
playerRef.current.currentTime(newTime);
}
};
const handleTouchEnd = (e) => {
isDragging = false;
e.preventDefault();
e.stopPropagation(); // Prevent event from reaching video element
// Re-enable big play button
const bigPlayButton = playerRef.current.getChild('bigPlayButton');
if (bigPlayButton && bigPlayButton.el()) {
setTimeout(() => {
bigPlayButton.el().style.pointerEvents = '';
bigPlayButton.el().style.touchAction = '';
}, 200);
}
// Clear the seekbar touching flag after a longer delay to prevent conflicts
setTimeout(() => {
if (playerRef.current) {
playerRef.current.seekbarTouching = false;
}
}, 300);
};
// Add touch event listeners specifically for Android
progressHolder.addEventListener('touchstart', handleTouchStart, {
passive: false,
});
progressHolder.addEventListener('touchmove', handleTouchMove, {
passive: false,
});
progressHolder.addEventListener('touchend', handleTouchEnd, { passive: false });
// Store cleanup function
customComponents.current.cleanupSeekbarTouch = () => {
progressHolder.removeEventListener('touchstart', handleTouchStart);
progressHolder.removeEventListener('touchmove', handleTouchMove);
progressHolder.removeEventListener('touchend', handleTouchEnd);
};
}
}
}, 500);
}
// END: Fix Android seekbar touch functionality
// BEGIN: Add comprehensive keyboard event handling
const handleAllKeyboardEvents = (event) => {
// Only handle if no input elements are focused
@ -2805,6 +2919,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
customComponents.current.cleanupArrowKeyHandler();
}
// Clean up seekbar touch handlers if they exist
if (customComponents.current && customComponents.current.cleanupSeekbarTouch) {
customComponents.current.cleanupSeekbarTouch();
}
if (playerRef.current && !playerRef.current.isDisposed()) {
playerRef.current.dispose();
playerRef.current = null;