You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
123 lines
4.1 KiB
123 lines
4.1 KiB
import { appState } from "./state.js";
|
|
import {
|
|
videoPlayer,
|
|
speedSlider,
|
|
offsetInput,
|
|
stopBtn,
|
|
playPauseBtn,
|
|
updateFrame,
|
|
updateDebugOverlay,
|
|
updatePersistentOverlays,
|
|
} from "./dom.js";
|
|
import { findRadarFrameIndexForTime } from "./utils.js";
|
|
|
|
// --- NEW Playback Control Functions ---
|
|
|
|
export function startPlayback() {
|
|
if (videoPlayer.src && videoPlayer.readyState > 1) {
|
|
appState.masterClockStart = performance.now();
|
|
appState.mediaTimeStart = videoPlayer.currentTime;
|
|
appState.lastSyncTime = appState.masterClockStart;
|
|
videoPlayer.play();
|
|
videoPlayer.requestVideoFrameCallback(videoFrameCallback); // Start the high-precision loop
|
|
}
|
|
requestAnimationFrame(animationLoop); // Keep rAF for non-video sync (e.g. scrubbing)
|
|
}
|
|
|
|
export function pausePlayback() {
|
|
if (videoPlayer.src) {
|
|
videoPlayer.pause();
|
|
}
|
|
}
|
|
|
|
export function stopPlayback() {
|
|
videoPlayer.pause();
|
|
if (appState.vizData) {
|
|
updateFrame(0, true);
|
|
} else if (videoPlayer.src) {
|
|
videoPlayer.currentTime = 0;
|
|
}
|
|
}
|
|
|
|
export function videoFrameCallback(now, metadata) {
|
|
// If the video is no longer playing, stop the callback loop.
|
|
if (!appState.isPlaying || videoPlayer.paused) {
|
|
return;
|
|
}
|
|
|
|
// This is now the main animation driver during playback.
|
|
// It's perfectly synced with the video's frame presentation.
|
|
const currentTime = metadata.mediaTime;
|
|
const frameIndex = findRadarFrameIndexForTime(currentTime * 1000);
|
|
|
|
updateFrame(frameIndex, false); // Update radar, but don't seek video.
|
|
|
|
// Re-register the callback for the next frame to create a loop
|
|
videoPlayer.requestVideoFrameCallback(videoFrameCallback);
|
|
}
|
|
|
|
export function animationLoop() {
|
|
if (!appState.isPlaying) return;
|
|
|
|
// Get the current playback speed from the slider
|
|
const playbackSpeed = parseFloat(speedSlider.value);
|
|
// Calculate the elapsed real time since the master clock started
|
|
const elapsedRealTime = performance.now() - appState.masterClockStart;
|
|
// Calculate the current media time based on the master clock, initial media time, elapsed real time, and playback speed
|
|
const currentMediaTime =
|
|
appState.mediaTimeStart + (elapsedRealTime / 1000) * playbackSpeed;
|
|
|
|
// Update radar frame based on the master clock
|
|
// Check if visualization data and video start date are available
|
|
if (appState.vizData && appState.videoStartDate) {
|
|
// Get the offset from the input field, default to 0 if not a valid number
|
|
// --- START: Corrected Logic ---
|
|
const offsetMs = parseFloat(offsetInput.value) || 0;
|
|
// The master clock represents the VIDEO's timeline.
|
|
// To find the corresponding RADAR time, we must add the offset.
|
|
const targetRadarTimeMs = currentMediaTime * 1000 + offsetMs;
|
|
|
|
const targetFrame = findRadarFrameIndexForTime(
|
|
targetRadarTimeMs,
|
|
appState.vizData
|
|
);
|
|
// --- END: Corrected Logic ---
|
|
if (targetFrame !== appState.currentFrame) {
|
|
// Update the displayed frame if it's different from the current one
|
|
updateFrame(targetFrame, false);
|
|
}
|
|
}
|
|
|
|
// Periodically check for drift between master clock and video element
|
|
const now = performance.now();
|
|
if (now - appState.lastSyncTime > 150) {
|
|
const videoTime = videoPlayer.currentTime;
|
|
const drift = Math.abs(currentMediaTime - videoTime);
|
|
// Resync if drift is > 150ms
|
|
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 debug overlay information
|
|
updatePersistentOverlays(currentMediaTime);
|
|
updateDebugOverlay(currentMediaTime);
|
|
|
|
// --- START: Centralized Redraw Logic ---
|
|
// Explicitly redraw all active sketches in sync with the animation frame.
|
|
if (appState.p5_instance) appState.p5_instance.redraw();
|
|
if (appState.speedGraphInstance) appState.speedGraphInstance.redraw();
|
|
// --- END: Centralized Redraw Logic ---
|
|
|
|
// Request the next frame
|
|
requestAnimationFrame(animationLoop);
|
|
}
|