From cc2de23ba07b6060e2ae3b786f7b3d238918675d Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Wed, 27 Aug 2025 15:16:15 +0530 Subject: [PATCH] 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. --- steps/index.html | 40 ++++++++---------------------------- steps/src/sync.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/steps/index.html b/steps/index.html index d79d5dc..580e3f0 100644 --- a/steps/index.html +++ b/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 { @@ -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(); } }); 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 --- document.addEventListener('DOMContentLoaded', () => { diff --git a/steps/src/sync.js b/steps/src/sync.js index e69de29..fc6cdfa 100644 --- a/steps/src/sync.js +++ b/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); +}