diff --git a/frontend-tools/video-js/src/VideoJS.css b/frontend-tools/video-js/src/VideoJS.css index 104849ee..b0b1e166 100644 --- a/frontend-tools/video-js/src/VideoJS.css +++ b/frontend-tools/video-js/src/VideoJS.css @@ -8,6 +8,28 @@ html { box-sizing: inherit; } +/* Global tooltip disabling for touch devices */ +@media (hover: none) and (pointer: coarse) { + /* Disable all CSS-based tooltips on touch devices */ + .video-js .vjs-control:hover::after, + .video-js .vjs-control:focus::after, + .video-js .vjs-control:active::after, + .video-js button.vjs-button:hover span.vjs-control-text, + .vjs-chapter-marker:hover .vjs-chapter-marker-tooltip, + .vjs-chapter-floating-tooltip, + .vjs-sprite-preview-tooltip { + display: none !important; + opacity: 0 !important; + visibility: hidden !important; + } + + /* Exception: Allow intentional touch-activated tooltips */ + .video-js .vjs-autoplay-toggle.touch-active::after { + opacity: 1 !important; + visibility: visible !important; + } +} + .playlist-items a { text-decoration: none !important; } @@ -1342,6 +1364,56 @@ 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) { @@ -1410,6 +1482,7 @@ button.vjs-button > .vjs-icon-placeholder:before { grid-template-columns: repeat(3, 1fr); } + /* Disable all tooltips on tablets */ .video-js .vjs-control:hover::after, .video-js .vjs-control:focus::after, .video-js .vjs-control:active::after { @@ -1432,6 +1505,26 @@ button.vjs-button > .vjs-icon-placeholder:before { opacity: 0 !important; visibility: hidden !important; } + + /* Disable Video.js native control text tooltips on tablets */ + .video-js button.vjs-button:hover span.vjs-control-text { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter marker tooltips on tablets */ + .vjs-chapter-marker:hover .vjs-chapter-marker-tooltip { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter floating tooltips on tablets */ + .vjs-chapter-floating-tooltip, + .vjs-sprite-preview-tooltip { + display: none !important; + opacity: 0 !important; + visibility: hidden !important; + } } @media (max-width: 1024px) { @@ -1531,6 +1624,7 @@ button.vjs-button > .vjs-icon-placeholder:before { grid-template-columns: repeat(2, 1fr); } + /* Disable all tooltips on mobile */ .video-js .vjs-control:hover::after, .video-js .vjs-control:focus::after, .video-js .vjs-control:active::after { @@ -1554,6 +1648,27 @@ button.vjs-button > .vjs-icon-placeholder:before { visibility: hidden !important; } + /* Disable Video.js native control text tooltips on mobile */ + .video-js button.vjs-button:hover span.vjs-control-text { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter marker tooltips on mobile */ + .vjs-chapter-marker:hover .vjs-chapter-marker-tooltip { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter floating tooltips on mobile */ + .vjs-chapter-floating-tooltip, + .vjs-sprite-preview-tooltip { + display: none !important; + opacity: 0 !important; + visibility: hidden !important; + } + + /* Exception: Allow touch-activated autoplay tooltip on mobile */ .video-js .vjs-autoplay-toggle.touch-active::after { opacity: 1; visibility: visible; @@ -1569,9 +1684,6 @@ button.vjs-button > .vjs-icon-placeholder:before { .vjs-related-video-thumbnail { height: 100%; } - .vjs-chapter-floating-tooltip { - font-size: 11px !important; - } .video-js-root-main .video-js.video-js-rounded-corners .custom-chapters-overlay { border-bottom-left-radius: 12px !important; border-bottom-right-radius: 12px !important; @@ -1665,6 +1777,7 @@ button.vjs-button > .vjs-icon-placeholder:before { padding: 0 10px; } + /* Disable all tooltips on small mobile screens */ .video-js .vjs-control:hover::after, .video-js .vjs-control:focus::after, .video-js .vjs-control:active::after { @@ -1688,6 +1801,26 @@ button.vjs-button > .vjs-icon-placeholder:before { visibility: hidden !important; } + /* Disable Video.js native control text tooltips on small mobile */ + .video-js button.vjs-button:hover span.vjs-control-text { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter marker tooltips on small mobile */ + .vjs-chapter-marker:hover .vjs-chapter-marker-tooltip { + opacity: 0 !important; + visibility: hidden !important; + } + + /* Disable chapter floating tooltips on small mobile */ + .vjs-chapter-floating-tooltip, + .vjs-sprite-preview-tooltip { + display: none !important; + opacity: 0 !important; + visibility: hidden !important; + } + .vjs-related-video-thumbnail { height: 100%; } diff --git a/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js b/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js index ef16dc5b..cd7ba4a1 100644 --- a/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js +++ b/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js @@ -113,6 +113,18 @@ class AutoplayToggleButton extends Button { // Add touch support for mobile tooltips addTouchSupport(button) { + // Check if device is touch-enabled + const isTouchDevice = + this.options_.isTouchDevice || + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; + + // Only add touch tooltip support on actual touch devices + if (!isTouchDevice) { + return; + } + let touchStartTime = 0; let touchHandled = false; @@ -132,19 +144,20 @@ class AutoplayToggleButton extends Button { (e) => { const touchDuration = Date.now() - touchStartTime; - // Only show tooltip for quick taps (not swipes) - if (touchDuration < 500) { + // Only show tooltip for quick taps (not swipes) and only on mobile screens + const isMobileScreen = window.innerWidth <= 767; + if (touchDuration < 500 && isMobileScreen) { e.preventDefault(); e.stopPropagation(); - // Show tooltip + // Show tooltip briefly button.classList.add('touch-active'); touchHandled = true; - // Hide tooltip after delay + // Hide tooltip after shorter delay on mobile setTimeout(() => { button.classList.remove('touch-active'); - }, 2000); + }, 1500); } }, { passive: false } diff --git a/frontend-tools/video-js/src/components/markers/ChapterMarkers.js b/frontend-tools/video-js/src/components/markers/ChapterMarkers.js index c0a02468..4def10ad 100644 --- a/frontend-tools/video-js/src/components/markers/ChapterMarkers.js +++ b/frontend-tools/video-js/src/components/markers/ChapterMarkers.js @@ -73,6 +73,18 @@ class ChapterMarkers extends Component { } setupProgressBarHover() { + // Check if device is touch-enabled (tablet/mobile) + const isTouchDevice = + this.options_.isTouchDevice || + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; + + // Skip tooltip setup on touch devices + if (isTouchDevice) { + return; + } + const progressControl = this.player().getChild('controlBar').getChild('progressControl'); if (!progressControl) return; diff --git a/frontend-tools/video-js/src/components/markers/SpritePreview.js b/frontend-tools/video-js/src/components/markers/SpritePreview.js index 282b874d..9e67b9c6 100644 --- a/frontend-tools/video-js/src/components/markers/SpritePreview.js +++ b/frontend-tools/video-js/src/components/markers/SpritePreview.js @@ -23,6 +23,18 @@ class SpritePreview extends Component { } setupProgressBarHover() { + // Check if device is touch-enabled (tablet/mobile) + const isTouchDevice = + this.options_.isTouchDevice || + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; + + // Skip tooltip setup on touch devices + if (isTouchDevice) { + return; + } + const progressControl = this.player().getChild('controlBar').getChild('progressControl'); if (!progressControl) return; diff --git a/frontend-tools/video-js/src/components/overlays/EmbedInfoOverlay.js b/frontend-tools/video-js/src/components/overlays/EmbedInfoOverlay.js index 269c4217..13281f29 100644 --- a/frontend-tools/video-js/src/components/overlays/EmbedInfoOverlay.js +++ b/frontend-tools/video-js/src/components/overlays/EmbedInfoOverlay.js @@ -183,6 +183,10 @@ class EmbedInfoOverlay extends Component { const player = this.player(); const overlay = this.el(); + // Check if device is touch-enabled + const isTouchDevice = + 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; + // Show/hide with controls player.on('useractive', () => { overlay.style.opacity = '1'; @@ -190,8 +194,15 @@ class EmbedInfoOverlay extends Component { }); player.on('userinactive', () => { - overlay.style.opacity = '0'; - overlay.style.visibility = 'hidden'; + // On touch devices, keep overlay visible longer or don't hide it as aggressively + if (isTouchDevice) { + // Keep visible on touch devices when user inactive + overlay.style.opacity = '0.8'; + overlay.style.visibility = 'visible'; + } else { + overlay.style.opacity = '0'; + overlay.style.visibility = 'hidden'; + } }); // Always show when paused 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 0350c8e4..a63101bb 100644 --- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx +++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx @@ -25,6 +25,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) { // Check if this is an embed player (disable next video and autoplay features) const isEmbedPlayer = videoId === 'video-embed'; + // Utility function to detect touch devices + const isTouchDevice = useMemo(() => { + return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; + }, []); + // Environment-based development mode configuration const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app'); // Safely access window.MEDIA_DATA with fallback using useMemo @@ -2152,6 +2157,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { try { const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, { userPreferences: userPreferences.current, + isTouchDevice: isTouchDevice, }); // Add it before the chapters button (or at a suitable position) const chaptersButtonIndex = chaptersButton @@ -2378,16 +2384,19 @@ function VideoJSPlayer({ videoId = 'default-video' }) { // Use original ChapterMarkers with sprite functionality when chapters exist const chapterMarkers = new ChapterMarkers(playerRef.current, { previewSprite: mediaData.previewSprite, + isTouchDevice: isTouchDevice, }); seekBar.addChild(chapterMarkers); - } else if (mediaData.previewSprite) { + } else if (mediaData.previewSprite && !isTouchDevice) { // Use separate SpritePreview component only when no chapters but sprite data exists + // Skip on touch devices to avoid unwanted tooltips const spritePreview = new SpritePreview(playerRef.current, { previewSprite: mediaData.previewSprite, + isTouchDevice: isTouchDevice, }); seekBar.addChild(spritePreview); - // Setup sprite preview hover functionality + // Setup sprite preview hover functionality (only on non-touch devices) setTimeout(() => { spritePreview.setupProgressBarHover(); }, 100); @@ -2466,6 +2475,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { userPreferences: userPreferences.current, qualities: availableQualities, hasSubtitles: hasSubtitles, + isTouchDevice: isTouchDevice, }); // If qualities change per video (e.g., via MEDIA_DATA update), refresh menu