'use client'; import { RefObject, useEffect } from 'react'; import { AlertTriangle, Loader2 } from 'lucide-react'; import type { DetectionResultLog, VideoAnnotationFrameDetection, } from '@/types'; import { useDetectionThumbnails } from './useDetectionThumbnails'; import BoundingBoxOverlay from '@/components/annotation/boundingBoxOverlay'; import { MediaPlayer, MediaProvider, Poster, isVideoProvider, type MediaPlayerInstance, useMediaProvider, } from '@vidstack/react'; import { DefaultVideoLayout, defaultLayoutIcons, } from '@vidstack/react/player/layouts/default'; interface AnnotatedVideoPlayerProps { videoRef: RefObject; logs: DetectionResultLog[]; visibleDetections: VideoAnnotationFrameDetection[]; videoWidth: number; videoHeight: number; videoUrl: string | null; isLoading: boolean; isError: boolean; onRetry: () => void; onTimeUpdate: () => void; onVideoFrame: (mediaTime: number) => void; onSeeked: () => void; } function VideoFrameSync({ onVideoFrame, }: Pick) { const provider = useMediaProvider(); useEffect(() => { if (!isVideoProvider(provider)) return; const video = provider.video; let callbackId: number; const update = (_now: number, metadata: VideoFrameCallbackMetadata) => { onVideoFrame(metadata.mediaTime); callbackId = video.requestVideoFrameCallback(update); }; callbackId = video.requestVideoFrameCallback(update); return () => video.cancelVideoFrameCallback(callbackId); }, [onVideoFrame, provider]); return null; } export default function AnnotatedVideoPlayer({ videoRef, logs, visibleDetections, videoWidth, videoHeight, videoUrl, isLoading, isError, onRetry, onTimeUpdate, onVideoFrame, onSeeked, }: AnnotatedVideoPlayerProps) { const thumbnails = useDetectionThumbnails(logs); return (
{videoUrl ? ( ) : (
{isError ? ( <>

Failed to load the annotated video.

) : ( <>

{isLoading ? 'Loading annotated video...' : 'Preparing video...'}

)}
)}
); }