mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-13 15:53:44 +08:00
feat: Enhance video playback stability, integrate more menu into control visibility, and improve player UI responsiveness.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "3.5.0",
|
||||
"version": "3.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Reference in New Issue
Block a user