Files
YoutubeRedzone/components/VideoCell.tsx
2025-06-14 18:25:25 -04:00

201 lines
6.7 KiB
TypeScript

import React, { useState, useEffect, useMemo, useRef } from 'react';
import { getYouTubeEmbedUrl } from '../utils/youtube';
interface VideoCellProps {
cellId: string;
initialUrl: string;
onUrlChange: (newUrl: string) => void;
cellNumber: number;
isBorderless: boolean;
isAudioActive: boolean;
setPlayerInstance: (cellId: string, player: YT.Player | null) => void;
isApiReady: boolean;
}
export const VideoCell: React.FC<VideoCellProps> = ({
cellId,
initialUrl,
onUrlChange,
cellNumber,
isBorderless,
isAudioActive,
setPlayerInstance,
isApiReady,
}) => {
const [inputValue, setInputValue] = useState<string>(initialUrl);
const playerRef = useRef<YT.Player | null>(null);
const iframeContainerRef = useRef<HTMLDivElement>(null);
const embedUrl = useMemo(() => getYouTubeEmbedUrl(initialUrl), [initialUrl]);
useEffect(() => {
if (initialUrl !== inputValue) {
setInputValue(initialUrl);
}
}, [initialUrl]);
const handleSubmit = () => {
if (inputValue !== initialUrl) {
if (playerRef.current) {
playerRef.current.destroy();
playerRef.current = null;
setPlayerInstance(cellId, null);
}
onUrlChange(inputValue);
}
};
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
handleSubmit();
event.currentTarget.blur();
}
};
useEffect(() => {
if (!isApiReady || !embedUrl || !window.YT || !iframeContainerRef.current) {
if (playerRef.current) {
playerRef.current.destroy();
playerRef.current = null;
setPlayerInstance(cellId, null);
}
return;
}
const videoId = embedUrl.split('/embed/')[1]?.split('?')[0];
if (!videoId) return;
if (playerRef.current) {
playerRef.current.destroy();
playerRef.current = null;
}
const playerElementId = `ytplayer-${cellId}`;
let playerHostDiv = document.getElementById(playerElementId);
if (!playerHostDiv && iframeContainerRef.current) {
playerHostDiv = document.createElement('div');
playerHostDiv.id = playerElementId;
iframeContainerRef.current.innerHTML = '';
iframeContainerRef.current.appendChild(playerHostDiv);
}
if (!playerHostDiv) return;
const player = new window.YT.Player(playerElementId, {
height: '100%', // Player fills its container (playerHostDiv which is appended to iframeContainerRef)
width: '100%', // Player fills its container
videoId: videoId,
playerVars: {
autoplay: 1,
mute: 1,
controls: isBorderless ? 0 : 1,
playsinline: 1,
modestbranding: 1,
rel: 0,
iv_load_policy: 3,
origin: window.location.origin,
},
events: {
onReady: (event) => {
playerRef.current = event.target;
setPlayerInstance(cellId, event.target);
if (isAudioActive) {
event.target.unMute();
} else {
event.target.mute();
}
},
onError: (event) => {
console.error(`YouTube Player Error for ${cellId}:`, event.data);
}
},
});
return () => {
if (playerRef.current) {
// Check if destroy method exists before calling, good practice with external APIs
if (typeof playerRef.current.destroy === 'function') {
playerRef.current.destroy();
}
playerRef.current = null;
setPlayerInstance(cellId, null);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isApiReady, embedUrl, cellId, setPlayerInstance, isBorderless]); // isAudioActive removed as onReady handles initial mute based on it. Mute control is separate effect.
useEffect(() => {
if (playerRef.current && typeof playerRef.current.isMuted === 'function') {
if (isAudioActive) {
if (playerRef.current.isMuted()) {
playerRef.current.unMute();
}
} else {
if (!playerRef.current.isMuted()) {
playerRef.current.mute();
}
}
}
}, [isAudioActive, cellId]);
return (
<div
className={`flex flex-col bg-gray-800 ${isBorderless ? 'p-0 border-0 rounded-none' : 'p-2 sm:p-3 rounded-lg shadow-xl border border-gray-700 space-y-2'}`}
role="region"
aria-labelledby={`video-cell-title-${cellId}`}
>
{!isBorderless && (
<>
<h2 id={`video-cell-title-${cellId}`} className="sr-only">Video Cell {cellNumber}</h2>
<label htmlFor={`url-input-${cellId}`} className="sr-only">YouTube URL for Video {cellNumber}</label>
<div className="flex space-x-2 items-center">
<span
className="flex-shrink-0 w-8 h-8 sm:w-10 sm:h-10 rounded-full bg-red-600 flex items-center justify-center text-white font-bold text-sm sm:text-base"
aria-hidden="true"
>
{cellNumber}
</span>
<input
id={`url-input-${cellId}`}
type="url"
value={inputValue}
onChange={handleInputChange}
onBlur={handleSubmit}
onKeyDown={handleKeyDown}
placeholder="Paste YouTube URL & press Enter"
className="flex-grow p-2 sm:p-2.5 rounded bg-gray-700 text-white border border-gray-600 focus:ring-2 focus:ring-red-500 focus:border-red-500 outline-none placeholder-gray-400 text-sm sm:text-base"
/>
</div>
</>
)}
<div
ref={iframeContainerRef}
className={`bg-black ${isBorderless ? 'w-full h-full rounded-none' : 'aspect-video rounded overflow-hidden border border-gray-700 shadow-inner'}`}
>
{(!isApiReady && embedUrl) && <p className="text-white p-4 text-center">YouTube API loading...</p>}
{!embedUrl && !isBorderless && (
<div className="w-full h-full flex items-center justify-center text-gray-500 text-xs sm:text-sm p-4 text-center">
Paste a YouTube URL above and press Enter or click away.
</div>
)}
{embedUrl && !isApiReady && isBorderless && (
<div className="w-full h-full flex items-center justify-center text-gray-900 text-xs sm:text-sm p-4 text-center bg-gray-600">
Loading video...
</div>
)}
{(initialUrl && !embedUrl && !isBorderless) && (
<div className="w-full h-full flex items-center justify-center text-red-400 text-xs sm:text-sm p-4 text-center">
Invalid or unsupported YouTube URL.
</div>
)}
</div>
</div>
);
};