import { useEffect, useRef } from "react"; import { X } from "lucide-react"; import { type Video } from "@shared/schema"; import { Button } from "@/components/ui/button"; import { apiRequest } from "@/lib/queryClient"; import Hls from "hls.js"; interface VideoModalProps { video: Video | null; isOpen: boolean; onClose: () => void; } function formatDuration(seconds: number): string { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } function formatViews(views: number): string { if (views >= 1000000) { return `${(views / 1000000).toFixed(1)}M views`; } else if (views >= 1000) { return `${(views / 1000).toFixed(1)}K views`; } return `${views} views`; } function formatDate(date: Date | string): string { const now = new Date(); const createdDate = typeof date === 'string' ? new Date(date) : date; if (!createdDate || isNaN(createdDate.getTime())) { return "Unknown"; } const diffTime = Math.abs(now.getTime() - createdDate.getTime()); const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); if (diffDays === 0) return "Today"; if (diffDays === 1) return "1 day ago"; if (diffDays < 7) return `${diffDays} days ago`; if (diffDays < 30) return `${Math.floor(diffDays / 7)} week${Math.floor(diffDays / 7) > 1 ? 's' : ''} ago`; return `${Math.floor(diffDays / 30)} month${Math.floor(diffDays / 30) > 1 ? 's' : ''} ago`; } export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) { const videoRef = useRef(null); const hlsRef = useRef(null); useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) { onClose(); } }; if (isOpen) { document.addEventListener("keydown", handleEscape); document.body.style.overflow = "hidden"; } else { document.body.style.overflow = ""; } return () => { document.removeEventListener("keydown", handleEscape); document.body.style.overflow = ""; }; }, [isOpen, onClose]); // Initialize HLS when video is available useEffect(() => { if (isOpen && video && videoRef.current) { const videoElement = videoRef.current; // Clean up previous HLS instance if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } const videoUrl = video.videoUrl; console.log('Loading video:', videoUrl); // Check if the video URL is HLS (.m3u8) if (videoUrl.includes('.m3u8')) { if (Hls.isSupported()) { // Use HLS.js for browsers that don't support HLS natively const hls = new Hls({ debug: true, enableWorker: false, lowLatencyMode: true, backBufferLength: 90 }); hls.loadSource(videoUrl); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () => { console.log('HLS manifest loaded successfully'); }); hls.on(Hls.Events.ERROR, (event, data) => { console.error('HLS error:', data); if (data.fatal) { switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: console.log('Network error, trying to recover...'); hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: console.log('Media error, trying to recover...'); hls.recoverMediaError(); break; default: console.log('Fatal error, destroying HLS instance...'); hls.destroy(); break; } } }); hlsRef.current = hls; } else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) { // For Safari that supports HLS natively videoElement.src = videoUrl; console.log('Using native HLS support'); } else { console.error('HLS is not supported in this browser'); } } else { // For regular MP4 videos videoElement.src = videoUrl; console.log('Using native video support for MP4'); } } // Cleanup when modal closes return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [isOpen, video]); const handleVideoPlay = async () => { if (video) { try { await apiRequest("POST", `/api/videos/${video.id}/view`); } catch (error) { console.error("Failed to track video view:", error); } } }; const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; if (!isOpen || !video) return null; return (
{video.videoUrl.includes('iframe.mediadelivery.net') ? (