Final working front

This commit is contained in:
Suman991
2026-01-29 14:27:57 +05:30
parent a83545d1db
commit 93c1d763b5
4 changed files with 165 additions and 35 deletions

View File

@@ -13,8 +13,9 @@ import type { DetectionData, DetectionType } from "@/app/page"
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1"
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || "ws://127.0.0.1:8000/api/v1"
type UploadSectionProps = {
onDetectionComplete: (data: DetectionData, videoId: string) => void
onDetectionComplete: (data: DetectionData, videoId: string, file: File) => void
onDetectionTypeChange: (type: DetectionType) => void
}
@@ -110,8 +111,10 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
setStatusMessage("✓ Complete!")
setError(null)
// Pass video_id instead of file
onDetectionComplete(detectionData, videoId)
// Pass detection data, video_id, and the original file
if (file) {
onDetectionComplete(detectionData, videoId, file)
}
} catch (error) {
console.error("[Upload] Failed to load results:", error)
setError("Failed to load results. Please try again.")
@@ -139,6 +142,10 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
try {
console.log("[Upload] Uploading to:", `${API_URL}/upload`)
console.log("[Upload] Detection type:", detectionType)
console.log("[Upload] FormData contents:")
console.log(" - file:", file.name)
console.log(" - detection_type:", detectionType)
console.log(" - speed_kmh:", speed)
const response = await fetch(`${API_URL}/upload`, {
method: "POST",
@@ -159,6 +166,7 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
const videoId = result.video_id
console.log("[Upload] Video uploaded successfully, ID:", videoId)
console.log("[Upload] Response:", result)
setStatusMessage("Uploaded! Starting processing...")
setProgress(10)

View File

@@ -12,6 +12,7 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1
type VideoPlayerSectionProps = {
data: DetectionData
videoId: string
videoFile: File
detectionType: DetectionType
}
@@ -109,8 +110,10 @@ function SummarySection({ data, show, detectionType }: { data: DetectionData; sh
)
}
export default function VideoPlayerSection({ data, videoId, detectionType }: VideoPlayerSectionProps) {
export default function VideoPlayerSection({ data, videoId, videoFile, detectionType }: VideoPlayerSectionProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const [currentFrame, setCurrentFrame] = useState(0)
const [detectionsCount, setDetectionsCount] = useState(0)
const [logs, setLogs] = useState<DetectionLog[]>([])
@@ -147,23 +150,131 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
frameDetectionMap.current = map
}, [data, isPothole])
// Load processed video from backend
// Function to draw bounding boxes on canvas
const drawBoundingBoxes = useCallback((detections: any[]) => {
const canvas = canvasRef.current
const video = videoRef.current
if (!canvas || !video) return
const ctx = canvas.getContext('2d')
if (!ctx) return
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (!detections || detections.length === 0) return
// Calculate scale between video natural size and displayed size
const scaleX = canvas.width / data.video_info.width
const scaleY = canvas.height / data.video_info.height
detections.forEach((detection) => {
const bbox = detection.bbox
if (!bbox) return
// Scale coordinates to match displayed video size
const x1 = bbox.x1 * scaleX
const y1 = bbox.y1 * scaleY
const x2 = bbox.x2 * scaleX
const y2 = bbox.y2 * scaleY
const width = x2 - x1
const height = y2 - y1
// Set colors based on detection type
const boxColor = isPothole ? '#ef4444' : '#3b82f6' // red for potholes, blue for signboards
const textBgColor = isPothole ? 'rgba(239, 68, 68, 0.9)' : 'rgba(59, 130, 246, 0.9)'
// Draw bounding box
ctx.strokeStyle = boxColor
ctx.lineWidth = 3
ctx.strokeRect(x1, y1, width, height)
// Draw semi-transparent fill
ctx.fillStyle = isPothole ? 'rgba(239, 68, 68, 0.15)' : 'rgba(59, 130, 246, 0.15)'
ctx.fillRect(x1, y1, width, height)
// Prepare label text
const id = isPothole ? detection.pothole_id : detection.signboard_id
const confidence = (detection.confidence * 100).toFixed(1)
let labelText = isPothole
? `Pothole #${id}`
: `${detection.type || 'Sign'} #${id}`
labelText += ` ${confidence}%`
// Draw label background
ctx.font = 'bold 14px system-ui'
const textMetrics = ctx.measureText(labelText)
const textWidth = textMetrics.width + 16
const textHeight = 26
ctx.fillStyle = textBgColor
ctx.fillRect(x1, y1 - textHeight - 2, textWidth, textHeight)
// Draw label text
ctx.fillStyle = '#ffffff'
ctx.fillText(labelText, x1 + 8, y1 - 8)
})
}, [data.video_info.width, data.video_info.height, isPothole])
// Resize canvas to match video display size
const resizeCanvas = useCallback(() => {
const video = videoRef.current
const canvas = canvasRef.current
const container = containerRef.current
if (!video || !canvas || !container) return
// Get the displayed size of the video
const rect = video.getBoundingClientRect()
canvas.width = rect.width
canvas.height = rect.height
// Redraw current frame's detections
const frame = Math.round(video.currentTime * data.video_info.fps)
const detections = frameDetectionMap.current.get(frame)
if (detections) {
drawBoundingBoxes(detections)
}
}, [data.video_info.fps, drawBoundingBoxes])
// Handle window resize
useEffect(() => {
if (videoRef.current && videoId) {
const videoUrl = `${API_URL}/video/${videoId}`
console.log(`[VideoPlayer] Loading processed video from: ${videoUrl}`)
window.addEventListener('resize', resizeCanvas)
return () => window.removeEventListener('resize', resizeCanvas)
}, [resizeCanvas])
// Initialize canvas size when video loads
useEffect(() => {
const video = videoRef.current
if (!video) return
const handleLoadedMetadata = () => {
resizeCanvas()
}
video.addEventListener('loadedmetadata', handleLoadedMetadata)
return () => video.removeEventListener('loadedmetadata', handleLoadedMetadata)
}, [resizeCanvas])
// Load video from uploaded file
useEffect(() => {
if (videoRef.current && videoFile) {
const videoUrl = URL.createObjectURL(videoFile)
console.log(`[VideoPlayer] Loading video from uploaded file`)
videoRef.current.src = videoUrl
// Handle video load errors
const handleError = () => {
console.error("[VideoPlayer] Failed to load processed video")
setVideoError("Failed to load processed video. Please try refreshing the page.")
console.error("[VideoPlayer] Failed to load video")
setVideoError("Failed to load video. Please try refreshing the page.")
}
const handleLoaded = () => {
console.log("[VideoPlayer] Processed video loaded successfully")
console.log("[VideoPlayer] Video loaded successfully")
setVideoError(null)
resizeCanvas()
}
videoRef.current.addEventListener("error", handleError)
@@ -174,9 +285,11 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
videoRef.current.removeEventListener("error", handleError)
videoRef.current.removeEventListener("loadeddata", handleLoaded)
}
// Revoke object URL to free memory
URL.revokeObjectURL(videoUrl)
}
}
}, [videoId])
}, [videoFile, resizeCanvas])
// Add detection log with deduplication and size limit
const addDetectionLog = useCallback((frame: number, detections: any[]) => {
@@ -215,11 +328,14 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
const detections = frameDetectionMap.current.get(frame)
setDetectionsCount(detections?.length || 0)
// Draw bounding boxes for current frame
drawBoundingBoxes(detections || [])
if (detections && detections.length > 0) {
addDetectionLog(frame, detections)
}
}
}, [data.video_info.fps, addDetectionLog])
}, [data.video_info.fps, addDetectionLog, drawBoundingBoxes])
// Video playback monitoring
useEffect(() => {
@@ -270,6 +386,9 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
const detections = frameDetectionMap.current.get(frame)
setDetectionsCount(detections?.length || 0)
// Draw bounding boxes for current frame
drawBoundingBoxes(detections || [])
}
const handleSeeked = () => {
@@ -294,7 +413,7 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
video.removeEventListener("seeked", handleSeeked)
video.removeEventListener("play", handlePlay)
}
}, [data.video_info.fps])
}, [data.video_info.fps, drawBoundingBoxes])
return (
<div className="space-y-6">
@@ -302,12 +421,12 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
<CardHeader>
<CardTitle>Video Playback with Detection</CardTitle>
<CardDescription>
Watch the processed video with {isPothole ? "pothole" : "signboard"} detections already drawn
Watch the video with real-time {isPothole ? "pothole" : "signboard"} detection overlays
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Video Player */}
{/* Video Player with Canvas Overlay */}
<div className="lg:col-span-2 space-y-4">
{videoError && (
<div className="bg-destructive/10 text-destructive p-4 rounded-lg mb-4">
@@ -315,17 +434,27 @@ export default function VideoPlayerSection({ data, videoId, detectionType }: Vid
</div>
)}
<div className="relative bg-black rounded-lg overflow-hidden">
<div
ref={containerRef}
className="relative bg-black rounded-lg overflow-hidden"
style={{
aspectRatio: `${data.video_info.width} / ${data.video_info.height}`,
}}
>
<video
ref={videoRef}
controls
className="w-full h-auto"
style={{
aspectRatio: `${data.video_info.width} / ${data.video_info.height}`,
}}
className="w-full h-full"
>
Your browser does not support the video tag.
</video>
{/* Canvas overlay for bounding boxes */}
<canvas
ref={canvasRef}
className="absolute top-0 left-0 pointer-events-none"
style={{ width: '100%', height: '100%' }}
/>
</div>
{/* Video Info */}