import { useEffect, useRef, useState } from "react" import { createFileRoute, Link } from "@tanstack/react-router" import { getStreamByUsername, type StreamPageData } from "@/lib/stream/db" import { VideoPlayer } from "@/components/VideoPlayer" import { CloudflareStreamPlayer } from "@/components/CloudflareStreamPlayer" import { WebRTCPlayer } from "@/components/WebRTCPlayer" import { resolveStreamPlayback } from "@/lib/stream/playback" import { JazzProvider } from "@/lib/jazz/provider" import { ViewerCount } from "@/components/ViewerCount" import { CommentBox } from "@/components/CommentBox" import { getSpotifyNowPlaying, type SpotifyNowPlayingResponse, } from "@/lib/spotify/now-playing" import { getStreamStatus } from "@/lib/stream/status" import { authClient } from "@/lib/auth-client" import { MessageCircle, LogIn, X } from "lucide-react" export const Route = createFileRoute("/$username")({ ssr: false, component: StreamPage, }) // Cloudflare Stream HLS URL const HLS_URL = "https://customer-xctsztqzu046isdc.cloudflarestream.com/1b0363e3f8d54ddc639dc85737f8c28a/manifest/video.m3u8" const NIKIV_PLAYBACK = resolveStreamPlayback({ hlsUrl: HLS_URL, webrtcUrl: null }) const READY_PULSE_MS = 1200 // Hardcoded user for nikiv const NIKIV_DATA: StreamPageData = { user: { id: "nikiv", name: "Nikita", username: "nikiv", image: null, }, stream: { id: "nikiv-stream", title: "Live Coding", description: "Building in public", is_live: false, // Set to true when actually streaming viewer_count: 0, hls_url: HLS_URL, webrtc_url: null, playback: NIKIV_PLAYBACK, thumbnail_url: null, started_at: null, }, } // Free preview duration in milliseconds (5 minutes) const FREE_PREVIEW_MS = 5 * 60 * 1000 const STORAGE_KEY = "linsa_stream_watch_time" function getWatchTime(): number { if (typeof window === "undefined") return 0 const stored = localStorage.getItem(STORAGE_KEY) if (!stored) return 0 try { const data = JSON.parse(stored) // Reset if older than 24 hours if (Date.now() - data.startedAt > 24 * 60 * 60 * 1000) { localStorage.removeItem(STORAGE_KEY) return 0 } return data.watchTime || 0 } catch { return 0 } } function saveWatchTime(watchTime: number, startedAt: number) { if (typeof window === "undefined") return localStorage.setItem(STORAGE_KEY, JSON.stringify({ watchTime, startedAt })) } function StreamPage() { const { username } = Route.useParams() const { data: session, isPending: sessionLoading } = authClient.useSession() const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [playerReady, setPlayerReady] = useState(false) const [hlsLive, setHlsLive] = useState(null) const [isConnecting, setIsConnecting] = useState(false) const [nowPlaying, setNowPlaying] = useState( null, ) const [nowPlayingLoading, setNowPlayingLoading] = useState(false) const [nowPlayingError, setNowPlayingError] = useState(false) const [streamLive, setStreamLive] = useState(false) const [showReadyPulse, setShowReadyPulse] = useState(false) const readyPulseTimeoutRef = useRef | null>(null) const hasConnectedOnce = useRef(false) // Free preview tracking const [watchTime, setWatchTime] = useState(0) const [previewExpired, setPreviewExpired] = useState(false) const watchStartRef = useRef(null) // Mobile chat overlay const [showMobileChat, setShowMobileChat] = useState(false) const isAuthenticated = !sessionLoading && !!session?.user // Track watch time for unauthenticated users useEffect(() => { if (isAuthenticated || sessionLoading) return // Initialize from localStorage const savedTime = getWatchTime() setWatchTime(savedTime) if (savedTime >= FREE_PREVIEW_MS) { setPreviewExpired(true) return } watchStartRef.current = Date.now() const startedAt = Date.now() - savedTime const interval = setInterval(() => { const elapsed = Date.now() - (watchStartRef.current || Date.now()) + savedTime setWatchTime(elapsed) saveWatchTime(elapsed, startedAt) if (elapsed >= FREE_PREVIEW_MS) { setPreviewExpired(true) clearInterval(interval) } }, 1000) return () => { clearInterval(interval) if (watchStartRef.current) { const elapsed = Date.now() - watchStartRef.current + savedTime saveWatchTime(elapsed, startedAt) } } }, [isAuthenticated, sessionLoading]) useEffect(() => { let isActive = true const setReadySafe = (ready: boolean) => { if (isActive) { setStreamReady(ready) } } const setDataSafe = (next: StreamPageData | null) => { if (isActive) { setData(next) } } const setLoadingSafe = (next: boolean) => { if (isActive) { setLoading(next) } } const setErrorSafe = (next: string | null) => { if (isActive) { setError(next) } } const setWebRtcFailedSafe = (next: boolean) => { if (isActive) { setWebRtcFailed(next) } } setReadySafe(false) setWebRtcFailedSafe(false) // Special handling for nikiv - hardcoded stream if (username === "nikiv") { setDataSafe(NIKIV_DATA) setLoadingSafe(false) return () => { isActive = false } } const loadData = async () => { setLoadingSafe(true) setErrorSafe(null) try { const result = await getStreamByUsername(username) setDataSafe(result) } catch (err) { setErrorSafe("Failed to load stream") console.error(err) } finally { setLoadingSafe(false) } } loadData() return () => { isActive = false } }, [username]) // Poll stream status for nikiv from nikiv.dev/api/stream-status useEffect(() => { if (username !== "nikiv") { return } let isActive = true const fetchStatus = async () => { const status = await getStreamStatus() console.log("[Stream Status] nikiv.dev/api/stream-status:", status) if (isActive) { setStreamLive(status.isLive) } } // Fetch immediately fetchStatus() // Poll every 10 seconds const interval = setInterval(fetchStatus, 10000) return () => { isActive = false clearInterval(interval) } }, [username]) useEffect(() => { if (readyPulseTimeoutRef.current) { clearTimeout(readyPulseTimeoutRef.current) readyPulseTimeoutRef.current = null } if (!playerReady) { setShowReadyPulse(false) return } setShowReadyPulse(true) readyPulseTimeoutRef.current = setTimeout(() => { setShowReadyPulse(false) readyPulseTimeoutRef.current = null }, READY_PULSE_MS) return () => { if (readyPulseTimeoutRef.current) { clearTimeout(readyPulseTimeoutRef.current) readyPulseTimeoutRef.current = null } } }, [playerReady]) const stream = data?.stream ?? null // For nikiv, always use HLS directly (no WebRTC) const activePlayback = username === "nikiv" ? { type: "hls" as const, url: HLS_URL } : stream?.playback ?? null const isHlsPlaylistLive = (manifest: string) => { const upper = manifest.toUpperCase() const hasEndlist = upper.includes("#EXT-X-ENDLIST") const isVod = upper.includes("#EXT-X-PLAYLIST-TYPE:VOD") const hasSegments = upper.includes("#EXTINF") || upper.includes("#EXT-X-PART") // Also check for #EXTM3U which is the start of any valid HLS manifest const isValidManifest = upper.includes("#EXTM3U") return isValidManifest && !hasEndlist && !isVod && hasSegments } // For nikiv, use server-side API to check HLS (avoids CORS) useEffect(() => { if (username !== "nikiv") return let isActive = true const checkHlsViaApi = async () => { try { const res = await fetch("/api/check-hls", { cache: "no-store" }) if (!isActive) return const data = await res.json() if (data.isLive) { // Stream is live - set connecting state if first time if (!hasConnectedOnce.current) { setIsConnecting(true) } setHlsLive(true) } else { // Only set offline if we haven't connected yet // This prevents flickering when HLS check temporarily fails if (!hasConnectedOnce.current) { setHlsLive(false) } } } catch { // Silently ignore errors - don't change state on network issues } } // Initial check setHlsLive(null) checkHlsViaApi() // Poll every 5 seconds to detect when stream goes live const interval = setInterval(checkHlsViaApi, 5000) return () => { isActive = false clearInterval(interval) } }, [username]) // For non-nikiv users, use direct HLS check useEffect(() => { if (username === "nikiv" || !activePlayback || activePlayback.type !== "hls") { return } let isActive = true const checkHlsLive = async () => { try { const res = await fetch(activePlayback.url, { cache: "no-store", mode: "cors", }) if (!isActive) return if (!res.ok) { if (!hasConnectedOnce.current) { setHlsLive(false) } return } const manifest = await res.text() if (!isActive) return const live = isHlsPlaylistLive(manifest) if (live) { if (!hasConnectedOnce.current) { setIsConnecting(true) } setHlsLive(true) } else if (!hasConnectedOnce.current) { setHlsLive(false) } } catch { // Silently ignore fetch errors } } setHlsLive(null) checkHlsLive() const interval = setInterval(checkHlsLive, 5000) return () => { isActive = false clearInterval(interval) } }, [ username, activePlayback?.type, activePlayback?.type === "hls" ? activePlayback.url : null, ]) useEffect(() => { let isActive = true if (!stream?.hls_url || activePlayback?.type === "hls") { return () => { isActive = false } } setHlsLive(null) fetch(stream.hls_url) .then(async (res) => { if (!isActive) return if (!res.ok) { setHlsLive(false) return } const manifest = await res.text() if (!isActive) return setHlsLive(isHlsPlaylistLive(manifest)) }) .catch(() => { if (isActive) { setHlsLive(false) } }) return () => { isActive = false } }, [activePlayback?.type, stream?.hls_url]) // For nikiv, use HLS live check from our API // For other users, use stream?.is_live from the database const isActuallyLive = username === "nikiv" ? hlsLive === true : Boolean(stream?.is_live) // Only show Spotify when we know stream is offline (not during initial check) const shouldFetchSpotify = username === "nikiv" && !isActuallyLive && hlsLive === false useEffect(() => { if (!shouldFetchSpotify) { setNowPlaying(null) setNowPlayingLoading(false) setNowPlayingError(false) return } let isActive = true const fetchNowPlaying = async (showLoading: boolean) => { if (showLoading) { setNowPlayingLoading(true) } try { const response = await getSpotifyNowPlaying() if (!isActive) return setNowPlaying(response) setNowPlayingError(false) } catch { if (!isActive) return // Silently handle Spotify errors - it's not critical setNowPlayingError(true) } finally { if (isActive && showLoading) { setNowPlayingLoading(false) } } } fetchNowPlaying(true) const interval = setInterval(() => fetchNowPlaying(false), 30000) return () => { isActive = false clearInterval(interval) } }, [shouldFetchSpotify]) // Format remaining time const remainingMs = Math.max(0, FREE_PREVIEW_MS - watchTime) const remainingMin = Math.floor(remainingMs / 60000) const remainingSec = Math.floor((remainingMs % 60000) / 1000) const remainingFormatted = `${remainingMin}:${remainingSec.toString().padStart(2, "0")}` // Auth gate - show preview for 5 min, then require login if (sessionLoading) { return (
Loading...
) } // Show auth wall when preview expires for unauthenticated users if (!isAuthenticated && previewExpired) { return (

Free preview ended

Sign in to continue watching this stream

Sign in to continue
) } if (loading) { return (
Loading...
) } if (error) { return (

Error

{error}

) } if (!data) { return (

User not found

This username doesn't exist or hasn't set up streaming.

) } const nowPlayingTrack = nowPlaying?.track ?? null const nowPlayingArtists = nowPlayingTrack?.artists.length ? nowPlayingTrack.artists.join(", ") : null const nowPlayingText = nowPlayingTrack ? nowPlayingArtists ? `${nowPlayingArtists} — ${nowPlayingTrack.title}` : nowPlayingTrack.title : null // Callback when player is ready const handlePlayerReady = () => { hasConnectedOnce.current = true setIsConnecting(false) setPlayerReady(true) } // Show loading state during initial check const isChecking = hlsLive === null return (
{/* Main content area */}
{/* Free preview countdown banner - hidden on mobile */} {!isAuthenticated && !previewExpired && isActuallyLive && (
Free preview: {remainingFormatted} remaining Sign in for unlimited access
)} {/* Viewer count overlay - hidden on mobile */} {isActuallyLive && (
)} {/* Loading state - checking if stream is live */} {isChecking ? (

Checking stream status...

) : isActuallyLive && activePlayback ? ( /* Stream is live - show the player */
{/* Loading overlay while connecting */} {(isConnecting || !playerReady) && (

Connecting to stream...

)} {/* Ready pulse */} {showReadyPulse && (
🔴
)}
) : ( /* Stream is offline */
{shouldFetchSpotify ? (
Offline

Not live right now

{nowPlayingLoading ? ( Checking Spotify... ) : nowPlaying?.isPlaying && nowPlayingTrack ? ( Currently playing{" "} {nowPlayingTrack.url ? ( {nowPlayingText ?? "Spotify"} ) : ( {nowPlayingText ?? "Spotify"} )} ) : null}
nikiv.dev
) : ( )}
)}
{/* Desktop Chat sidebar */}
{/* Mobile bottom bar */}
{!isAuthenticated && ( Sign In )}
{/* Mobile chat overlay */} {showMobileChat && (
Chat
)}
) }