Compare commits

..

3 Commits

Author SHA1 Message Date
Timo 65ad4b5c6b chore: Bump version to 1.2.1
Patch release for:
- fix: Seek relay filtering (HLS/DASH buffering micro-seeks no longer relayed)
- feat: Seek diagnostic logging in Dev tab (Filtered/Relayed with delta)
- feat: Log buffer increased from 50 to 200 entries
2026-04-25 17:47:59 +02:00
Timo c2857dbdda feat: Improve seek logging and increase log buffer to 200 entries
- Log [Seek] Filtered when delta < 3s threshold (warn level) showing exact delta
- Log [Seek] Relayed when a seek passes all filters (info level) showing target time + delta
- Programmatic seeks (force sync, peer commands) remain silent in logs
- Increase log ring buffer from 50 -> 200 entries in all three enforcement points
2026-04-25 17:45:06 +02:00
Timo d07bf745a3 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
2026-04-25 17:42:55 +02:00
4 changed files with 54 additions and 6 deletions
+3 -3
View File
@@ -36,7 +36,7 @@ function ensureState() {
if (data.currentTabTitle !== undefined) currentTabTitle = data.currentTabTitle;
// Merge data from storage with any early-arriving state
// New entries (added during boot) must stay at the top (index 0)
if (data.logs) logs = [...logs, ...data.logs].slice(0, 50);
if (data.logs) logs = [...logs, ...data.logs].slice(0, 200);
if (data.history) history = [...history, ...data.history].slice(0, 20);
if (data.currentRoom) currentRoom = data.currentRoom;
if (data.lastActionState) lastActionState = data.lastActionState;
@@ -82,7 +82,7 @@ function ensureState() {
// Process any early logs/history that weren't captured in the spread
if (pendingLogs.length > 0) {
logs = [...pendingLogs, ...logs].slice(0, 50);
logs = [...pendingLogs, ...logs].slice(0, 200);
chrome.storage.session.set({ logs });
pendingLogs = [];
}
@@ -169,7 +169,7 @@ function addLog(message, type = 'info') {
pendingLogs.unshift(log);
} else {
logs.unshift(log);
if (logs.length > 50) logs.pop();
if (logs.length > 200) logs.pop();
chrome.storage.session.set({ logs });
}
chrome.runtime.sendMessage({ type: 'LOG_UPDATE', log }).catch(() => {});
+49 -1
View File
@@ -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,48 @@
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
// No log — this is routine programmatic behavior (Force Sync, lobby, peer command)
return;
}
const delta = lastReportedSeekTime !== null ? Math.abs(current - lastReportedSeekTime) : null;
const deltaStr = delta !== null ? `Δ${delta.toFixed(2)}s` : 'Δ?';
// Step 2: Delta check — skip micro-seeks (buffering, chapter markers, etc.)
if (lastReportedSeekTime !== null && delta < MIN_SEEK_DELTA) {
reportLog(`[Seek] Filtered (${deltaStr} < ${MIN_SEEK_DELTA}s threshold) @ ${current.toFixed(2)}s — not relayed`, 'warn');
return;
}
// 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;
const finalDelta = lastReportedSeekTime !== null ? Math.abs(settled - lastReportedSeekTime) : null;
const finalDeltaStr = finalDelta !== null ? `Δ${finalDelta.toFixed(2)}s` : 'Δ?';
lastReportedSeekTime = settled;
reportLog(`[Seek] Relayed @ ${settled.toFixed(2)}s (${finalDeltaStr})`, 'info');
reportEvent(EVENTS.SEEK);
}, 800);
};
let lastVideoSrc = null;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "KoalaSync",
"version": "1.2.0",
"version": "1.2.1",
"description": "Synchronize video playback across different tabs and users.",
"permissions": [
"storage",
+1 -1
View File
@@ -7,7 +7,7 @@
*/
export const PROTOCOL_VERSION = "1.0.0";
export const APP_VERSION = "1.2.0";
export const APP_VERSION = "1.2.1";
export const OFFICIAL_SERVER_URL = 'wss://sync.shik3i.net';
export const OFFICIAL_LANDING_PAGE_URL = 'https://koalasync.shik3i.net';