mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-09 00:48: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;
|
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 {
|
.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 */
|
/* Make seekbar more touch-friendly on Android */
|
||||||
@ -551,9 +551,9 @@ button.vjs-button > .vjs-icon-placeholder:before {
|
|||||||
border-radius: 0 !important;
|
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 {
|
.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 {
|
.video-container {
|
||||||
|
|||||||
@ -65,6 +65,39 @@
|
|||||||
overflow: hidden !important;
|
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 {
|
.youtube-seek-icon {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
|
|||||||
@ -48,13 +48,32 @@ class SeekIndicator extends Component {
|
|||||||
clearTimeout(this.showTimeout);
|
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
|
// Set content based on direction - YouTube-style circular design
|
||||||
if (direction === 'forward') {
|
if (direction === 'forward') {
|
||||||
iconEl.innerHTML = `
|
iconEl.innerHTML = `
|
||||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 80px;
|
width: ${circleSize};
|
||||||
height: 80px;
|
height: ${circleSize};
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -69,14 +88,14 @@ class SeekIndicator extends Component {
|
|||||||
-moz-border-radius: 50%;
|
-moz-border-radius: 50%;
|
||||||
">
|
">
|
||||||
<div style="display: flex; align-items: center; justify-content: center; margin-bottom: 4px;">
|
<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="M8 5v14l11-7z"/>
|
||||||
<path d="M13 5v14l11-7z" opacity="0.6"/>
|
<path d="M13 5v14l11-7z" opacity="0.6"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div style="
|
<div style="
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 10px;
|
font-size: ${textSize};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
@ -90,8 +109,8 @@ class SeekIndicator extends Component {
|
|||||||
iconEl.innerHTML = `
|
iconEl.innerHTML = `
|
||||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 80px;
|
width: ${circleSize};
|
||||||
height: 80px;
|
height: ${circleSize};
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -106,14 +125,14 @@ class SeekIndicator extends Component {
|
|||||||
-moz-border-radius: 50%;
|
-moz-border-radius: 50%;
|
||||||
">
|
">
|
||||||
<div style="display: flex; align-items: center; justify-content: center; margin-bottom: 4px;">
|
<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="M16 19V5l-11 7z"/>
|
||||||
<path d="M11 19V5L0 12z" opacity="0.6"/>
|
<path d="M11 19V5L0 12z" opacity="0.6"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div style="
|
<div style="
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 10px;
|
font-size: ${textSize};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
@ -127,8 +146,8 @@ class SeekIndicator extends Component {
|
|||||||
iconEl.innerHTML = `
|
iconEl.innerHTML = `
|
||||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 80px;
|
width: ${circleSize};
|
||||||
height: 80px;
|
height: ${circleSize};
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -139,19 +158,19 @@ class SeekIndicator extends Component {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
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"/>
|
<path d="M8 5v14l11-7z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
textEl.textContent = 'Play';
|
textEl.textContent = 'Play';
|
||||||
} else if (direction === 'pause') {
|
} else if (direction === 'pause' || direction === 'pause-mobile') {
|
||||||
iconEl.innerHTML = `
|
iconEl.innerHTML = `
|
||||||
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 80px;
|
width: ${circleSize};
|
||||||
height: 80px;
|
height: ${circleSize};
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -162,7 +181,7 @@ class SeekIndicator extends Component {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
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"/>
|
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@ -193,10 +212,73 @@ class SeekIndicator extends Component {
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Auto-hide after 1 second
|
// Auto-hide timing based on action type
|
||||||
this.showTimeout = setTimeout(() => {
|
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();
|
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.display = 'none';
|
||||||
el.style.visibility = 'hidden';
|
el.style.visibility = 'hidden';
|
||||||
}, 200); // Wait for fade out animation
|
}, 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];
|
const touch = e.touches[0];
|
||||||
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
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
|
const progressControl = playerRef.current
|
||||||
.getChild('controlBar')
|
.getChild('controlBar')
|
||||||
?.getChild('progressControl');
|
?.getChild('progressControl');
|
||||||
if (progressControl && progressControl.el()) {
|
if (progressControl && progressControl.el()) {
|
||||||
const progressRect = progressControl.el().getBoundingClientRect();
|
const progressRect = progressControl.el().getBoundingClientRect();
|
||||||
|
const seekbarDeadZone = 8; // Only 8px above seekbar is protected for easier seeking
|
||||||
const isInSeekbarArea =
|
const isInSeekbarArea =
|
||||||
touch.clientY >= progressRect.top && touch.clientY <= progressRect.bottom;
|
touch.clientY >= progressRect.top - seekbarDeadZone &&
|
||||||
|
touch.clientY <= progressRect.bottom;
|
||||||
if (isInSeekbarArea) {
|
if (isInSeekbarArea) {
|
||||||
playerRef.current.seekbarTouching = true;
|
playerRef.current.seekbarTouching = true;
|
||||||
}
|
}
|
||||||
@ -2142,15 +2144,40 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
window.getComputedStyle(controlBarEl).opacity !== '0' &&
|
window.getComputedStyle(controlBarEl).opacity !== '0' &&
|
||||||
window.getComputedStyle(controlBarEl).visibility !== 'hidden';
|
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()) {
|
if (playerRef.current.paused()) {
|
||||||
// Always play if video is paused
|
// Always play if video is paused
|
||||||
playerRef.current.play();
|
playerRef.current.play();
|
||||||
|
} else if (isTapOnCenterIcon) {
|
||||||
|
// Pause if tapping on center icon (highest priority)
|
||||||
|
playerRef.current.pause();
|
||||||
} else if (isControlsVisible) {
|
} else if (isControlsVisible) {
|
||||||
// Only pause if controls are actually visible
|
// Pause if controls are visible and not touching seekbar area
|
||||||
playerRef.current.pause();
|
playerRef.current.pause();
|
||||||
} else {
|
} 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);
|
playerRef.current.userActive(true);
|
||||||
|
if (seekIndicator) {
|
||||||
|
seekIndicator.showMobilePauseIcon();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#page-embed .video-js-root-embed .video-js .vjs-progress-control {
|
#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;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user