Browse Source

refactor(sync): Extract animation loop into sync module

Moves the main animationLoop function from index.html into a dedicated src/sync.js module.

This change isolates the core playback, clock management, and video resynchronization logic into a single, focused file.

The main script in index.html now imports and calls this function, simplifying its responsibilities and preparing for the final wiring step.
refactor/modularize
RUSHIL AMBARISH KADU 9 months ago
parent
commit
cc2de23ba0
  1. 40
      steps/index.html
  2. 52
      steps/src/sync.js

40
steps/index.html

@ -263,6 +263,14 @@
// =========================================================================================================== // ===========================================================================================================
// import animation loop from './src/sync.js';
import {
animationLoop
} from './src/sync.js';
// import radar sketch from './src/p5/radarSketch.js'; // import radar sketch from './src/p5/radarSketch.js';
import { import {
@ -506,38 +514,6 @@
document.addEventListener('keydown', (event) => { if (!appState.vizData || ['ArrowRight', 'ArrowLeft'].indexOf(event.key) === -1) return; event.preventDefault(); if (appState.isPlaying) { appState.isPlaying = false; playPauseBtn.textContent = 'Play'; videoPlayer.pause(); } let newFrame = appState.currentFrame; if (event.key === 'ArrowRight') newFrame = Math.min(appState.vizData.radarFrames.length - 1, appState.currentFrame + 1); else if (event.key === 'ArrowLeft') newFrame = Math.max(0, appState.currentFrame - 1); if (newFrame !== appState.currentFrame) { updateFrame(newFrame, true); appState.mediaTimeStart = videoPlayer.currentTime; appState.masterClockStart = performance.now(); } }); document.addEventListener('keydown', (event) => { if (!appState.vizData || ['ArrowRight', 'ArrowLeft'].indexOf(event.key) === -1) return; event.preventDefault(); if (appState.isPlaying) { appState.isPlaying = false; playPauseBtn.textContent = 'Play'; videoPlayer.pause(); } let newFrame = appState.currentFrame; if (event.key === 'ArrowRight') newFrame = Math.min(appState.vizData.radarFrames.length - 1, appState.currentFrame + 1); else if (event.key === 'ArrowLeft') newFrame = Math.max(0, appState.currentFrame - 1); if (newFrame !== appState.currentFrame) { updateFrame(newFrame, true); appState.mediaTimeStart = videoPlayer.currentTime; appState.masterClockStart = performance.now(); } });
function calculateAndSetOffset() { const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename); const videoTimestampInfo = extractTimestampInfo(appState.videoFilename); if (videoTimestampInfo) { appState.videoStartDate = parseTimestamp(videoTimestampInfo.timestampStr, videoTimestampInfo.format); if (appState.videoStartDate) console.log(`Video start date set to: ${appState.videoStartDate.toISOString()}`); } if (jsonTimestampInfo) { const jsonDate = parseTimestamp(jsonTimestampInfo.timestampStr, jsonTimestampInfo.format); if (jsonDate) { appState.radarStartTimeMs = jsonDate.getTime(); console.log(`Radar start date set to: ${jsonDate.toISOString()}`); if (appState.videoStartDate) { const offset = appState.radarStartTimeMs - appState.videoStartDate.getTime(); offsetInput.value = offset; localStorage.setItem('visualizerOffset', offset); autoOffsetIndicator.classList.remove('hidden'); console.log(`Auto-calculated offset: ${offset} ms`); } } } } function calculateAndSetOffset() { const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename); const videoTimestampInfo = extractTimestampInfo(appState.videoFilename); if (videoTimestampInfo) { appState.videoStartDate = parseTimestamp(videoTimestampInfo.timestampStr, videoTimestampInfo.format); if (appState.videoStartDate) console.log(`Video start date set to: ${appState.videoStartDate.toISOString()}`); } if (jsonTimestampInfo) { const jsonDate = parseTimestamp(jsonTimestampInfo.timestampStr, jsonTimestampInfo.format); if (jsonDate) { appState.radarStartTimeMs = jsonDate.getTime(); console.log(`Radar start date set to: ${jsonDate.toISOString()}`); if (appState.videoStartDate) { const offset = appState.radarStartTimeMs - appState.videoStartDate.getTime(); offsetInput.value = offset; localStorage.setItem('visualizerOffset', offset); autoOffsetIndicator.classList.remove('hidden'); console.log(`Auto-calculated offset: ${offset} ms`); } } } }
function animationLoop() {
if (!appState.isPlaying) return;
const playbackSpeed = parseFloat(speedSlider.value);
const elapsedRealTime = performance.now() - appState.masterClockStart;
const currentMediaTime = appState.mediaTimeStart + (elapsedRealTime / 1000) * playbackSpeed;
if (appState.vizData && appState.videoStartDate) {
const offsetMs = parseFloat(offsetInput.value) || 0;
const targetRadarTimeMs = (currentMediaTime * 1000) + offsetMs;
const targetFrame = findRadarFrameIndexForTime(targetRadarTimeMs, appState.vizData);
if (targetFrame !== appState.currentFrame) {
updateFrame(targetFrame, false);
}
}
const now = performance.now();
if (now - appState.lastSyncTime > 500) {
const videoTime = videoPlayer.currentTime;
const drift = Math.abs(currentMediaTime - videoTime);
if (drift > 0.15) {
console.warn(`Resyncing video. Drift was: ${drift.toFixed(3)}s`);
videoPlayer.currentTime = currentMediaTime;
}
appState.lastSyncTime = now;
}
if (currentMediaTime >= videoPlayer.duration) {
stopBtn.click();
return;
}
updateCanDisplay(currentMediaTime);
updateDebugOverlay(currentMediaTime);
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
requestAnimationFrame(animationLoop);
}
// --- Application Initialization --- // --- Application Initialization ---
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {

52
steps/src/sync.js

@ -0,0 +1,52 @@
import { appState } from './state.js';
import { videoPlayer, speedSlider, offsetInput, stopBtn, updateFrame, updateCanDisplay, updateDebugOverlay } from './dom.js';
import { findRadarFrameIndexForTime } from './utils.js';
/**
* The main animation loop that drives the synchronized playback.
* It calculates the current media time based on performance.now() for a smooth clock,
* finds the corresponding radar frame, and handles resynchronization with the video element.
*/
export function animationLoop() {
if (!appState.isPlaying) return;
const playbackSpeed = parseFloat(speedSlider.value);
const elapsedRealTime = performance.now() - appState.masterClockStart;
const currentMediaTime = appState.mediaTimeStart + (elapsedRealTime / 1000) * playbackSpeed;
// Update radar frame based on the master clock
if (appState.vizData && appState.videoStartDate) {
const offsetMs = parseFloat(offsetInput.value) || 0;
const targetRadarTimeMs = (currentMediaTime * 1000) + offsetMs;
const targetFrame = findRadarFrameIndexForTime(targetRadarTimeMs, appState.vizData);
if (targetFrame !== appState.currentFrame) {
updateFrame(targetFrame, false);
}
}
// Periodically check for drift between master clock and video element
const now = performance.now();
if (now - appState.lastSyncTime > 500) {
const videoTime = videoPlayer.currentTime;
const drift = Math.abs(currentMediaTime - videoTime);
if (drift > 0.15) { // Resync if drift is > 150ms
console.warn(`Resyncing video. Drift was: ${drift.toFixed(3)}s`);
videoPlayer.currentTime = currentMediaTime;
}
appState.lastSyncTime = now;
}
// Stop playback at the end of the video
if (currentMediaTime >= videoPlayer.duration) {
stopBtn.click();
return;
}
// Update other UI elements
updateCanDisplay(currentMediaTime);
updateDebugOverlay(currentMediaTime);
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
// Request the next frame
requestAnimationFrame(animationLoop);
}
Loading…
Cancel
Save