import React, { useState, useRef, useEffect, useCallback } from 'react'; import { VideoCell } from './components/VideoCell'; import { BorderlessToolbar } from './components/BorderlessToolbar'; import { getYouTubeEmbedUrl } from './utils/youtube'; import { loadYouTubeIframeApi } from './utils/youtubeApiLoader'; const initialVideoUrls = ['', '', '', '']; // Up to 4 videos const App: React.FC = () => { const [videoUrls, setVideoUrls] = useState(initialVideoUrls); const [isBorderlessModeActive, setIsBorderlessModeActive] = useState(false); const [activeAudioVideoId, setActiveAudioVideoId] = useState(null); const [isYtApiReady, setIsYtApiReady] = useState(false); const [appErrorMessage, setAppErrorMessage] = useState(null); const [toolbarHeight, setToolbarHeight] = useState(0); const playerRefs = useRef<{ [key: string]: YT.Player | null }>({}); const toolbarRef = useRef(null); useEffect(() => { loadYouTubeIframeApi() .then(() => { setIsYtApiReady(true); setAppErrorMessage(null); }) .catch(error => { console.error("Failed to load YouTube API:", error); setAppErrorMessage("Error: Could not load YouTube player API. Video playback may not work correctly. Please try refreshing the page."); setIsYtApiReady(false); }); }, []); const handleUrlChange = useCallback((index: number, newUrl: string) => { setVideoUrls(prevUrls => { const updatedUrls = [...prevUrls]; updatedUrls[index] = newUrl; return updatedUrls; }); if (!newUrl && activeAudioVideoId === `video-${index}`) { setActiveAudioVideoId(null); } }, [activeAudioVideoId]); const setPlayerInstance = useCallback((cellId: string, player: YT.Player | null) => { playerRefs.current[cellId] = player; }, []); const handleSelectAudio = useCallback((cellId: string) => { setActiveAudioVideoId(cellId); Object.keys(playerRefs.current).forEach(key => { const player = playerRefs.current[key]; if (player && typeof player.isMuted === 'function') { if (key === cellId) { console.log(`Unmuting player for cell: ${cellId}`); if (player.isMuted()) player.unMute(); player.setVolume(100); // Ensure volume is set to audible level } else { console.log(`Muting player for cell: ${key}`); player.mute(); // Always mute without checking current state } } }); }, []); const toggleBorderlessMode = () => { setIsBorderlessModeActive(prev => { const newMode = !prev; if (!newMode) { // Exiting borderless mode setActiveAudioVideoId(null); Object.values(playerRefs.current).forEach(player => { if (player && typeof player.isMuted === 'function' && !player.isMuted()) { player.mute(); } }); } return newMode; }); }; useEffect(() => { const calculateToolbarHeight = () => { if (isBorderlessModeActive && toolbarRef.current) { setToolbarHeight(toolbarRef.current.offsetHeight); } }; if (isBorderlessModeActive) { document.body.style.overflow = 'hidden'; calculateToolbarHeight(); // Initial calculation window.addEventListener('resize', calculateToolbarHeight); } else { document.body.style.overflow = ''; setToolbarHeight(0); // Reset when exiting } return () => { document.body.style.overflow = ''; window.removeEventListener('resize', calculateToolbarHeight); }; }, [isBorderlessModeActive]); const activeVideoDetails = videoUrls .map((url, index) => ({ url, cellId: `video-${index}`, cellNumber: index + 1, originalIndex: index, // Keep original index isValid: getYouTubeEmbedUrl(url) !== null, })) .filter(video => video.isValid); let gridLayoutClasses = "grid grid-cols-1 md:grid-cols-2 gap-3 sm:gap-4 w-full max-w-7xl"; let mainContainerStyles: React.CSSProperties = {}; if (isBorderlessModeActive) { mainContainerStyles = { position: 'fixed', top: `${toolbarHeight}px`, // Offset by toolbar height left: 0, width: '100vw', height: `calc(100vh - ${toolbarHeight}px)`, // Adjust height zIndex: 40, // Below toolbar (z-50) but above default content background: '#000', }; switch (activeVideoDetails.length) { case 1: gridLayoutClasses = "grid grid-cols-1 grid-rows-1 h-full"; break; case 2: gridLayoutClasses = "grid grid-cols-2 grid-rows-1 h-full"; break; case 3: gridLayoutClasses = "grid grid-cols-3 grid-rows-1 h-full"; break; case 4: default: gridLayoutClasses = "grid grid-cols-2 grid-rows-2 h-full"; break; } if (activeVideoDetails.length === 0) { gridLayoutClasses = "flex items-center justify-center h-full"; } } return (
{!isBorderlessModeActive && (

YouTube Redzone Viewer

Load up to four YouTube videos. Enter Borderless Mode for immersive viewing!

{(!isYtApiReady && activeVideoDetails.length > 0) && (

YouTube Player API is not ready. Borderless mode might not function correctly.

)} {appErrorMessage && (

{appErrorMessage}

)}
)} {isBorderlessModeActive && ( ({ cellId: v.cellId, cellNumber: v.cellNumber }))} onSelectAudio={handleSelectAudio} currentActiveAudioVideoId={activeAudioVideoId} /> )}
{isBorderlessModeActive ? ( activeVideoDetails.length > 0 ? ( activeVideoDetails.map(video => ( handleUrlChange(video.originalIndex, newUrl)} cellNumber={video.cellNumber} isBorderless={true} isAudioActive={activeAudioVideoId === video.cellId} setPlayerInstance={setPlayerInstance} isApiReady={isYtApiReady} /> )) ) : (
No active videos to display in borderless mode. Add some URLs!
) ) : ( videoUrls.map((url, index) => ( handleUrlChange(index, newUrl)} cellNumber={index + 1} isBorderless={false} isAudioActive={activeAudioVideoId === `video-${index}`} setPlayerInstance={setPlayerInstance} isApiReady={isYtApiReady} /> )) )}
{!isBorderlessModeActive && ( )}
); }; export default App;