Browse Source

Definitve fix of stutter issue. Now the playback engine is completely fixed and works as intended.

refactor/sync-centralize
RUSHIL AMBARISH KADU 6 months ago
parent
commit
a5c5400c57
  1. 3
      .gitignore
  2. 5
      steps/src/main.js
  3. 4
      steps/src/p5/radarSketch.js
  4. 90
      steps/src/sync.js

3
.gitignore

@ -1,2 +1,5 @@
~$Plan.docx
D:\ARAS\refactor\zoomsketch-issue
steps/Console_logs/Log_After_Updateframe_moved.log
steps/Console_logs/127.0.0.1-1763094792904.log
steps/Console_logs/

5
steps/src/main.js

@ -346,6 +346,11 @@ function finalizeSetup(_parsedJsonData) {
appState.speedGraphInstance.setData(appState.vizData, videoPlayer.duration);
appState.speedGraphInstance.redraw();
}
// --- START: FIX for Initial Overlay Visibility ---
// Manually update overlays on initial load so they are visible before playback starts.
updatePersistentOverlays(videoPlayer.currentTime);
updateDebugOverlay(videoPlayer.currentTime);
// --- END: FIX for Initial Overlay Visibility ---
// Update SNR inputs now that data is loaded
if (appState.vizData) {

4
steps/src/p5/radarSketch.js

@ -130,7 +130,9 @@ export const radarSketch = function (p) {
};
p.draw = function () {
if (debugFlags.drawing) console.log("draw_DEBUG: radarSketch.draw() called.");
if (debugFlags.drawing) {
console.log(`[${performance.now().toFixed(3)}] draw_DEBUG: radarSketch.draw() called.`);
}
// --- START: FPS Calculation & Display ---
const currentTime = p.millis();

90
steps/src/sync.js

@ -137,6 +137,12 @@ export function updateFrame(frame, forceVideoSeek = false) {
// --- END: Centralized Explorer Update ---
const endTime = performance.now();
appState.lastFrameRenderTime = endTime - startTime; // <-- End timer and update state
// --- START: FIX for Overlay Visibility During Scrubbing ---
// Update overlays here to ensure they refresh when scrubbing while paused.
updatePersistentOverlays(videoPlayer.currentTime);
updateDebugOverlay(videoPlayer.currentTime);
// --- END: FIX for Overlay Visibility During Scrubbing ---
}
// --- [END] MOVED FROM DOM.JS ---
@ -154,7 +160,9 @@ export function stopPlayback() {
* Its ONLY job is to update appState.currentFrame. It does NO drawing.
*/
export function videoFrameCallback(now, metadata) {
if (debugFlags.sync) console.log("vfc_DEBUG: videoFrameCallback running.");
if (debugFlags.sync) {
console.log(`[${performance.now().toFixed(3)}] vfc_DEBUG: videoFrameCallback running.`);
}
if (!appState.isPlaying || videoPlayer.paused || !appState.vizData) {
return;
@ -170,7 +178,7 @@ export function videoFrameCallback(now, metadata) {
// 3. Update the application state. This is the ONLY state this function changes.
if (frameIndex !== appState.currentFrame) {
appState.currentFrame = frameIndex;
updateFrame(appState.currentFrame); // <-- MOVE UI UPDATE CALL HERE
// This is the ONLY state this function should change. All UI updates are in animationLoop.
}
// Re-register the callback for the next frame to create a loop
@ -182,12 +190,12 @@ export function videoFrameCallback(now, metadata) {
* Its ONLY job is to draw the current state. It does NO data calculation.
*/
export function animationLoop() {
if (debugFlags.sync) console.log("anim_DEBUG: animationLoop running.");
if (debugFlags.sync) {
console.log(`[${performance.now().toFixed(3)}] anim_DEBUG: animationLoop running.`);
}
// Update debug overlay information
updatePersistentOverlays(videoPlayer.currentTime);
// updatePersistentOverlays(); // This is a duplicate call and can be removed
updateDebugOverlay(videoPlayer.currentTime);
// The render loop is responsible for ALL UI updates, ensuring perfect sync.
updateFrame(appState.currentFrame);
// --- START: Centralized Redraw Logic ---
// Explicitly redraw all active sketches in sync with the animation frame.
@ -201,22 +209,86 @@ export function animationLoop() {
}
}
let timelineDebounceTimer;
export function handleTimelineInput(event) {
if (!appState.vizData) return;
// 1. If playing, pause playback to allow scrubbing.
if (appState.isPlaying) {
pausePlayback();
appState.isPlaying = false;
playPauseBtn.textContent = "Play";
}
// 2. Get the target frame from the slider.
const frame = parseInt(event.target.value, 10);
updateFrame(frame, true); // Seek the video to the new frame
// Manually trigger redraws since the animation loop is not running
// 3. Update UI immediately for responsiveness, but WITHOUT forcing a video seek.
updateFrame(frame, false);
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
// 4. Use a debouncer to perform the expensive video seek after the user stops dragging.
clearTimeout(timelineDebounceTimer);
timelineDebounceTimer = setTimeout(() => {
updateFrame(appState.currentFrame, true); // Perform final, precise video seek.
}, 300); // 300ms delay after last input event.
}
let lastScrollTime = 0;
let scrollSpeed = 0;
let seekDebounceTimer;
function handleTimelineWheel(event) {
if (!appState.vizData) return;
event.preventDefault(); // Prevent default page scroll
// 1. Pause playback if the user starts scrubbing.
if (appState.isPlaying) {
pausePlayback();
appState.isPlaying = false;
playPauseBtn.textContent = "Play";
}
// 2. Calculate scroll speed to create a dynamic seek amount.
const now = performance.now();
const timeDelta = now - (lastScrollTime || now);
lastScrollTime = now;
scrollSpeed = timeDelta > 0 ? 1000 / timeDelta : scrollSpeed;
// 3. Map scroll speed to an acceleration curve.
// The sensitivity value (e.g., 4) can be adjusted for more/less acceleration.
const speedMultiplier = 1 + Math.floor(scrollSpeed / 4);
const seekAmount = Math.max(1, speedMultiplier); // Ensure we always move at least 1 frame.
// 4. Calculate the new frame index.
const direction = Math.sign(event.deltaY);
// FIX: Invert the direction. Scrolling down (positive deltaY) should advance the frame.
let newFrame = appState.currentFrame + direction * seekAmount;
// 5. Clamp the new frame to the valid range.
const totalFrames = appState.vizData.radarFrames.length - 1;
newFrame = Math.max(0, Math.min(newFrame, totalFrames));
// 6. Update the UI immediately for responsive feedback, but WITHOUT forcing a video seek.
// This makes the slider feel fast without causing video stutter.
updateFrame(newFrame, false);
// --- START: Immediate Redraw for Responsiveness ---
// Manually trigger redraws here so the radar visualization updates as the user scrolls.
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
// --- END: Immediate Redraw for Responsiveness ---
// 7. Use a debouncer for the expensive video seek. This will only run once
// after the user has finished scrolling, ensuring a final, precise sync.
clearTimeout(seekDebounceTimer);
seekDebounceTimer = setTimeout(() => {
// Perform the final, expensive video seek.
updateFrame(appState.currentFrame, true);
}, 300); // 300ms delay after the last scroll event.
}
export function initSyncUIHandlers() {
timelineSlider.addEventListener("input", handleTimelineInput);
timelineSlider.addEventListener("wheel", handleTimelineWheel, { passive: false });
}
Loading…
Cancel
Save