'use client'; import { useState, useEffect, useRef } from 'react'; import { IoHeart, IoHeartOutline, IoChatbubbleOutline, IoShareOutline, IoEllipsisHorizontal, IoMusicalNote, IoRefresh, IoPlay, IoVolumeMute, IoVolumeHigh } from 'react-icons/io5'; declare global { interface Window { Hls: any; } } interface ShortVideo { id: string; title: string; uploader: string; thumbnail: string; view_count: number; duration?: string; } interface StreamInfo { stream_url: string; error?: string; } const SHORTS_QUERIES = ['#shorts', 'youtube shorts viral', 'tiktok short', 'shorts funny', 'shorts music']; const RANDOM_MODIFIERS = ['viral', 'popular', 'new', 'best', 'trending', 'hot', 'fresh', '2025']; function getRandomModifier(): string { return RANDOM_MODIFIERS[Math.floor(Math.random() * RANDOM_MODIFIERS.length)]; } function parseDuration(duration: string): number { if (!duration) return 0; const parts = duration.split(':').map(Number); if (parts.length === 2) return parts[0] * 60 + parts[1]; if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; return 0; } function formatViews(views: number): string { if (views >= 1000000) return (views / 1000000).toFixed(1) + 'M'; if (views >= 1000) return (views / 1000).toFixed(1) + 'K'; return views.toString(); } async function fetchShorts(page: number): Promise { try { const query = SHORTS_QUERIES[page % SHORTS_QUERIES.length] + ' ' + getRandomModifier(); const res = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=20`, { cache: 'no-store' }); if (!res.ok) return []; const data = await res.json(); return data.filter((v: ShortVideo) => parseDuration(v.duration || '') <= 90); } catch { return []; } } function ShortCard({ video, isActive }: { video: ShortVideo; isActive: boolean }) { const [liked, setLiked] = useState(false); const [likeCount, setLikeCount] = useState(Math.floor(Math.random() * 50000) + 1000); const [commentCount] = useState(Math.floor(Math.random() * 1000) + 50); const [muted, setMuted] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [useFallback, setUseFallback] = useState(false); const videoRef = useRef(null); const hlsRef = useRef(null); const [showControls, setShowControls] = useState(false); useEffect(() => { if (!isActive) { if (videoRef.current) { videoRef.current.pause(); } if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } return; } if (useFallback) return; const loadStream = async () => { setLoading(true); setError(false); try { const res = await fetch(`/api/get_stream_info?v=${video.id}`); const data: StreamInfo = await res.json(); if (data.error || !data.stream_url) { throw new Error(data.error || 'No stream URL'); } const videoEl = videoRef.current; if (!videoEl) return; const streamUrl = data.stream_url; const isHLS = streamUrl.includes('.m3u8') || streamUrl.includes('manifest'); if (isHLS && window.Hls && window.Hls.isSupported()) { if (hlsRef.current) { hlsRef.current.destroy(); } const hls = new window.Hls({ xhrSetup: (xhr: XMLHttpRequest) => { xhr.setRequestHeader('Referer', 'https://www.youtube.com/'); }, }); hlsRef.current = hls; hls.loadSource(streamUrl); hls.attachMedia(videoEl); hls.on(window.Hls.Events.MANIFEST_PARSED, () => { setLoading(false); videoEl.muted = muted; videoEl.play().catch(() => {}); }); hls.on(window.Hls.Events.ERROR, () => { setError(true); setUseFallback(true); }); } else if (videoEl.canPlayType('application/vnd.apple.mpegurl')) { videoEl.src = streamUrl; videoEl.muted = muted; videoEl.addEventListener('loadedmetadata', () => { setLoading(false); videoEl.play().catch(() => {}); }, { once: true }); } else { videoEl.src = streamUrl; videoEl.muted = muted; videoEl.addEventListener('loadeddata', () => { setLoading(false); videoEl.play().catch(() => {}); }, { once: true }); } } catch (err) { console.error('Stream load error:', err); setError(true); setUseFallback(true); } }; const timeout = setTimeout(() => { if (window.Hls) { loadStream(); } else { const checkHls = setInterval(() => { if (window.Hls) { clearInterval(checkHls); loadStream(); } }, 100); setTimeout(() => { clearInterval(checkHls); if (!window.Hls) { setUseFallback(true); } }, 3000); } }, 100); return () => { clearTimeout(timeout); }; }, [isActive, video.id, useFallback, muted]); const toggleMute = () => { if (videoRef.current) { videoRef.current.muted = !videoRef.current.muted; setMuted(videoRef.current.muted); } }; const handleShare = async () => { try { if (navigator.share) { await navigator.share({ title: video.title, url: `${window.location.origin}/watch?v=${video.id}`, }); } else { await navigator.clipboard.writeText(`${window.location.origin}/watch?v=${video.id}`); } } catch {} }; const handleRetry = () => { setUseFallback(false); setError(false); setLoading(false); }; return (
setShowControls(true)} onMouseLeave={() => setShowControls(false)} >
{useFallback ? (