diff --git a/client/src/components/hls-preview-thumbnail.tsx b/client/src/components/hls-preview-thumbnail.tsx index 6798471..9d946e7 100644 --- a/client/src/components/hls-preview-thumbnail.tsx +++ b/client/src/components/hls-preview-thumbnail.tsx @@ -20,64 +20,64 @@ export default function HLSPreviewThumbnail({ video, onClick, className = "" }: const hoverTimeoutRef = useRef(); const previewIntervalRef = useRef(); - // Initialize HLS for preview - useEffect(() => { + // Initialize HLS for preview only when hovering + const initializeVideoOnDemand = () => { if (!video.videoUrl.includes('.m3u8') && !video.videoUrlMp4) return; const videoElement = videoRef.current; - if (!videoElement) return; + if (!videoElement || isVideoLoaded || hlsInstance) return; let hls: Hls | null = null; - const initializeVideo = () => { - // Use MP4 if available for better seek performance - if (video.videoUrlMp4) { - videoElement.src = video.videoUrlMp4; - videoElement.muted = true; - videoElement.playsInline = true; - videoElement.preload = 'metadata'; - return; - } + // Use MP4 if available for better seek performance + if (video.videoUrlMp4) { + videoElement.src = video.videoUrlMp4; + videoElement.muted = true; + videoElement.playsInline = true; + videoElement.preload = 'metadata'; + + const handleLoadedMetadata = () => { + setIsVideoLoaded(true); + videoElement.removeEventListener('loadedmetadata', handleLoadedMetadata); + }; + videoElement.addEventListener('loadedmetadata', handleLoadedMetadata); + return; + } - // Use HLS with reduced quality for preview - if (Hls.isSupported() && video.videoUrl.includes('.m3u8')) { - hls = new Hls({ - startLevel: 0, // Start with lowest quality for faster loading - capLevelToPlayerSize: true, - maxLoadingDelay: 1, - maxBufferLength: 5, // Minimal buffering for previews - }); + // Use HLS with reduced quality for preview + if (Hls.isSupported() && video.videoUrl.includes('.m3u8')) { + hls = new Hls({ + startLevel: 0, // Start with lowest quality for faster loading + capLevelToPlayerSize: true, + maxLoadingDelay: 2, + maxBufferLength: 10, // Minimal buffering for previews + lowLatencyMode: true, + }); - hls.loadSource(video.videoUrl); - hls.attachMedia(videoElement); - - hls.on(Hls.Events.MANIFEST_PARSED, () => { - setIsVideoLoaded(true); - }); + hls.loadSource(video.videoUrl); + hls.attachMedia(videoElement); + + hls.on(Hls.Events.MANIFEST_PARSED, () => { + setIsVideoLoaded(true); + }); - hls.on(Hls.Events.ERROR, (event, data) => { - console.warn('HLS preview error:', data); - setIsVideoLoaded(false); - }); + hls.on(Hls.Events.ERROR, (event, data) => { + console.warn('HLS preview error:', data); + setIsVideoLoaded(false); + }); - setHlsInstance(hls); - } - }; - - const handleLoadedMetadata = () => { - setIsVideoLoaded(true); - }; - - videoElement.addEventListener('loadedmetadata', handleLoadedMetadata); - initializeVideo(); + setHlsInstance(hls); + } + }; + // Clean up on unmount + useEffect(() => { return () => { - videoElement.removeEventListener('loadedmetadata', handleLoadedMetadata); - if (hls) { - hls.destroy(); + if (hlsInstance) { + hlsInstance.destroy(); } }; - }, [video.videoUrl, video.videoUrlMp4]); + }, [hlsInstance]); const generatePreviewThumbnail = (time: number) => { const videoElement = videoRef.current; @@ -131,13 +131,18 @@ export default function HLSPreviewThumbnail({ video, onClick, className = "" }: }; const handleMouseEnter = () => { - if (!isVideoLoaded) return; + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } - // Delay before showing preview to avoid flickering hoverTimeoutRef.current = setTimeout(() => { setIsHovering(true); - generatePreviewThumbnail(video.duration * 0.25); // Start at 25% of video - }, 600); + // Initialize video only when user actually hovers + initializeVideoOnDemand(); + if (isVideoLoaded) { + generatePreviewThumbnail(video.duration * 0.25); // Start at 25% of video + } + }, 300); // Reduced delay for better UX }; const handleMouseLeave = () => { diff --git a/client/src/components/video-card.tsx b/client/src/components/video-card.tsx index 7be769e..b0e6306 100644 --- a/client/src/components/video-card.tsx +++ b/client/src/components/video-card.tsx @@ -42,12 +42,41 @@ function formatDate(date: Date | string): string { export default function VideoCard({ video, onClick }: VideoCardProps) { return ( -
- +
+ {/* Simple thumbnail with fallback - no HLS loading until needed */} +
onClick(video)} + > + {video.title} { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + if (target.parentElement) { + target.parentElement.style.background = 'linear-gradient(45deg, #374151, #4b5563)'; + } + }} + /> + + {/* Duration badge */} +
+ {formatDuration(video.duration)} +
+ + {/* Play button overlay */} +
+
+ +
+
+

{ if (videosResponse) { - console.log('Videos response:', videosResponse); if (offset === 0) { setAllVideos(videosResponse.videos); } else {