Joffrey Thomas
Multiple sources
6709b20
Raw
History Blame Contribute Delete
5.05 kB
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import LoadingScreen from "./components/LoadingScreen";
import CaptioningView from "./components/CaptioningView";
import WelcomeScreen from "./components/WelcomeScreen";
import SourceSelector from "./components/SourceSelector";
import type { AppState, VideoSource } from "./types";
export default function App() {
const [appState, setAppState] = useState<AppState>("source-selection");
const [videoSource, setVideoSource] = useState<VideoSource | null>(null);
const [isVideoReady, setIsVideoReady] = useState(false);
const videoRef = useRef<HTMLVideoElement | null>(null);
const handleSourceSelected = useCallback((source: VideoSource) => {
setVideoSource(source);
setAppState("welcome");
}, []);
const handleBackToSourceSelection = useCallback(() => {
// Clean up current source
if (videoSource?.stream) {
videoSource.stream.getTracks().forEach(track => track.stop());
}
if (videoSource?.url && videoSource.type === "upload") {
URL.revokeObjectURL(videoSource.url);
}
setVideoSource(null);
setIsVideoReady(false);
setAppState("source-selection");
}, [videoSource]);
const handleStart = useCallback(() => {
setAppState("loading");
}, []);
const handleLoadingComplete = useCallback(() => {
setAppState("captioning");
}, []);
const playVideo = useCallback(async (video: HTMLVideoElement) => {
try {
await video.play();
} catch (error) {
console.error("Failed to play video:", error);
}
}, []);
const setupVideo = useCallback(
(video: HTMLVideoElement, source: VideoSource) => {
// Reset video element
video.srcObject = null;
video.src = "";
video.load();
if (source.stream) {
// Stream-based sources (webcam, screen share)
video.srcObject = source.stream;
} else if (source.url) {
// URL-based sources (upload, examples)
video.src = source.url;
video.loop = true; // Loop for uploaded/example videos
}
const handleCanPlay = () => {
setIsVideoReady(true);
playVideo(video);
};
video.addEventListener("canplay", handleCanPlay, { once: true });
// Handle stream ended (e.g., user stops screen sharing)
if (source.stream) {
const track = source.stream.getVideoTracks()[0];
if (track) {
track.onended = () => {
handleBackToSourceSelection();
};
}
}
return () => {
video.removeEventListener("canplay", handleCanPlay);
};
},
[playVideo, handleBackToSourceSelection],
);
useEffect(() => {
if (videoSource && videoRef.current) {
const video = videoRef.current;
const cleanup = setupVideo(video, videoSource);
return cleanup;
}
}, [videoSource, setupVideo]);
const videoBlurState = useMemo(() => {
switch (appState) {
case "source-selection":
return "blur(20px) brightness(0.2) saturate(0.5)";
case "welcome":
return "blur(12px) brightness(0.3) saturate(0.7)";
case "loading":
return "blur(8px) brightness(0.4) saturate(0.8)";
case "captioning":
return "none";
default:
return "blur(20px) brightness(0.2) saturate(0.5)";
}
}, [appState]);
const getSourceLabel = () => {
if (!videoSource) return null;
switch (videoSource.type) {
case "webcam":
return "Webcam";
case "screen":
return "Screen Share";
case "upload":
return videoSource.name || "Uploaded Video";
case "example":
return videoSource.name || "Example Video";
}
};
return (
<div className="App relative h-screen overflow-hidden bg-[#FFFAEB]">
<div className="absolute inset-0 bg-[#FFFAEB]" />
{videoSource && (
<video
ref={videoRef}
autoPlay
muted
playsInline
className="absolute inset-0 w-full h-full object-cover transition-all duration-1000 ease-out"
style={{
filter: videoBlurState,
opacity: isVideoReady ? 1 : 0,
}}
/>
)}
{appState !== "captioning" && appState !== "source-selection" && (
<div className="absolute inset-0 bg-[#FFFAEB]/90 backdrop-blur-sm" />
)}
{appState === "source-selection" && (
<SourceSelector onSourceSelected={handleSourceSelected} />
)}
{appState === "welcome" && (
<WelcomeScreen
onStart={handleStart}
onBack={handleBackToSourceSelection}
sourceLabel={getSourceLabel() ?? undefined}
/>
)}
{appState === "loading" && (
<LoadingScreen onComplete={handleLoadingComplete} />
)}
{appState === "captioning" && (
<CaptioningView
videoRef={videoRef}
videoSource={videoSource}
onChangeSource={handleBackToSourceSelection}
/>
)}
</div>
);
}