From 3cdea9dfecd52eaefb3965d7d9bcba0ba7082a77 Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 26 Dec 2025 16:23:48 -0600 Subject: [PATCH] fix: video review caption sync and event listener issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix video event listeners not re-attaching when video element remounts (add activeTab?.videoUrl to useEffect dependency array) - Add retimed_captions_vtt to VTT API response for accessible videos - Use retimed captions for accessible video tab in VideoReviewPlayer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/api/v1/routes_jobs.py | 9 +++++++++ backend/app/schemas/job.py | 1 + .../src/components/VideoReview/VideoReviewPlayer.tsx | 10 +++++----- frontend/src/types/api.ts | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/app/api/v1/routes_jobs.py b/backend/app/api/v1/routes_jobs.py index e90a6df..274e362 100644 --- a/backend/app/api/v1/routes_jobs.py +++ b/backend/app/api/v1/routes_jobs.py @@ -771,6 +771,15 @@ async def get_job_vtt_content( except Exception as e: logger.warning(f"Failed to fetch AD VTT: {e}") + # Fetch retimed captions VTT if available (for accessible videos) + if "retimed_captions_vtt_gcs" in lang_output: + blob_path = lang_output["retimed_captions_vtt_gcs"].replace(f"gs://{settings.gcs_bucket}/", "") + try: + blob = gcs_service.bucket.blob(blob_path) + response.retimed_captions_vtt = blob.download_as_text() + except Exception as e: + logger.warning(f"Failed to fetch retimed captions VTT: {e}") + return response diff --git a/backend/app/schemas/job.py b/backend/app/schemas/job.py index 92462b7..7a853e6 100644 --- a/backend/app/schemas/job.py +++ b/backend/app/schemas/job.py @@ -82,6 +82,7 @@ class JobDownloadsResponse(BaseModel): class VttContentResponse(BaseModel): captions_vtt: Optional[str] = None audio_description_vtt: Optional[str] = None + retimed_captions_vtt: Optional[str] = None # Re-timed captions for accessible videos class AssetValidationResponse(BaseModel): diff --git a/frontend/src/components/VideoReview/VideoReviewPlayer.tsx b/frontend/src/components/VideoReview/VideoReviewPlayer.tsx index 701ee7d..2232c49 100644 --- a/frontend/src/components/VideoReview/VideoReviewPlayer.tsx +++ b/frontend/src/components/VideoReview/VideoReviewPlayer.tsx @@ -54,9 +54,9 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { const captions = useMemo(() => { if (!vttContent) return []; - // For accessible videos, use retimed captions if available - const vttSource = activeTab?.isAccessible && activeTab.captionsVttField === 'retimed_captions_vtt_gcs' - ? vttContent.captions_vtt // The API returns captions_vtt regardless of source + // For accessible videos, use retimed captions if available, fall back to regular captions + const vttSource = activeTab?.isAccessible + ? (vttContent.retimed_captions_vtt || vttContent.captions_vtt) : vttContent.captions_vtt; if (!vttSource) return []; @@ -114,7 +114,7 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { }; }, []); - // Video time update handler + // Video time update handler - re-attach listeners when video element changes useEffect(() => { const video = videoRef.current; if (!video) return; @@ -140,7 +140,7 @@ export function VideoReviewPlayer({ job, downloads }: VideoReviewPlayerProps) { video.removeEventListener('pause', handlePause); clearTimeout(timeoutId); }; - }, []); + }, [activeTab?.videoUrl]); // Re-run when video URL changes (causes element remount) // Reset video when tab changes useEffect(() => { diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 08f9a9f..e3a87e7 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -204,6 +204,7 @@ export interface JobDownloadsResponse { export interface VttContentResponse { captions_vtt?: string; audio_description_vtt?: string; + retimed_captions_vtt?: string; // Re-timed captions for accessible videos } export interface VttUpdateRequest {