Merge pull request #1 from MertUyanik/codex/fix-player-control-visibility-issues
Fix control visibility and stabilize HLS loading
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (videoState.playing) {
|
if (!videoState.playing || isMenuOpen) {
|
||||||
// Show controls on mouse movement
|
clearHideTimeout()
|
||||||
if (mouseMoving) {
|
showControls()
|
||||||
|
}
|
||||||
|
}, [videoState.playing, isMenuOpen, showControls, clearHideTimeout])
|
||||||
|
|
||||||
|
// Manage controls visibility when leaving fullscreen
|
||||||
|
useEffect(() => {
|
||||||
|
if (!videoState.fullscreen) {
|
||||||
|
clearHideTimeout()
|
||||||
|
if (!videoState.playing || isPointerOver) {
|
||||||
showControls()
|
showControls()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, [videoState.fullscreen, videoState.playing, isPointerOver, showControls, clearHideTimeout])
|
||||||
|
|
||||||
// Clear existing timeout
|
// Re-schedule auto hide when interaction changes
|
||||||
if (hideTimeoutRef.current) {
|
useEffect(() => {
|
||||||
window.clearTimeout(hideTimeoutRef.current)
|
if (autoHideEnabled && lastInteraction > 0) {
|
||||||
}
|
scheduleHide()
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,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'
|
||||||
|
|||||||
Reference in New Issue
Block a user