Zhen Ye Claude Opus 4.6 commited on
Commit
58bc91d
·
1 Parent(s): 83e3e44

fix: use actual video FPS for frame index calculation to prevent track card disappearance

Browse files

Frontend hardcoded 30fps for frame index calculation, causing drift on non-30fps
videos. Near the end of a 24fps video, the frontend would request frame indices
beyond what the backend stored, resulting in empty track data and disappearing
cards. Now uses the actual FPS from the backend summary endpoint, with clamping
to prevent overshoot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

frontend/js/core/state.js CHANGED
@@ -24,6 +24,8 @@ APP.core.state = {
24
  depthVideoUrl: null, // Depth video URL
25
  depthBlob: null, // Depth video blob
26
  summary: null,
 
 
27
  busy: false,
28
  lastError: null,
29
  missionSpec: null
 
24
  depthVideoUrl: null, // Depth video URL
25
  depthBlob: null, // Depth video blob
26
  summary: null,
27
+ fps: null, // actual video FPS from backend summary
28
+ totalFrames: null, // actual total frame count from backend
29
  busy: false,
30
  lastError: null,
31
  missionSpec: null
frontend/js/core/timeline.js CHANGED
@@ -60,7 +60,7 @@ APP.core.timeline = {};
60
  const ctx = canvas.getContext("2d");
61
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
62
 
63
- const fps = 30;
64
  const totalFrames = Math.ceil(duration * fps);
65
  if (totalFrames <= 0) return;
66
 
@@ -155,6 +155,8 @@ APP.core.timeline = {};
155
  // Cache duration from backend metadata (bypass video.duration dependency)
156
  if (data.total_frames > 0 && data.fps > 0) {
157
  APP.core.timeline._cachedDuration = data.total_frames / data.fps;
 
 
158
  }
159
 
160
  console.log(`[timeline] Loaded summary: ${Object.keys(frames).length} frames, duration=${APP.core.timeline._cachedDuration}s`);
 
60
  const ctx = canvas.getContext("2d");
61
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
62
 
63
+ const fps = APP.core.state.hf.fps || 30;
64
  const totalFrames = Math.ceil(duration * fps);
65
  if (totalFrames <= 0) return;
66
 
 
155
  // Cache duration from backend metadata (bypass video.duration dependency)
156
  if (data.total_frames > 0 && data.fps > 0) {
157
  APP.core.timeline._cachedDuration = data.total_frames / data.fps;
158
+ state.hf.fps = data.fps;
159
+ state.hf.totalFrames = data.total_frames;
160
  }
161
 
162
  console.log(`[timeline] Loaded summary: ${Object.keys(frames).length} frames, duration=${APP.core.timeline._cachedDuration}s`);
frontend/js/core/video.js CHANGED
@@ -61,6 +61,8 @@ APP.core.video.unloadVideo = async function (options = {}) {
61
  state.hf.completedJobId = null;
62
  state.hf.asyncStatus = "idle";
63
  state.hf.videoUrl = null;
 
 
64
 
65
  setHfStatus("idle");
66
  state.hasReasoned = false;
 
61
  state.hf.completedJobId = null;
62
  state.hf.asyncStatus = "idle";
63
  state.hf.videoUrl = null;
64
+ state.hf.fps = null;
65
+ state.hf.totalFrames = null;
66
 
67
  setHfStatus("idle");
68
  state.hasReasoned = false;
frontend/js/main.js CHANGED
@@ -166,6 +166,8 @@ document.addEventListener("DOMContentLoaded", () => {
166
  state.tracker.nextId = 1;
167
  state.tracker.heatmap = {};
168
  state.tracker.assessmentCache = {};
 
 
169
  APP.core.timeline._cachedDuration = null;
170
  if (btnPause) btnPause.textContent = "Pause";
171
  renderFrameTrackList();
@@ -592,7 +594,9 @@ document.addEventListener("DOMContentLoaded", () => {
592
  // Backend sync every 333ms — always runs, even with zero tracks
593
  const jobId = state.hf.asyncJobId || state.hf.completedJobId;
594
  if (jobId && (t - state.tracker.lastHFSync > 333)) {
595
- const frameIdx = Math.floor(videoEngage.currentTime * 30);
 
 
596
  if (isFinite(frameIdx) && frameIdx >= 0) {
597
  APP.core.tracker.syncWithBackend(frameIdx);
598
  }
 
166
  state.tracker.nextId = 1;
167
  state.tracker.heatmap = {};
168
  state.tracker.assessmentCache = {};
169
+ state.hf.fps = null;
170
+ state.hf.totalFrames = null;
171
  APP.core.timeline._cachedDuration = null;
172
  if (btnPause) btnPause.textContent = "Pause";
173
  renderFrameTrackList();
 
594
  // Backend sync every 333ms — always runs, even with zero tracks
595
  const jobId = state.hf.asyncJobId || state.hf.completedJobId;
596
  if (jobId && (t - state.tracker.lastHFSync > 333)) {
597
+ const fps = state.hf.fps || 30;
598
+ const maxFrame = state.hf.totalFrames ? state.hf.totalFrames - 1 : Infinity;
599
+ const frameIdx = Math.min(Math.floor(videoEngage.currentTime * fps), maxFrame);
600
  if (isFinite(frameIdx) && frameIdx >= 0) {
601
  APP.core.tracker.syncWithBackend(frameIdx);
602
  }