From 400bf899aa32438d0e7b32729b1779bb942f1450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Uyan=C4=B1k?= <99019289+MertUyanik@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:58:50 +0300 Subject: [PATCH] Fix control visibility and stabilize HLS loading --- src/components/ControlsLayer.css | 3 + src/components/ControlsLayer.tsx | 125 ++++++++++++++++++++++--------- src/components/VideoPlayer.tsx | 2 +- 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/components/ControlsLayer.css b/src/components/ControlsLayer.css index db4bd24..e1f2cd8 100644 --- a/src/components/ControlsLayer.css +++ b/src/components/ControlsLayer.css @@ -14,6 +14,9 @@ .controls-layer.hidden.playing { opacity: 0; +} + +.controls-layer.fullscreen.hidden.playing { cursor: none; } diff --git a/src/components/ControlsLayer.tsx b/src/components/ControlsLayer.tsx index e377944..b0e8fd6 100644 --- a/src/components/ControlsLayer.tsx +++ b/src/components/ControlsLayer.tsx @@ -28,60 +28,116 @@ export const ControlsLayer: React.FC = ({ subtitles = [], audioTracks = [], }) => { - const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls } = usePlayerContext() - const [mouseMoving, setMouseMoving] = useState(false) + const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls } = + usePlayerContext() + const [isPointerOver, setIsPointerOver] = useState(false) + const [lastInteraction, setLastInteraction] = useState(0) const hideTimeoutRef = useRef() const containerRef = useRef(null) const lastClickTimeRef = useRef(0) + const isMenuOpen = + uiState.settingsOpen || + uiState.volumeControlOpen || + uiState.qualityMenuOpen || + uiState.subtitleMenuOpen + const autoHideEnabled = videoState.fullscreen && videoState.playing && !isMenuOpen - // Auto-hide controls after inactivity + const clearHideTimeout = useCallback(() => { + if (hideTimeoutRef.current) { + window.clearTimeout(hideTimeoutRef.current) + hideTimeoutRef.current = undefined + } + }, []) + + const scheduleHide = useCallback(() => { + if (!autoHideEnabled) { + clearHideTimeout() + return + } + + clearHideTimeout() + hideTimeoutRef.current = window.setTimeout(() => { + hideControls() + }, 3000) + }, [autoHideEnabled, clearHideTimeout, hideControls]) + + // Keep controls visible when not playing or when any menu is open useEffect(() => { - if (videoState.playing) { - // Show controls on mouse movement - if (mouseMoving) { + if (!videoState.playing || isMenuOpen) { + clearHideTimeout() + showControls() + } + }, [videoState.playing, isMenuOpen, showControls, clearHideTimeout]) + + // Manage controls visibility when leaving fullscreen + useEffect(() => { + if (!videoState.fullscreen) { + clearHideTimeout() + if (!videoState.playing || isPointerOver) { showControls() } + } + }, [videoState.fullscreen, videoState.playing, isPointerOver, showControls, clearHideTimeout]) - // Clear existing timeout - if (hideTimeoutRef.current) { - window.clearTimeout(hideTimeoutRef.current) - } - - // Hide controls after inactivity (3 seconds in all modes) - if (mouseMoving) { - const hideDelay = 3000 - hideTimeoutRef.current = window.setTimeout(() => { - hideControls() - setMouseMoving(false) - }, hideDelay) - } - } else { - // Always show controls when paused - showControls() + // Re-schedule auto hide when interaction changes + useEffect(() => { + if (autoHideEnabled && lastInteraction > 0) { + scheduleHide() } return () => { - if (hideTimeoutRef.current) { - window.clearTimeout(hideTimeoutRef.current) + if (autoHideEnabled) { + clearHideTimeout() } } - }, [mouseMoving, videoState.playing, videoState.fullscreen, showControls, hideControls]) + }, [autoHideEnabled, lastInteraction, scheduleHide, clearHideTimeout]) + + const handleMouseEnter = useCallback(() => { + setIsPointerOver(true) + showControls() + if (autoHideEnabled) { + setLastInteraction(Date.now()) + } + }, [autoHideEnabled, showControls]) // Handle mouse movement const handleMouseMove = useCallback(() => { - if (!mouseMoving) { - setMouseMoving(true) + setIsPointerOver(true) + showControls() + if (autoHideEnabled) { + setLastInteraction(Date.now()) } - }, [mouseMoving]) + }, [autoHideEnabled, showControls]) const handleMouseLeave = useCallback(() => { - // Only hide controls on mouse leave when in fullscreen mode - // When player is small, controls should stay visible - setMouseMoving(false) - if (videoState.playing && videoState.fullscreen) { + setIsPointerOver(false) + clearHideTimeout() + if (videoState.fullscreen) { + if (videoState.playing) { + hideControls() + } else { + showControls() + } + } else if (videoState.playing) { hideControls() } - }, [videoState.playing, videoState.fullscreen, hideControls]) + }, [clearHideTimeout, videoState.fullscreen, videoState.playing, hideControls, showControls]) + + useEffect(() => { + return () => { + clearHideTimeout() + } + }, [clearHideTimeout]) + + const previousAutoHide = useRef(autoHideEnabled) + useEffect(() => { + if (autoHideEnabled && !previousAutoHide.current) { + showControls() + setLastInteraction(Date.now()) + } + + previousAutoHide.current = autoHideEnabled + }, [autoHideEnabled, showControls]) // Keyboard shortcuts useKeyboardShortcuts(keyboardShortcuts) @@ -132,12 +188,13 @@ export const ControlsLayer: React.FC = ({ const controlsClassName = `controls-layer ${uiState.controlsVisible ? 'visible' : 'hidden'} ${ videoState.playing ? 'playing' : 'paused' - }` + } ${videoState.fullscreen ? 'fullscreen' : 'windowed'}` return (