feat: Enhance video playback stability, integrate more menu into control visibility, and improve player UI responsiveness.

This commit is contained in:
kuekhaoyang
2025-12-26 21:12:09 +08:00
parent cb04d276bb
commit ff18e10af5
8 changed files with 70 additions and 27 deletions
+2 -2
View File
@@ -165,8 +165,8 @@
/* Spinner with Glass Background */
.spinner-glass {
width: 56px;
height: 56px;
width: clamp(40px, 8vw, 56px);
height: clamp(40px, 8vw, 56px);
border: 4px solid rgba(255, 255, 255, 0.15);
border-top-color: var(--accent-color);
border-radius: var(--radius-full);
+12 -1
View File
@@ -1,5 +1,6 @@
'use client';
import React from 'react';
import { useDesktopPlayerState } from './hooks/useDesktopPlayerState';
import { useDesktopPlayerLogic } from './hooks/useDesktopPlayerLogic';
import { useHlsPlayer } from './hooks/useHlsPlayer';
@@ -53,6 +54,11 @@ export function DesktopVideoPlayer({
setIsLoading,
} = state;
// Reset loading state and show spinner when source changes
React.useEffect(() => {
setIsLoading(true);
}, [src, setIsLoading]);
const logic = useDesktopPlayerLogic({
src,
initialTime,
@@ -120,12 +126,17 @@ export function DesktopVideoPlayer({
onMoreMenuMouseEnter={() => {
if (refs.moreMenuTimeoutRef.current) {
clearTimeout(refs.moreMenuTimeoutRef.current);
refs.moreMenuTimeoutRef.current = null;
}
}}
onMoreMenuMouseLeave={() => {
if (refs.moreMenuTimeoutRef.current) {
clearTimeout(refs.moreMenuTimeoutRef.current);
}
refs.moreMenuTimeoutRef.current = setTimeout(() => {
state.setShowMoreMenu(false);
}, 300);
refs.moreMenuTimeoutRef.current = null;
}, 800); // Increased timeout for better stability
}}
onCopyLink={logic.handleCopyLink}
// Speed Menu Props
+8 -8
View File
@@ -123,7 +123,7 @@ export function DesktopOverlay({
{/* Previous Button (Method: Skip Backward) */}
<div
className={`absolute left-0 top-0 bottom-0 flex items-center justify-center p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
className={`absolute left-0 top-0 bottom-0 flex items-center justify-center p-4 md:p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
}`}
style={{ pointerEvents: showNavButtons ? 'auto' : 'none' }}
>
@@ -132,16 +132,16 @@ export function DesktopOverlay({
e.stopPropagation();
onSkipBackward();
}}
className="group flex items-center justify-center w-16 h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95"
className="group flex items-center justify-center w-12 h-12 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95"
aria-label="Skip Backward 10s"
>
<Icons.SkipBack size={32} className="text-white/80 group-hover:text-white" />
<Icons.SkipBack className="w-6 h-6 md:w-8 md:h-8 text-white/80 group-hover:text-white" />
</button>
</div>
{/* Next Button (Method: Skip Forward) */}
<div
className={`absolute right-0 top-0 bottom-0 flex items-center justify-center p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
className={`absolute right-0 top-0 bottom-0 flex items-center justify-center p-4 md:p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
}`}
style={{ pointerEvents: showNavButtons ? 'auto' : 'none' }}
>
@@ -150,10 +150,10 @@ export function DesktopOverlay({
e.stopPropagation();
onSkipForward();
}}
className="group flex items-center justify-center w-16 h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95"
className="group flex items-center justify-center w-12 h-12 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95"
aria-label="Skip Forward 10s"
>
<Icons.SkipForward size={32} className="text-white/80 group-hover:text-white" />
<Icons.SkipForward className="w-6 h-6 md:w-8 md:h-8 text-white/80 group-hover:text-white" />
</button>
</div>
@@ -162,10 +162,10 @@ export function DesktopOverlay({
<div className="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
<button
onClick={onTogglePlay}
className="pointer-events-auto w-20 h-20 rounded-full bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] border border-[var(--glass-border)] flex items-center justify-center transition-all duration-300 hover:scale-110 hover:bg-[var(--accent-color)] shadow-[var(--shadow-md)] will-change-transform cursor-pointer"
className="pointer-events-auto w-16 h-16 md:w-20 md:h-20 rounded-full bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] border border-[var(--glass-border)] flex items-center justify-center transition-all duration-300 hover:scale-110 hover:bg-[var(--accent-color)] shadow-[var(--shadow-md)] will-change-transform cursor-pointer"
aria-label="Play"
>
<Icons.Play size={32} className="text-white ml-1" />
<Icons.Play className="w-8 h-8 md:w-10 md:h-10 text-white ml-1" />
</button>
</div>
)}
@@ -4,8 +4,10 @@ interface UseControlsVisibilityProps {
isPlaying: boolean;
showControls: boolean;
showSpeedMenu: boolean;
showMoreMenu: boolean;
setShowControls: (show: boolean) => void;
setShowSpeedMenu: (show: boolean) => void;
setShowMoreMenu: (show: boolean) => void;
controlsTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
speedMenuTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
mouseMoveThrottleRef: React.MutableRefObject<NodeJS.Timeout | null>;
@@ -15,25 +17,46 @@ export function useControlsVisibility({
isPlaying,
showControls,
showSpeedMenu,
showMoreMenu,
setShowControls,
setShowSpeedMenu,
setShowMoreMenu,
controlsTimeoutRef,
speedMenuTimeoutRef,
mouseMoveThrottleRef
}: UseControlsVisibilityProps) {
useEffect(() => {
if (!isPlaying) return;
// Shared hide controls logic
const hideControls = useCallback(() => {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
controlsTimeoutRef.current = setTimeout(() => {
if (isPlaying && !showSpeedMenu && !showMoreMenu) {
setShowControls(false);
}
}, 3000);
}, [isPlaying, showSpeedMenu, showMoreMenu, setShowControls, controlsTimeoutRef]);
const hideControls = () => {
// Force controls to show when paused
useEffect(() => {
if (!isPlaying) {
setShowControls(true);
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
controlsTimeoutRef.current = setTimeout(() => {
if (isPlaying && !showSpeedMenu) {
setShowControls(false);
}
}, 3000);
};
} else {
// When resuming play, start the timer to hide controls
hideControls();
}
}, [isPlaying, setShowControls, hideControls, controlsTimeoutRef]);
useEffect(() => {
if (!isPlaying || showSpeedMenu || showMoreMenu) {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
return;
}
hideControls();
@@ -40,7 +40,15 @@ export function usePlaybackControls({
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
const playPromise = videoRef.current.play();
if (playPromise !== undefined) {
playPromise.catch(error => {
// Ignore AbortError: The play() request was interrupted by a call to pause().
if (error.name !== 'AbortError') {
console.error('Playback failed:', error);
}
});
}
}
}, [isPlaying, videoRef]);
@@ -58,7 +58,8 @@ export function useDesktopPlayerLogic({
setIsSkipForwardAnimatingOut, setIsSkipBackwardAnimatingOut,
setShowVolumeBar, setToastMessage, setShowToast,
isCastAvailable, setIsCastAvailable,
isCasting, setIsCasting
isCasting, setIsCasting,
showMoreMenu, setShowMoreMenu
} = state;
const playbackControls = usePlaybackControls({
@@ -94,8 +95,8 @@ export function useDesktopPlayerLogic({
});
const controlsVisibility = useControlsVisibility({
isPlaying, showControls, showSpeedMenu,
setShowControls, setShowSpeedMenu,
isPlaying, showControls, showSpeedMenu, showMoreMenu,
setShowControls, setShowSpeedMenu, setShowMoreMenu,
controlsTimeoutRef, speedMenuTimeoutRef, mouseMoveThrottleRef
});
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "kvideo",
"version": "3.5.0",
"version": "3.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvideo",
"version": "3.5.0",
"version": "3.6.0",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "3.5.0",
"version": "3.6.0",
"private": true,
"scripts": {
"dev": "next dev",