diff --git a/frontend-tools/video-js/src/VideoJS.css b/frontend-tools/video-js/src/VideoJS.css index 8b1242d2..bf8ac341 100644 --- a/frontend-tools/video-js/src/VideoJS.css +++ b/frontend-tools/video-js/src/VideoJS.css @@ -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 { diff --git a/frontend-tools/video-js/src/components/controls/SeekIndicator.css b/frontend-tools/video-js/src/components/controls/SeekIndicator.css index 63f9fb32..2740a47d 100644 --- a/frontend-tools/video-js/src/components/controls/SeekIndicator.css +++ b/frontend-tools/video-js/src/components/controls/SeekIndicator.css @@ -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; diff --git a/frontend-tools/video-js/src/components/controls/SeekIndicator.js b/frontend-tools/video-js/src/components/controls/SeekIndicator.js index b9d2e3c5..fd8dab9e 100644 --- a/frontend-tools/video-js/src/components/controls/SeekIndicator.js +++ b/frontend-tools/video-js/src/components/controls/SeekIndicator.js @@ -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 = `
- +
- +
- +
`; textEl.textContent = 'Play'; - } else if (direction === 'pause') { + } else if (direction === 'pause' || direction === 'pause-mobile') { iconEl.innerHTML = `
- +
@@ -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'; } /** diff --git a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx index e0570e8e..0748bccd 100644 --- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx +++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx @@ -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(); + } } } } diff --git a/frontend-tools/video-js/src/styles/embed.css b/frontend-tools/video-js/src/styles/embed.css index f699c96e..f22fb161 100644 --- a/frontend-tools/video-js/src/styles/embed.css +++ b/frontend-tools/video-js/src/styles/embed.css @@ -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; }