Explorar el Código

fix compare mode: playhead movement, timecode update, per-video annotations

- VideoPlayer rVFC onFrame: compare player now calls setCurrentTime so its
  own playhead/timeline thumb updates during playback. onTimeUpdate is skipped
  to avoid parent feedback loop. redrawAnnotationsRef still fires so compare
  video's own annotations render.
- onSeeked handler: compare player calls setCurrentTime on external seek
  so its seeker thumb follows the main player.
- Compare comments: fetch comments for the compare asset on selection
  (duration match) so it has its own annotation data.
- Compare annotations: derive compareVisibleAnnotations from compareComments,
  pass to compare VideoPlayer. Reset on exit compare.
Claude Dev hace 1 mes
padre
commit
096724c3a7
Se han modificado 2 ficheros con 34 adiciones y 14 borrados
  1. 19 3
      src/app/review/[assetId]/page.tsx
  2. 15 11
      src/components/video-player/VideoPlayer.tsx

+ 19 - 3
src/app/review/[assetId]/page.tsx

@@ -64,6 +64,7 @@ export default function ReviewPage() {
   const [showComparePicker, setShowComparePicker] = useState(false);
   const [projectAssets, setProjectAssets] = useState<Asset[]>([]);
   const [compareMismatch, setCompareMismatch] = useState<string | null>(null);
+  const [compareComments, setCompareComments] = useState<Comment[]>([]);
   const [playing, setPlaying] = useState(false);
 
   const handleCompareSelect = useCallback((compareAssetArg: Asset) => {
@@ -84,12 +85,19 @@ export default function ReviewPage() {
     }
     setCompareAsset(compareAssetArg);
     setCompareMode(true);
-  }, [asset]);
+    // Fetch compare asset's own comments for per-video annotations
+    if (token) {
+      commentsApi.list(token, compareAssetArg.id).then(({ comments: cc }) => {
+        setCompareComments(cc);
+      }).catch(() => setCompareComments([]));
+    }
+  }, [asset, token]);
 
   const handleExitCompare = useCallback(() => {
     setCompareMode(false);
     setCompareAsset(null);
     setCompareMismatch(null);
+    setCompareComments([]);
   }, []);
 
   useEffect(() => {
@@ -371,6 +379,14 @@ export default function ReviewPage() {
       (c.annotations ?? []).map(ann => ({ annotation: ann, timestamp: c.timestamp ?? 0 }))
     );
 
+  // Annotations for the compare video — independent per-video data
+  const compareVisibleComments = compareComments.filter(c => !c.deleted && (showResolved || !c.resolved));
+  const compareVisibleAnnotations = compareVisibleComments
+    .filter(c => !c.deleted)
+    .flatMap(c =>
+      (c.annotations ?? []).map(ann => ({ annotation: ann, timestamp: c.timestamp ?? 0 }))
+    );
+
   if (loading) {
     return (
       <div className="h-screen flex items-center justify-center" style={{ background: 'var(--bg)' }}>
@@ -613,8 +629,8 @@ export default function ReviewPage() {
                     src={compareAsset.hlsPath ? `${API_BASE}/uploads${compareAsset.hlsPath}` : `${API_BASE}/uploads/${compareAsset.filePath}`}
                     mimeType={compareAsset.mimeType}
                     fps={compareAsset.fps ?? 30}
-                    comments={[]}
-                    visibleAnnotations={[]}
+                    comments={compareComments}
+                    visibleAnnotations={compareVisibleAnnotations}
                     drawMode={false}
                     drawTool={drawTool}
                     drawColor={drawColor}

+ 15 - 11
src/components/video-player/VideoPlayer.tsx

@@ -133,14 +133,14 @@ export function VideoPlayer({
         videoCallbackRef.current = (video as any).requestVideoFrameCallback(onFrame);
         return;
       }
-      // In compare mode the parent controls state — rVFC just keeps redraws flowing
-      if (isComparePlayer) {
-        videoCallbackRef.current = (video as any).requestVideoFrameCallback(onFrame);
-        return;
-      }
       const t = metadata.mediaTime;
-      setCurrentTime(t);
-      onTimeUpdate(t);
+      // Compare player: update its own currentTime so its timeline/playhead moves during playback.
+      // Do NOT call onTimeUpdate here — that would create a feedback loop since the parent
+      // owns compare time via externalCurrentTime → seek effect.
+      if (!isComparePlayer) {
+        setCurrentTime(t);
+        onTimeUpdate(t);
+      }
       redrawAnnotationsRef.current(t);
       videoCallbackRef.current = (video as any).requestVideoFrameCallback(onFrame);
     }
@@ -148,11 +148,15 @@ export function VideoPlayer({
     function onSeeked() {
       // Only update UI for non-frame-step seeks (e.g. click-to-seek on timeline)
       if (stepInFlightRef.current) return;
-      // Compare player: skip — parent owns state
-      if (isComparePlayer) return;
       const t = videoRef.current?.currentTime ?? 0;
-      setCurrentTime(t);
-      onTimeUpdate(t);
+      // Compare player: skip onTimeUpdate to avoid feedback loop, but still update
+      // setCurrentTime so its own seeker/thumb moves when parent seeks it externally.
+      if (isComparePlayer) {
+        setCurrentTime(t);
+      } else {
+        setCurrentTime(t);
+        onTimeUpdate(t);
+      }
       redrawAnnotationsRef.current(t);
     }