mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-08 16:38:54 -05:00
fix: Improve seekbar for mobile devices
This commit is contained in:
parent
1788ed27f4
commit
e885cc7c28
@ -26,9 +26,9 @@ html {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
/* Simple fix: Move seekbar up by 10px on touch devices */
|
||||
/* Simple fix: Move seekbar closer to controls on touch devices */
|
||||
.video-js .vjs-progress-control {
|
||||
bottom: 56px !important; /* Move up 10px from original 46px */
|
||||
bottom: 44px !important; /* Much closer to control bar - minimal gap */
|
||||
}
|
||||
|
||||
/* Make seekbar more touch-friendly on Android */
|
||||
@ -551,9 +551,9 @@ button.vjs-button > .vjs-icon-placeholder:before {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Move seekbar up by 10px on small mobile to prevent accidental button touches */
|
||||
/* Move seekbar closer to controls on small mobile */
|
||||
.video-js .vjs-progress-control {
|
||||
bottom: 56px !important; /* Move up 10px from original 46px */
|
||||
bottom: 44px !important; /* Much closer to control bar - minimal gap */
|
||||
}
|
||||
|
||||
.video-container {
|
||||
|
||||
@ -65,6 +65,39 @@
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Responsive sizing for mobile devices */
|
||||
@media (max-width: 768px) {
|
||||
.vjs-seek-indicator {
|
||||
top: calc(50% - 30px) !important; /* Move up slightly to avoid seekbar on tablet */
|
||||
}
|
||||
|
||||
.youtube-seek-circle {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
}
|
||||
|
||||
.youtube-seek-icon svg {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.vjs-seek-indicator {
|
||||
top: calc(50% - 40px) !important; /* Move up more to avoid seekbar on mobile */
|
||||
}
|
||||
|
||||
.youtube-seek-circle {
|
||||
width: 50px !important;
|
||||
height: 50px !important;
|
||||
}
|
||||
|
||||
.youtube-seek-icon svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.youtube-seek-icon {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
|
||||
@ -48,13 +48,32 @@ class SeekIndicator extends Component {
|
||||
clearTimeout(this.showTimeout);
|
||||
}
|
||||
|
||||
// Get responsive size based on screen width for all directions
|
||||
const isMobile = window.innerWidth <= 480;
|
||||
const isTablet = window.innerWidth <= 768 && window.innerWidth > 480;
|
||||
|
||||
let circleSize, iconSize, textSize;
|
||||
if (isMobile) {
|
||||
circleSize = '50px';
|
||||
iconSize = '20';
|
||||
textSize = '8px';
|
||||
} else if (isTablet) {
|
||||
circleSize = '60px';
|
||||
iconSize = '22';
|
||||
textSize = '9px';
|
||||
} else {
|
||||
circleSize = '80px';
|
||||
iconSize = '24';
|
||||
textSize = '10px';
|
||||
}
|
||||
|
||||
// Set content based on direction - YouTube-style circular design
|
||||
if (direction === 'forward') {
|
||||
iconEl.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: ${circleSize};
|
||||
height: ${circleSize};
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
@ -69,14 +88,14 @@ class SeekIndicator extends Component {
|
||||
-moz-border-radius: 50%;
|
||||
">
|
||||
<div style="display: flex; align-items: center; justify-content: center; margin-bottom: 4px;">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
<path d="M13 5v14l11-7z" opacity="0.6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-size: ${textSize};
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
@ -90,8 +109,8 @@ class SeekIndicator extends Component {
|
||||
iconEl.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: ${circleSize};
|
||||
height: ${circleSize};
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
@ -106,14 +125,14 @@ class SeekIndicator extends Component {
|
||||
-moz-border-radius: 50%;
|
||||
">
|
||||
<div style="display: flex; align-items: center; justify-content: center; margin-bottom: 4px;">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<path d="M16 19V5l-11 7z"/>
|
||||
<path d="M11 19V5L0 12z" opacity="0.6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-size: ${textSize};
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
@ -127,8 +146,8 @@ class SeekIndicator extends Component {
|
||||
iconEl.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: ${circleSize};
|
||||
height: ${circleSize};
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
@ -139,19 +158,19 @@ class SeekIndicator extends Component {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
">
|
||||
<svg viewBox="0 0 24 24" width="32" height="32" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
textEl.textContent = 'Play';
|
||||
} else if (direction === 'pause') {
|
||||
} else if (direction === 'pause' || direction === 'pause-mobile') {
|
||||
iconEl.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: ${circleSize};
|
||||
height: ${circleSize};
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
@ -162,7 +181,7 @@ class SeekIndicator extends Component {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
">
|
||||
<svg viewBox="0 0 24 24" width="32" height="32" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="white" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
@ -193,10 +212,73 @@ class SeekIndicator extends Component {
|
||||
padding: 0 !important;
|
||||
`;
|
||||
|
||||
// Auto-hide after 1 second
|
||||
this.showTimeout = setTimeout(() => {
|
||||
// Auto-hide timing based on action type
|
||||
if (direction === 'forward' || direction === 'backward') {
|
||||
// Seek operations: 1 second
|
||||
this.showTimeout = setTimeout(() => {
|
||||
this.hide();
|
||||
}, 1000);
|
||||
} else if (direction === 'play' || direction === 'pause' || direction === 'pause-mobile') {
|
||||
// Play/pause operations: 500ms
|
||||
this.showTimeout = setTimeout(() => {
|
||||
this.hide();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show pause icon for mobile (uses 500ms from main show method)
|
||||
*/
|
||||
showMobilePauseIcon() {
|
||||
this.show('pause-mobile'); // This will auto-hide after 500ms
|
||||
|
||||
// Make the icon clickable for mobile
|
||||
const el = this.el();
|
||||
el.style.pointerEvents = 'auto !important';
|
||||
|
||||
// Add click handler for the center icon
|
||||
const handleCenterIconClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.player().paused()) {
|
||||
this.player().play();
|
||||
} else {
|
||||
this.player().pause();
|
||||
}
|
||||
|
||||
// Hide immediately after click
|
||||
this.hide();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
el.addEventListener('click', handleCenterIconClick);
|
||||
el.addEventListener('touchend', handleCenterIconClick);
|
||||
|
||||
// Store handlers for cleanup
|
||||
this.mobileClickHandler = handleCenterIconClick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide mobile pause icon and clean up
|
||||
*/
|
||||
hideMobileIcon() {
|
||||
const el = this.el();
|
||||
|
||||
// Remove click handlers
|
||||
const allClickHandlers = el.cloneNode(true);
|
||||
el.parentNode.replaceChild(allClickHandlers, el);
|
||||
|
||||
// Reset pointer events
|
||||
allClickHandlers.style.pointerEvents = 'none !important';
|
||||
|
||||
// Hide the icon
|
||||
this.hide();
|
||||
|
||||
// Clear timeout
|
||||
if (this.mobileTimeout) {
|
||||
clearTimeout(this.mobileTimeout);
|
||||
this.mobileTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,6 +292,22 @@ class SeekIndicator extends Component {
|
||||
el.style.display = 'none';
|
||||
el.style.visibility = 'hidden';
|
||||
}, 200); // Wait for fade out animation
|
||||
|
||||
// Clear any existing timeout
|
||||
if (this.showTimeout) {
|
||||
clearTimeout(this.showTimeout);
|
||||
this.showTimeout = null;
|
||||
}
|
||||
|
||||
// Clean up mobile click handlers if they exist
|
||||
if (this.mobileClickHandler) {
|
||||
el.removeEventListener('click', this.mobileClickHandler);
|
||||
el.removeEventListener('touchend', this.mobileClickHandler);
|
||||
this.mobileClickHandler = null;
|
||||
}
|
||||
|
||||
// Reset pointer events
|
||||
el.style.pointerEvents = 'none !important';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2102,14 +2102,16 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
||||
const touch = e.touches[0];
|
||||
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
||||
|
||||
// Check if touch is in seekbar area
|
||||
// Check if touch is in seekbar area or the zone above it
|
||||
const progressControl = playerRef.current
|
||||
.getChild('controlBar')
|
||||
?.getChild('progressControl');
|
||||
if (progressControl && progressControl.el()) {
|
||||
const progressRect = progressControl.el().getBoundingClientRect();
|
||||
const seekbarDeadZone = 8; // Only 8px above seekbar is protected for easier seeking
|
||||
const isInSeekbarArea =
|
||||
touch.clientY >= progressRect.top && touch.clientY <= progressRect.bottom;
|
||||
touch.clientY >= progressRect.top - seekbarDeadZone &&
|
||||
touch.clientY <= progressRect.bottom;
|
||||
if (isInSeekbarArea) {
|
||||
playerRef.current.seekbarTouching = true;
|
||||
}
|
||||
@ -2142,15 +2144,40 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
||||
window.getComputedStyle(controlBarEl).opacity !== '0' &&
|
||||
window.getComputedStyle(controlBarEl).visibility !== 'hidden';
|
||||
|
||||
// Check if center play/pause icon is visible and if tap is on it
|
||||
const seekIndicator = customComponents.current.seekIndicator;
|
||||
const seekIndicatorEl = seekIndicator ? seekIndicator.el() : null;
|
||||
const isSeekIndicatorVisible =
|
||||
seekIndicatorEl &&
|
||||
window.getComputedStyle(seekIndicatorEl).opacity !== '0' &&
|
||||
window.getComputedStyle(seekIndicatorEl).visibility !== 'hidden' &&
|
||||
window.getComputedStyle(seekIndicatorEl).display !== 'none';
|
||||
|
||||
let isTapOnCenterIcon = false;
|
||||
if (seekIndicatorEl && isSeekIndicatorVisible) {
|
||||
const iconRect = seekIndicatorEl.getBoundingClientRect();
|
||||
isTapOnCenterIcon =
|
||||
touch.clientX >= iconRect.left &&
|
||||
touch.clientX <= iconRect.right &&
|
||||
touch.clientY >= iconRect.top &&
|
||||
touch.clientY <= iconRect.bottom;
|
||||
}
|
||||
|
||||
if (playerRef.current.paused()) {
|
||||
// Always play if video is paused
|
||||
playerRef.current.play();
|
||||
} else if (isTapOnCenterIcon) {
|
||||
// Pause if tapping on center icon (highest priority)
|
||||
playerRef.current.pause();
|
||||
} else if (isControlsVisible) {
|
||||
// Only pause if controls are actually visible
|
||||
// Pause if controls are visible and not touching seekbar area
|
||||
playerRef.current.pause();
|
||||
} else {
|
||||
// If controls are not visible, just show them (trigger user activity)
|
||||
// If controls are not visible, show them AND show center pause icon
|
||||
playerRef.current.userActive(true);
|
||||
if (seekIndicator) {
|
||||
seekIndicator.showMobilePauseIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
}
|
||||
|
||||
#page-embed .video-js-root-embed .video-js .vjs-progress-control {
|
||||
bottom: 56px !important; /* Adjust for larger control bar */
|
||||
bottom: 44px !important; /* Much closer to control bar - minimal gap */
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user