From c969a0218ff785666dfd92045b2373dfb61fd66a Mon Sep 17 00:00:00 2001 From: you Date: Thu, 19 Mar 2026 07:25:32 +0000 Subject: [PATCH] Fix scrub: auto-replay on release, replay from correct timestamp 1. scrubRelease now calls vcrReplayFromTs directly (no pause step) 2. vcrReplayFromTs builds replay buffer from ONLY fetched DB packets, not merged with stale WS buffer entries. Starts playhead at 0 (first fetched packet), not closest-match in mixed buffer. Fixes replay starting from session start (00:06) instead of scrub target. --- public/live.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/public/live.js b/public/live.js index 9cca2de..9724dc7 100644 --- a/public/live.js +++ b/public/live.js @@ -112,26 +112,27 @@ function vcrReplayFromTs(targetTs) { const fetchFrom = new Date(targetTs - 5000).toISOString(); + stopReplay(); vcrSetMode('REPLAY'); fetch(`/api/packets?limit=200&grouped=false&since=${encodeURIComponent(fetchFrom)}`) .then(r => r.json()) .then(data => { const pkts = (data.packets || []).reverse(); - const existingIds = new Set(VCR.buffer.map(b => b.pkt.id).filter(Boolean)); - const newEntries = pkts.filter(p => !existingIds.has(p.id)).map(p => ({ + // Build a fresh replay buffer from ONLY the fetched packets + const replayEntries = pkts.map(p => ({ ts: new Date(p.timestamp || p.created_at).getTime(), pkt: dbPacketToLive(p) })); - if (newEntries.length) { - VCR.buffer = [...newEntries, ...VCR.buffer].sort((a, b) => a.ts - b.ts); + if (replayEntries.length === 0) { + vcrSetMode('PAUSED'); + return; } - let closest = 0, minDist = Infinity; - VCR.buffer.forEach((entry, i) => { - const dist = Math.abs(entry.ts - targetTs); - if (dist < minDist) { minDist = dist; closest = i; } - }); - VCR.playhead = closest; - VCR.scrubEnd = Math.min(closest + 50, VCR.buffer.length); + // Replace buffer with fetched packets for clean replay + // (keep existing buffer entries that are newer for when we resume live) + const oldNew = VCR.buffer.filter(b => b.ts > replayEntries[replayEntries.length - 1].ts); + VCR.buffer = [...replayEntries, ...oldNew]; + VCR.playhead = 0; // start from first fetched packet + VCR.scrubEnd = replayEntries.length; VCR.scrubTs = null; startReplay(); }) @@ -635,11 +636,10 @@ function scrubRelease() { VCR.dragging = false; VCR.frozenNow = Date.now(); - stopReplay(); - // Store the target timestamp so playhead stays put - VCR.scrubTs = VCR.frozenNow - VCR.timelineScope + VCR.dragPct * VCR.timelineScope; - vcrSetMode('PAUSED'); - updateVCRClock(VCR.scrubTs); + const targetTs = VCR.frozenNow - VCR.timelineScope + VCR.dragPct * VCR.timelineScope; + VCR.scrubTs = targetTs; + updateVCRClock(targetTs); + vcrReplayFromTs(targetTs); } timelineEl.addEventListener('mousedown', (e) => {