From a6310a93d2b206219444eeac377ffe2d461efaaf 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 9cca2de5..9724dc74 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) => {