Fix control visibility and stabilize HLS loading

This commit is contained in:
Mert Uyanık
2025-10-29 07:58:50 +03:00
parent b57b24d051
commit 400bf899aa
3 changed files with 95 additions and 35 deletions
+3
View File
@@ -14,6 +14,9 @@
.controls-layer.hidden.playing { .controls-layer.hidden.playing {
opacity: 0; opacity: 0;
}
.controls-layer.fullscreen.hidden.playing {
cursor: none; cursor: none;
} }
+88 -31
View File
@@ -28,60 +28,116 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
subtitles = [], subtitles = [],
audioTracks = [], audioTracks = [],
}) => { }) => {
const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls } = usePlayerContext() const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls } =
const [mouseMoving, setMouseMoving] = useState(false) usePlayerContext()
const [isPointerOver, setIsPointerOver] = useState(false)
const [lastInteraction, setLastInteraction] = useState<number>(0)
const hideTimeoutRef = useRef<number>() const hideTimeoutRef = useRef<number>()
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const lastClickTimeRef = useRef<number>(0) const lastClickTimeRef = useRef<number>(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(() => {
useEffect(() => {
if (videoState.playing) {
// Show controls on mouse movement
if (mouseMoving) {
showControls()
}
// Clear existing timeout
if (hideTimeoutRef.current) { if (hideTimeoutRef.current) {
window.clearTimeout(hideTimeoutRef.current) window.clearTimeout(hideTimeoutRef.current)
hideTimeoutRef.current = undefined
}
}, [])
const scheduleHide = useCallback(() => {
if (!autoHideEnabled) {
clearHideTimeout()
return
} }
// Hide controls after inactivity (3 seconds in all modes) clearHideTimeout()
if (mouseMoving) {
const hideDelay = 3000
hideTimeoutRef.current = window.setTimeout(() => { hideTimeoutRef.current = window.setTimeout(() => {
hideControls() hideControls()
setMouseMoving(false) }, 3000)
}, hideDelay) }, [autoHideEnabled, clearHideTimeout, hideControls])
}
} else { // Keep controls visible when not playing or when any menu is open
// Always show controls when paused useEffect(() => {
if (!videoState.playing || isMenuOpen) {
clearHideTimeout()
showControls() 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])
// Re-schedule auto hide when interaction changes
useEffect(() => {
if (autoHideEnabled && lastInteraction > 0) {
scheduleHide()
}
return () => { return () => {
if (hideTimeoutRef.current) { if (autoHideEnabled) {
window.clearTimeout(hideTimeoutRef.current) 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 // Handle mouse movement
const handleMouseMove = useCallback(() => { const handleMouseMove = useCallback(() => {
if (!mouseMoving) { setIsPointerOver(true)
setMouseMoving(true) showControls()
if (autoHideEnabled) {
setLastInteraction(Date.now())
} }
}, [mouseMoving]) }, [autoHideEnabled, showControls])
const handleMouseLeave = useCallback(() => { const handleMouseLeave = useCallback(() => {
// Only hide controls on mouse leave when in fullscreen mode setIsPointerOver(false)
// When player is small, controls should stay visible clearHideTimeout()
setMouseMoving(false) if (videoState.fullscreen) {
if (videoState.playing && videoState.fullscreen) { if (videoState.playing) {
hideControls()
} else {
showControls()
}
} else if (videoState.playing) {
hideControls() 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 // Keyboard shortcuts
useKeyboardShortcuts(keyboardShortcuts) useKeyboardShortcuts(keyboardShortcuts)
@@ -132,12 +188,13 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
const controlsClassName = `controls-layer ${uiState.controlsVisible ? 'visible' : 'hidden'} ${ const controlsClassName = `controls-layer ${uiState.controlsVisible ? 'visible' : 'hidden'} ${
videoState.playing ? 'playing' : 'paused' videoState.playing ? 'playing' : 'paused'
}` } ${videoState.fullscreen ? 'fullscreen' : 'windowed'}`
return ( return (
<div <div
ref={containerRef} ref={containerRef}
className={controlsClassName} className={controlsClassName}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
onClick={handleClick} onClick={handleClick}
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState, useCallback } from 'react'
import { PlayerProvider } from '../contexts/PlayerContext' import { PlayerProvider } from '../contexts/PlayerContext'
import { VideoElement } from './VideoElement' import { VideoElement } from './VideoElement'
import { ControlsLayer } from './ControlsLayer' import { ControlsLayer } from './ControlsLayer'