import React, { useState, useEffect } from "react"; import DynamicPageContent from "../components/Layout/DynamicPageContent"; import Button from "../components/Input/Button"; import Input from "../components/Input/Input"; import ListItem from "../components/Layout/ListItem"; import { X as XIcon, Eye as ShowIcon, EyeOff as HideIcon } from "lucide-react"; import { useAuth } from "../context/AuthContext"; import { debounce } from "lodash"; import VideoPlayer from "../components/Stream/VideoPlayer"; interface StreamData { title: string; category_name: string; viewer_count: number; start_time: string; stream_key: string; } interface Category { category_id: number; category_name: string; } const StreamDashboardPage: React.FC = () => { const { username } = useAuth(); const [isStreaming, setIsStreaming] = useState(false); const [streamData, setStreamData] = useState({ title: "", category_name: "", viewer_count: 0, start_time: "", stream_key: "", }); const [streamDetected, setStreamDetected] = useState(false); const [timeStarted, setTimeStarted] = useState(""); const [categories, setCategories] = useState([]); const [isCategoryFocused, setIsCategoryFocused] = useState(false); const [filteredCategories, setFilteredCategories] = useState([]); const [thumbnail, setThumbnail] = useState(null); const [thumbnailPreview, setThumbnailPreview] = useState<{ url: string; isCustom: boolean; }>({ url: "", isCustom: false }); const [debouncedCheck, setDebouncedCheck] = useState(null); const [showKey, setShowKey] = useState(false); useEffect(() => { const categoryCheck = debounce((categoryName: string) => { const isValidCategory = categories.some( (cat) => cat.category_name.toLowerCase() === categoryName.toLowerCase() ); if (isValidCategory && !thumbnailPreview.isCustom) { const defaultThumbnail = `/images/thumbnails/categories/${categoryName .toLowerCase() .replace(/ /g, "_")}.webp`; setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); } }, 300); setDebouncedCheck(() => categoryCheck); return () => { categoryCheck.cancel(); }; }, [categories, thumbnailPreview.isCustom]); useEffect(() => { checkStreamStatus(); fetchCategories(); }, [username]); const checkStreamStatus = async () => { try { const response = await fetch(`/api/user/${username}/status`); const data = await response.json(); setIsStreaming(data.is_live); if (data.is_live) { const streamResponse = await fetch( `/api/streams/${data.user_id}/data`, { credentials: "include" } ); const streamData = await streamResponse.json(); setStreamData({ title: streamData.title, category_name: streamData.category_name, viewer_count: streamData.num_viewers, start_time: streamData.start_time, stream_key: streamData.stream_key, }); console.log("Stream data:", streamData); const time = Math.floor( (Date.now() - new Date(streamData.start_time).getTime()) / 60000 // Convert to minutes ); if (time < 60) setTimeStarted(`${time}m ago`); else if (time < 1440) setTimeStarted(`${Math.floor(time / 60)}h ${time % 60}m ago`); else setTimeStarted( `${Math.floor(time / 1440)}d ${Math.floor((time % 1440) / 60)}h ${ time % 60 }m ago` ); } else { // Just need the stream key if not streaming const response = await fetch(`/api/user/${username}/stream_key`); const keyData = await response.json(); setStreamData((prev) => ({ ...prev, stream_key: keyData.stream_key, })); } } catch (error) { console.error("Error checking stream status:", error); } }; const fetchCategories = async () => { try { const response = await fetch("/api/categories/popular/100"); const data = await response.json(); setCategories(data); setFilteredCategories(data); } catch (error) { console.error("Error fetching categories:", error); } }; const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setStreamData((prev) => ({ ...prev, [name]: value })); if (name === "category_name") { const filtered = categories.filter((cat) => cat.category_name.toLowerCase().includes(value.toLowerCase()) ); setFilteredCategories(filtered); if (debouncedCheck) { debouncedCheck(value); } } }; const handleCategorySelect = (categoryName: string) => { console.log("Selected category:", categoryName); setStreamData((prev) => ({ ...prev, category_name: categoryName })); setFilteredCategories([]); if (debouncedCheck) { debouncedCheck(categoryName); } }; const handleThumbnailChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; setThumbnail(file); setThumbnailPreview({ url: URL.createObjectURL(file), isCustom: true, }); } else { setThumbnail(null); if (streamData.category_name && debouncedCheck) { debouncedCheck(streamData.category_name); } else { setThumbnailPreview({ url: "", isCustom: false }); } } }; const clearThumbnail = () => { setThumbnail(null); if (streamData.category_name) { console.log( "Clearing thumbnail as category is set and default category thumbnail will be used" ); const defaultThumbnail = `/images/thumbnails/categories/${streamData.category_name .toLowerCase() .replace(/ /g, "_")}.webp`; setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); } else { setThumbnailPreview({ url: "", isCustom: false }); } }; const isFormValid = () => { return ( streamData.title.trim() !== "" && streamData.category_name.trim() !== "" && categories.some( (cat) => cat.category_name.toLowerCase() === streamData.category_name.toLowerCase() ) && streamDetected ); }; const handlePublishStream = async () => { console.log("Starting stream with data:", streamData); const formData = new FormData(); formData.append("data", JSON.stringify(streamData)); try { const response = await fetch("/api/publish_stream", { method: "POST", body: formData, }); if (response.ok) { console.log("Stream published successfully"); window.location.reload(); } else if (response.status === 403) { console.error("Unauthorized - Invalid stream key or already streaming"); } else { console.error("Failed to publish stream"); } } catch (error) { console.error("Error publishing stream:", error); } }; const handleUpdateStream = async () => { console.log("Updating stream with data:", streamData); const formData = new FormData(); formData.append("key", streamData.stream_key); formData.append("title", streamData.title); formData.append("category_name", streamData.category_name); if (thumbnail) { formData.append("thumbnail", thumbnail); } try { const response = await fetch("/api/update_stream", { method: "POST", body: formData, }); if (response.ok) { console.log("Stream updated successfully"); window.location.reload(); } else { console.error("Failed to update stream"); } } catch (error) { console.error("Error updating stream:", error); } }; const handleEndStream = async () => { console.log("Ending stream..."); const formData = new FormData(); formData.append("key", streamData.stream_key); try { const response = await fetch("/api/end_stream", { method: "POST", body: formData, }); if (response.ok) { console.log("Stream ended successfully"); window.location.reload(); } else { console.error("Failed to end stream"); } } catch (error) { console.error("Error ending stream:", error); } }; return (

{isStreaming ? "Stream Dashboard" : "Start Streaming"}

{/* Left side - Stream Settings */}

Stream Settings

setIsCategoryFocused(true)} onBlur={() => setTimeout(() => setIsCategoryFocused(false), 200) } placeholder="Select or type a category" extraClasses="w-[70%] focus:w-[70%]" maxLength={50} autoComplete="off" type="search" /> {isCategoryFocused && filteredCategories.length > 0 && (
{filteredCategories.map((category) => (
handleCategorySelect(category.category_name) } > {category.category_name}
))}
)}
{thumbnail ? thumbnail.name : "No file selected"} {thumbnailPreview.isCustom && ( )}
{!thumbnailPreview.isCustom && (

No thumbnail selected - the default category image will be used

)}
{isStreaming && (

Stream Info

Viewers: {streamData.viewer_count}

Started:{" "} {new Date(streamData.start_time!).toLocaleTimeString()} {` (${timeStarted})`}

)}
{isStreaming && ( )}
{!streamDetected && (

No stream input detected. Please start streaming using your broadcast software.

)}
{/* Right side - Preview */}

Stream Preview

Video

List Item

{}} extraClasses="max-w-[20vw]" />
); }; export default StreamDashboardPage;