From d07bf745a337164254093f41c75b92520b0c6498 Mon Sep 17 00:00:00 2001 From: Timo <6156589+Shik3i@users.noreply.github.com> Date: Sat, 25 Apr 2026 17:42:55 +0200 Subject: [PATCH] fix: Add seek delta threshold and debounce to prevent HLS/DASH buffering micro-seeks from being relayed as user seeks Streaming players (Emby, Jellyfin, etc.) perform frequent internal seeks for buffering that are < 1s in magnitude. These were being relayed to peers, causing brief video freezes every few minutes. Fix: - MIN_SEEK_DELTA = 3.0s: ignore seeks smaller than 3 seconds - 800ms debounce: settle rapid-fire seeks (e.g. scrubbing) before relaying - Programmatic seek suppression still takes priority via expectedEvents - lastReportedSeekTime baseline updated on programmatic seeks too --- extension/content.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/extension/content.js b/extension/content.js index db9f6de..743ffb4 100644 --- a/extension/content.js +++ b/extension/content.js @@ -30,6 +30,13 @@ let expectedEvents = new Set(); let expectedTimeouts = {}; + // --- Seek Relay Filtering --- + // Minimum seek delta (seconds) to report. Prevents HLS/DASH buffering micro-seeks + // from being relayed to peers as user-initiated seeks. + const MIN_SEEK_DELTA = 3.0; + let lastReportedSeekTime = null; // last currentTime we relayed as a SEEK + let seekDebounceTimer = null; // debounce timer for rapid seek events + // --- Episode Auto-Sync State --- let lastKnownMediaTitle = null; let episodeTransitionDebounce = null; @@ -394,7 +401,39 @@ const handlePlay = () => reportEvent(EVENTS.PLAY); const handlePause = () => reportEvent(EVENTS.PAUSE); - const handleSeeked = () => reportEvent(EVENTS.SEEK); + + // Seek filtering: ignore HLS/DASH buffering micro-seeks. + // Only relay if delta >= MIN_SEEK_DELTA AND not already debouncing. + const handleSeeked = () => { + const video = findVideo(); + if (!video) return; + const current = video.currentTime; + if (!Number.isFinite(current)) return; + + // Step 1: Check expectedEvents (programmatic seek suppression) + if (expectedEvents.has('seek')) { + expectedEvents.delete('seek'); + lastReportedSeekTime = current; // Update baseline so next user seek is relative to here + return; + } + + // Step 2: Delta check — skip micro-seeks (buffering, chapter markers, etc.) + if (lastReportedSeekTime !== null && Math.abs(current - lastReportedSeekTime) < MIN_SEEK_DELTA) { + return; // Too small — likely an internal player seek, not user-initiated + } + + // Step 3: Debounce rapid consecutive seeks (e.g. scrubbing) + // — wait 800ms for the user to settle before relaying + if (seekDebounceTimer) clearTimeout(seekDebounceTimer); + seekDebounceTimer = setTimeout(() => { + seekDebounceTimer = null; + const v = findVideo(); + if (!v) return; + const settled = v.currentTime; + lastReportedSeekTime = settled; + reportEvent(EVENTS.SEEK); + }, 800); + }; let lastVideoSrc = null;