From 72f58a57332c4c88c136cc09607c105194d7cd24 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Thu, 13 Nov 2025 09:56:18 +0530 Subject: [PATCH] Moved the timeline slider logic to sync.js --- steps/src/main.js | 91 ++-------------------------------------------- steps/src/sync.js | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/steps/src/main.js b/steps/src/main.js index e11c9d7..99bf04a 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -33,6 +33,8 @@ import { pausePlayback, stopPlayback, forceResyncWithOffset, + initSyncUIHandlers, + handleTimelineInput, } from "./sync.js"; import { radarSketch } from "./p5/radarSketch.js"; import { speedGraphSketch } from "./p5/speedGraphSketch.js"; @@ -117,10 +119,6 @@ import { initializeTheme } from "./theme.js"; import { initDB, saveFileWithMetadata, loadFreshFileFromDB } from "./db.js"; -let seekDebounceTimer = null; //timeline slider variables. -let lastScrollTime = 0; //timeline slider variables. -let scrollSpeed = 0; //timeline slider variables. - // --- [START] CORRECTED UNIFIED FILE LOADING LOGIC --- // These variables will hold the file objects during the loading process. @@ -696,92 +694,8 @@ stopBtn.addEventListener("click", () => { }); // Event listener for timeline slider input. -// In src/main.js, REPLACE the existing timelineSlider 'input' event listener with this: - -timelineSlider.addEventListener("input", (event) => { - if (!appState.vizData) return; - updateDebugOverlay(videoPlayer.currentTime); - updatePersistentOverlays(videoPlayer.currentTime); - // --- 1. Live Seeking (Throttled for performance) --- - // This part gives you the immediate visual feedback as you drag the slider. - // We use a simple timestamp check to prevent it from running too often. - const now = performance.now(); - if ( - !timelineSlider.lastInputTime || - now - timelineSlider.lastInputTime > 32 - ) { - // ~30fps throttle - if (appState.isPlaying) { - videoPlayer.pause(); - appState.isPlaying = false; - playPauseBtn.textContent = "Play"; - } - const frame = parseInt(event.target.value, 10); - updateFrame(frame, true); - appState.mediaTimeStart = videoPlayer.currentTime; - appState.masterClockStart = now; - } - - // --- 2. Final, Precise Sync (Debounced for reliability) --- - // This part ensures a perfect sync only AFTER you stop moving the slider. - clearTimeout(seekDebounceTimer); // Always cancel the previously scheduled sync - - seekDebounceTimer = setTimeout(() => { - console.log("Slider movement stopped. Performing final, debounced resync."); - const finalFrame = parseInt(event.target.value, 10); - updateFrame(finalFrame, true); // Perform the final, precise seek - - // Also update the debug overlay with the final, settled time - updateDebugOverlay(videoPlayer.currentTime); - }, 250); // Wait for 250ms of inactivity before firing -}); - // --- Timeline Scroll-to-Seek Logic --- -timelineSlider.addEventListener("wheel", (event) => { - if (!appState.vizData) return; - // 1. Prevent the page from scrolling up and down - event.preventDefault(); - - // 2. Calculate scroll speed - const now = performance.now(); - const timeDelta = now - (lastScrollTime || now); // Handle first scroll - lastScrollTime = now; - // Calculate speed as "events per second", giving more weight to recent, fast scrolls - scrollSpeed = timeDelta > 0 ? 1000 / timeDelta : scrollSpeed; - - // 3. Map scroll speed to a dynamic seek multiplier - // This creates a nice acceleration curve. The '50' is a sensitivity value you can adjust. - const speedMultiplier = 1 + Math.floor(scrollSpeed / 4); - const baseSeekAmount = 1; // Base frames to move on a slow scroll - let seekAmount = Math.max(baseSeekAmount, speedMultiplier); - - // 4. Calculate the new frame index - const direction = Math.sign(event.deltaY); // +1 for down/right, -1 for up/left - const currentFrame = parseInt(timelineSlider.value, 10); - let newFrame = currentFrame - seekAmount * direction; - - // Clamp the new frame to the valid range - const totalFrames = appState.vizData.radarFrames.length - 1; - newFrame = Math.max(0, Math.min(newFrame, totalFrames)); - - // 5. Update the UI - if (appState.isPlaying) { - playPauseBtn.click(); // Pause if playing - } - updateFrame(newFrame, true); - - // 6. Reuse the debouncer for a final, precise sync after scrolling stops - clearTimeout(seekDebounceTimer); - seekDebounceTimer = setTimeout(() => { - console.log("Scrolling stopped. Performing final, debounced resync."); - updateFrame(newFrame, true); - updatePersistentOverlays(videoPlayer.currentTime); - updateDebugOverlay(videoPlayer.currentTime); - }, 300); // Wait 300ms after the last scroll event - throttledUpdateExplorer(); -}); - // In src/main.js, add this new block of event listeners // --- Timeline Scrub-to-Seek Preview Logic --- @@ -1067,6 +981,7 @@ offsetInput.addEventListener("keydown", (event) => { document.addEventListener("DOMContentLoaded", () => { initializeTheme(); initializeDataExplorer(); // <-- ADD THIS LINE + initSyncUIHandlers(); initDB(async () => { console.log("Database initialized. Checking for cached session..."); diff --git a/steps/src/sync.js b/steps/src/sync.js index 48e96d4..a98b8ed 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -8,11 +8,16 @@ import { updateDebugOverlay, updatePersistentOverlays, videoPlayer, + timelineSlider, } from "./dom.js"; import { findRadarFrameIndexForTime } from "./utils.js"; +import { throttledUpdateExplorer } from "./dataExplorer.js"; // --- NEW Playback Control Functions --- +let seekDebounceTimer = null; +let lastScrollTime = 0; +let scrollSpeed = 0; export function startPlayback() { if (videoPlayer.src && videoPlayer.readyState > 1) { appState.masterClockStart = performance.now(); @@ -142,3 +147,90 @@ export function animationLoop() { // Request the next frame requestAnimationFrame(animationLoop); } + +export function handleTimelineInput(event) { + if (!appState.vizData) return; + updateDebugOverlay(videoPlayer.currentTime); + updatePersistentOverlays(videoPlayer.currentTime); + // --- 1. Live Seeking (Throttled for performance) --- + // This part gives you the immediate visual feedback as you drag the slider. + // We use a simple timestamp check to prevent it from running too often. + const now = performance.now(); + if ( + !timelineSlider.lastInputTime || + now - timelineSlider.lastInputTime > 32 + ) { + // ~30fps throttle + if (appState.isPlaying) { + videoPlayer.pause(); + appState.isPlaying = false; + playPauseBtn.textContent = "Play"; + } + const frame = parseInt(event.target.value, 10); + updateFrame(frame, true); + appState.mediaTimeStart = videoPlayer.currentTime; + appState.masterClockStart = now; + } + + // --- 2. Final, Precise Sync (Debounced for reliability) --- + // This part ensures a perfect sync only AFTER you stop moving the slider. + clearTimeout(seekDebounceTimer); // Always cancel the previously scheduled sync + + seekDebounceTimer = setTimeout(() => { + console.log("Slider movement stopped. Performing final, debounced resync."); + const finalFrame = parseInt(event.target.value, 10); + updateFrame(finalFrame, true); // Perform the final, precise seek + + // Also update the debug overlay with the final, settled time + updateDebugOverlay(videoPlayer.currentTime); + }, 250); // Wait for 250ms of inactivity before firing +} + +export function handleTimelineWheel(event) { + if (!appState.vizData) return; + // 1. Prevent the page from scrolling up and down + event.preventDefault(); + + // 2. Calculate scroll speed + const now = performance.now(); + const timeDelta = now - (lastScrollTime || now); // Handle first scroll + lastScrollTime = now; + // Calculate speed as "events per second", giving more weight to recent, fast scrolls + scrollSpeed = timeDelta > 0 ? 1000 / timeDelta : scrollSpeed; + + // 3. Map scroll speed to a dynamic seek multiplier + // This creates a nice acceleration curve. The '50' is a sensitivity value you can adjust. + const speedMultiplier = 1 + Math.floor(scrollSpeed / 4); + const baseSeekAmount = 1; // Base frames to move on a slow scroll + let seekAmount = Math.max(baseSeekAmount, speedMultiplier); + + // 4. Calculate the new frame index + const direction = Math.sign(event.deltaY); // +1 for down/right, -1 for up/left + const currentFrame = parseInt(timelineSlider.value, 10); + let newFrame = currentFrame - seekAmount * direction; + + // Clamp the new frame to the valid range + const totalFrames = appState.vizData.radarFrames.length - 1; + newFrame = Math.max(0, Math.min(newFrame, totalFrames)); + + // 5. Update the UI + if (appState.isPlaying) { + playPauseBtn.click(); // Pause if playing + } + updateFrame(newFrame, true); + + // 6. Reuse the debouncer for a final, precise sync after scrolling stops + clearTimeout(seekDebounceTimer); + seekDebounceTimer = setTimeout(() => { + console.log("Scrolling stopped. Performing final, debounced resync."); + updateFrame(newFrame, true); + updatePersistentOverlays(videoPlayer.currentTime); + updateDebugOverlay(videoPlayer.currentTime); + }, 300); // Wait 300ms after the last scroll event + throttledUpdateExplorer(); +} + +export function initSyncUIHandlers() { + timelineSlider.addEventListener("input", handleTimelineInput); + timelineSlider.addEventListener("wheel", handleTimelineWheel); +}