fastvlm-webgpu-video / src /components /VideoUploadScreen.tsx
Lokis's picture
Update src/components/VideoUploadScreen.tsx
40ff32c verified
import { useState, useRef, useCallback } from "react";
import GlassContainer from "./GlassContainer";
import GlassButton from "./GlassButton";
import { GLASS_EFFECTS } from "../constants";
import type { VideoUploadState } from "../types";
interface VideoUploadScreenProps {
onVideoReady: (videoState: VideoUploadState) => void;
onBack: () => void;
}
export default function VideoUploadScreen({ onVideoReady, onBack }: VideoUploadScreenProps) {
const [dragActive, setDragActive] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFiles = useCallback(async (files: FileList | null) => {
if (!files || files.length === 0) return;
const file = files[0];
// Validate file type
if (!file.type.startsWith('video/')) {
setUploadError('Please select a valid video file.');
return;
}
// Validate file size (100MB limit)
if (file.size > 1024 * 1024 * 1024) {
setUploadError('Video file is too large. Please select a file under 1GB.');
return;
}
setIsProcessing(true);
setUploadError(null);
try {
// Create video element
const videoElement = document.createElement('video');
videoElement.muted = true;
videoElement.controls = false;
videoElement.preload = 'metadata';
// Create object URL for the video
const videoUrl = URL.createObjectURL(file);
videoElement.src = videoUrl;
// Wait for video metadata to load
await new Promise<void>((resolve, reject) => {
videoElement.onloadedmetadata = () => resolve();
videoElement.onerror = () => reject(new Error('Failed to load video'));
videoElement.load();
});
// Validate video duration (max 10 minutes)
if (videoElement.duration > 600) {
setUploadError('Video is too long. Please select a video under 10 minutes.');
URL.revokeObjectURL(videoUrl);
return;
}
const videoState: VideoUploadState = {
file,
videoElement,
isReady: true
};
onVideoReady(videoState);
} catch (error) {
console.error('Error processing video:', error);
setUploadError('Failed to process video file. Please try a different file.');
} finally {
setIsProcessing(false);
}
}, [onVideoReady]);
const handleDrag = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDragIn = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setDragActive(true);
}
}, []);
const handleDragOut = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files);
}
}, [handleFiles]);
const handleFileSelect = useCallback(() => {
fileInputRef.current?.click();
}, []);
const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
handleFiles(e.target.files);
}, [handleFiles]);
return (
<div className="absolute inset-0 text-white flex items-center justify-center p-8">
<div className="max-w-2xl w-full space-y-8">
{/* Header */}
<GlassContainer
className="rounded-3xl shadow-2xl hover:scale-105 transition-transform duration-200"
role="banner"
>
<div className="p-8 text-center">
<h1 className="text-4xl font-bold text-gray-100 mb-4">Upload Video</h1>
<p className="text-lg text-gray-300 leading-relaxed">
Select a video file to analyze with FastVLM
</p>
</div>
</GlassContainer>
{/* Upload Area */}
<GlassContainer
className={`rounded-2xl shadow-2xl transition-all duration-200 border-2 border-dashed ${
dragActive
? 'border-blue-400 bg-blue-500/10 scale-105'
: uploadError
? 'border-red-400 bg-red-500/10'
: 'border-gray-500 hover:border-gray-400 hover:scale-105'
}`}
bgColor={
dragActive
? GLASS_EFFECTS.COLORS.BUTTON_BG
: uploadError
? GLASS_EFFECTS.COLORS.ERROR_BG
: GLASS_EFFECTS.COLORS.DEFAULT_BG
}
onDrag={handleDrag}
onDragStart={handleDrag}
onDragEnd={handleDrag}
onDragOver={handleDrag}
onDragEnter={handleDragIn}
onDragLeave={handleDragOut}
onDrop={handleDrop}
>
<div className="p-12 text-center">
{isProcessing ? (
<div className="space-y-4">
<div className="text-6xl"></div>
<h3 className="text-xl font-semibold text-gray-200">Processing video...</h3>
<p className="text-gray-400">Please wait while we prepare your video</p>
</div>
) : (
<div className="space-y-6">
<div className="text-6xl">{dragActive ? '📂' : '📁'}</div>
<div>
<h3 className="text-xl font-semibold text-gray-200 mb-2">
{dragActive ? 'Drop your video here' : 'Upload Video File'}
</h3>
<p className="text-gray-400 mb-4">
Drag and drop a video file or click to browse
</p>
<div className="text-sm text-gray-500 space-y-1">
<p>Supported formats: MP4, WebM, AVI, MOV</p>
<p>Maximum size: 100MB | Maximum duration: 10 minutes</p>
</div>
</div>
<GlassButton
onClick={handleFileSelect}
className="px-6 py-3 rounded-xl"
disabled={isProcessing}
>
Choose File
</GlassButton>
</div>
)}
{uploadError && (
<div className="mt-6 p-4 rounded-xl bg-red-500/20 border border-red-500/30">
<p className="text-red-300 font-medium">{uploadError}</p>
</div>
)}
</div>
</GlassContainer>
{/* Back Button */}
<div className="flex justify-center">
<GlassButton
onClick={onBack}
className="px-6 py-3 rounded-xl"
disabled={isProcessing}
>
← Back to Options
</GlassButton>
</div>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept="video/*"
onChange={handleFileChange}
className="hidden"
/>
</div>
</div>
);
}