import React from 'react'; import { useParams } from 'react-router-dom'; import { Loader2, Maximize2, Minimize2, Pause, Play, WifiOff } from 'lucide-react'; import { AnimatePresence, motion } from 'framer-motion'; import { useLiveShowState } from '../hooks/useLiveShowState'; import { useLiveShowPlayback } from '../hooks/useLiveShowPlayback'; import LiveShowStage from '../components/LiveShowStage'; import LiveShowBackdrop from '../components/LiveShowBackdrop'; import { useTranslation } from '../i18n/useTranslation'; import { prefersReducedMotion } from '../lib/motion'; import { resolveLiveShowEffect } from '../lib/liveShowEffects'; export default function LiveShowPlayerPage() { const { token } = useParams<{ token: string }>(); const { t } = useTranslation(); const { status, connection, error, event, photos, settings } = useLiveShowState(token ?? null); const [paused, setPaused] = React.useState(false); const { frame, layout, frameKey, nextFrame } = useLiveShowPlayback(photos, settings, { paused }); const hasPhoto = frame.length > 0; const stageTitle = event?.name ?? t('liveShowPlayer.title', 'Live Show'); const reducedMotion = prefersReducedMotion(); const effect = resolveLiveShowEffect(settings.effect_preset, settings.effect_intensity, reducedMotion); const showStage = status === 'ready' && hasPhoto; const showEmpty = status === 'ready' && !hasPhoto; const [controlsVisible, setControlsVisible] = React.useState(true); const [isFullscreen, setIsFullscreen] = React.useState(false); const [isOnline, setIsOnline] = React.useState(typeof navigator !== 'undefined' ? navigator.onLine : true); const hideTimerRef = React.useRef(null); const preloadRef = React.useRef>(new Set()); const stageRef = React.useRef(null); React.useEffect(() => { document.body.classList.add('guest-immersive'); return () => { document.body.classList.remove('guest-immersive'); }; }, []); React.useEffect(() => { const updateOnline = () => setIsOnline(navigator.onLine); window.addEventListener('online', updateOnline); window.addEventListener('offline', updateOnline); return () => { window.removeEventListener('online', updateOnline); window.removeEventListener('offline', updateOnline); }; }, []); React.useEffect(() => { const handleFullscreen = () => setIsFullscreen(Boolean(document.fullscreenElement)); document.addEventListener('fullscreenchange', handleFullscreen); handleFullscreen(); return () => document.removeEventListener('fullscreenchange', handleFullscreen); }, []); const revealControls = React.useCallback(() => { setControlsVisible(true); if (hideTimerRef.current) { window.clearTimeout(hideTimerRef.current); } hideTimerRef.current = window.setTimeout(() => { setControlsVisible(false); }, 3000); }, []); React.useEffect(() => { if (!showStage) { setControlsVisible(true); return; } revealControls(); }, [revealControls, showStage, frameKey]); const togglePause = React.useCallback(() => { setPaused((prev) => !prev); }, []); const toggleFullscreen = React.useCallback(async () => { const target = stageRef.current ?? document.documentElement; try { if (!document.fullscreenElement) { await target.requestFullscreen?.(); } else { await document.exitFullscreen?.(); } } catch (err) { console.warn('Fullscreen toggle failed', err); } }, []); React.useEffect(() => { const handleKey = (event: KeyboardEvent) => { if (event.target && (event.target as HTMLElement).closest('input, textarea, select, button')) { return; } if (event.code === 'Space') { event.preventDefault(); togglePause(); revealControls(); } if (event.key.toLowerCase() === 'f') { event.preventDefault(); toggleFullscreen(); revealControls(); } if (event.key === 'Escape' && document.fullscreenElement) { event.preventDefault(); document.exitFullscreen?.(); } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [revealControls, toggleFullscreen, togglePause]); React.useEffect(() => { const candidates = [...frame, ...nextFrame].slice(0, 6); candidates.forEach((photo) => { const src = photo.full_url || photo.thumb_url; if (!src || preloadRef.current.has(src)) { return; } const img = new Image(); img.src = src; preloadRef.current.add(src); }); }, [frame, nextFrame]); return (
{stageTitle} {connection === 'sse' ? t('liveShowPlayer.connection.live', 'Live') : t('liveShowPlayer.connection.sync', 'Sync')}
{status === 'loading' && (

{t('liveShowPlayer.loading', 'Live Show wird geladen...')}

)} {status === 'error' && (

{t('liveShowPlayer.error.title', 'Live Show nicht erreichbar')}

{error ?? t('liveShowPlayer.error.description', 'Bitte überprüfe den Live-Link.')}

)} {showStage && ( )} {showStage && effect.flash && ( )} {controlsVisible && ( {!isOnline && ( {t('liveShowPlayer.controls.offline', 'Offline')} )} )} {paused && showStage && (
{t('liveShowPlayer.controls.paused', 'Paused')}
)} {showEmpty && (

{t('liveShowPlayer.empty.title', 'Noch keine Live-Fotos')}

{t('liveShowPlayer.empty.description', 'Warte auf die ersten Uploads...')}

)}
); }