From 8a21fbe07fa5a8bdfd00e7eeb434af0e33922d85 Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Thu, 2 Jul 2026 09:43:11 +0200
Subject: [PATCH 01/22] Fix Netflix seek handling
---
extension/background.js | 86 +++++++++++++++++++++++++++++++++++++----
extension/content.js | 28 ++++++++++----
extension/popup.js | 17 ++++----
3 files changed, 106 insertions(+), 25 deletions(-)
diff --git a/extension/background.js b/extension/background.js
index 2b5f601..dac9678 100644
--- a/extension/background.js
+++ b/extension/background.js
@@ -1592,6 +1592,68 @@ async function routeToContent(action, payload) {
_routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, 0);
}
+function isNetflixUrl(url) {
+ try {
+ const host = new URL(url).hostname.toLowerCase();
+ return host === 'netflix.com' || host.endsWith('.netflix.com');
+ } catch (_e) {
+ return false;
+ }
+}
+
+function installNetflixSeekBridge() {
+ if (window.__koalaNetflixSeekBridgeInstalled) return;
+ window.__koalaNetflixSeekBridgeInstalled = true;
+
+ function getNetflixPlayer() {
+ try {
+ const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
+ const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
+ return sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
+ } catch (_e) {
+ return null;
+ }
+ }
+
+ window.addEventListener('message', (event) => {
+ if (event.source !== window) return;
+ const data = event.data;
+ if (!data || data.__koalaNetflixSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
+ try {
+ getNetflixPlayer()?.seek(Math.round(data.time * 1000));
+ } catch (_e) {
+ // Netflix player not ready or private API changed; the next sync tick can retry.
+ }
+ });
+}
+
+async function injectContentScript(tabId) {
+ let isNetflix = false;
+ try {
+ const tab = await chrome.tabs.get(tabId);
+ isNetflix = isNetflixUrl(tab?.url || '');
+ } catch (_e) {
+ // Fall through to the generic content script injection.
+ }
+
+ if (isNetflix) {
+ try {
+ await chrome.scripting.executeScript({
+ target: { tabId },
+ world: 'MAIN',
+ func: installNetflixSeekBridge
+ });
+ } catch (err) {
+ addLog(`Netflix seek bridge injection failed: ${err.message}`, 'warn');
+ }
+ }
+
+ return chrome.scripting.executeScript({
+ target: { tabId },
+ files: ['content.js']
+ });
+}
+
function _routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, retries) {
chrome.tabs.sendMessage(tabId, {
type: 'SERVER_COMMAND',
@@ -1606,10 +1668,7 @@ function _routeToContentInternal(tabId, action, payload, actionTimestamp, comman
return;
}
if (err.message.includes('Receiving end does not exist') || err.message.includes('Extension context invalidated')) {
- chrome.scripting.executeScript({
- target: { tabId },
- files: ['content.js']
- }).then(() => {
+ injectContentScript(tabId).then(() => {
setTimeout(() => _routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, retries + 1), 500);
}).catch(_err => {
addLog(`Auto-reinject failed for tab ${tabId}`, 'warn');
@@ -2196,6 +2255,20 @@ async function handleAsyncMessage(message, sender, sendResponse) {
addLog('Heartbeat settings error: ' + err.message, 'error');
sendResponse({ status: 'ok' });
});
+ } else if (message.type === 'INJECT_CONTENT_SCRIPT') {
+ const tabId = Number(message.tabId);
+ if (!Number.isInteger(tabId)) {
+ sendResponse({ status: 'invalid_tab' });
+ return true;
+ }
+
+ injectContentScript(tabId).then(() => {
+ sendResponse({ status: 'ok' });
+ }).catch(err => {
+ addLog(`Failed to inject into tab: ${err.message}`, 'warn');
+ sendResponse({ status: 'error', message: err.message });
+ });
+ return true;
} else if (message.type === 'SET_TARGET_TAB') {
const previousTabId = currentTabId;
currentTabId = message.tabId;
@@ -2213,10 +2286,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
if (currentTabId) {
const selectedTabId = currentTabId;
- chrome.scripting.executeScript({
- target: { tabId: selectedTabId },
- files: ['content.js']
- })
+ injectContentScript(selectedTabId)
.then(() => applyAudioSettingsToTab(selectedTabId))
.catch(err => {
addLog(`Failed to inject into tab: ${err.message}`, 'warn');
diff --git a/extension/content.js b/extension/content.js
index a7ade14..01a491e 100644
--- a/extension/content.js
+++ b/extension/content.js
@@ -57,6 +57,22 @@
let seekDebounceTimer = null; // debounce timer for rapid seek events
let expectedSeekTime = null; // strictly track programmatic seeks
+ const NETFLIX_SEEK_BRIDGE = 1;
+
+ function isNetflixHost() {
+ const host = window.location.hostname.toLowerCase();
+ return host === 'netflix.com' || host.endsWith('.netflix.com');
+ }
+
+ function seekVideo(video, targetTime) {
+ expectedSeekTime = targetTime;
+ if (isNetflixHost()) {
+ window.postMessage({ __koalaNetflixSeek: NETFLIX_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
+ return;
+ }
+ video.currentTime = targetTime;
+ }
+
// --- Play/Pause Coalescing (leading + trailing) ---
// Media players (HLS/DASH, ad insertion, ABR/quality switches, source swaps,
// page teardown) fire bursts of native play/pause events within a few hundred
@@ -748,8 +764,7 @@
ytButton.click();
}
if (action === EVENTS.SEEK) {
- expectedSeekTime = data.targetTime;
- video.currentTime = data.targetTime;
+ seekVideo(video, data.targetTime);
}
return;
}
@@ -764,8 +779,7 @@
twitchButton.click();
}
if (action === EVENTS.SEEK) {
- expectedSeekTime = data.targetTime;
- video.currentTime = data.targetTime;
+ seekVideo(video, data.targetTime);
}
return;
}
@@ -782,8 +796,7 @@
_setSuppress('paused');
video.pause();
} else if (action === EVENTS.SEEK) {
- expectedSeekTime = data.targetTime;
- video.currentTime = data.targetTime;
+ seekVideo(video, data.targetTime);
}
} catch (e) {
reportLog(`Media Action Error: ${e.message}`, 'error');
@@ -931,10 +944,9 @@
return;
}
_setSuppress('paused');
- expectedSeekTime = payload.targetTime;
video.pause();
try {
- video.currentTime = payload.targetTime;
+ seekVideo(video, payload.targetTime);
} catch (e) {
reportLog(`Force Sync Seek Error: ${e.message}`, 'error');
}
diff --git a/extension/popup.js b/extension/popup.js
index a9e2952..7ecead6 100644
--- a/extension/popup.js
+++ b/extension/popup.js
@@ -1609,10 +1609,14 @@ elements.forceSyncBtn.addEventListener('click', async () => {
if (mode === 'jump-to-me') {
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (response) => {
if (chrome.runtime.lastError || !response || response.currentTime === undefined) {
- chrome.scripting.executeScript({
- target: { tabId },
- files: ['content.js']
- }).then(() => {
+ chrome.runtime.sendMessage({ type: 'INJECT_CONTENT_SCRIPT', tabId }, (injectResponse) => {
+ if (chrome.runtime.lastError || !injectResponse || injectResponse.status !== 'ok') {
+ showError(getMessage('ERR_NO_VIDEO_TAB'));
+ forceSyncDone = true;
+ elements.forceSyncBtn.disabled = false;
+ elements.forceSyncBtn.textContent = originalText;
+ return;
+ }
setTimeout(() => {
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (retryResponse) => {
if (chrome.runtime.lastError) return;
@@ -1621,11 +1625,6 @@ elements.forceSyncBtn.addEventListener('click', async () => {
}
});
}, 500);
- }).catch(() => {
- showError(getMessage('ERR_NO_VIDEO_TAB'));
- forceSyncDone = true;
- elements.forceSyncBtn.disabled = false;
- elements.forceSyncBtn.textContent = originalText;
});
return;
}
From 57cd071d58cce500daaae15eb738a3684b0fb50b Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Thu, 2 Jul 2026 09:50:39 +0200
Subject: [PATCH 02/22] Add hidden remote seek test controls
---
extension/background.js | 66 +++++++++++++++++++++++++++++++++++++++++
extension/popup.html | 9 ++++++
extension/popup.js | 43 +++++++++++++++++++++++++--
3 files changed, 115 insertions(+), 3 deletions(-)
diff --git a/extension/background.js b/extension/background.js
index dac9678..d46b479 100644
--- a/extension/background.js
+++ b/extension/background.js
@@ -1592,6 +1592,62 @@ async function routeToContent(action, payload) {
_routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, 0);
}
+function getTabVideoState(tabId) {
+ return new Promise((resolve) => {
+ chrome.tabs.sendMessage(tabId, { type: 'GET_VIDEO_STATE' }, (res) => {
+ if (chrome.runtime.lastError) {
+ resolve({ error: chrome.runtime.lastError.message });
+ return;
+ }
+ resolve(res);
+ });
+ });
+}
+
+async function getReadyTabVideoState(tabId) {
+ let state = await getTabVideoState(tabId);
+ if (!state || state.error) {
+ await injectContentScript(tabId);
+ await new Promise(resolve => setTimeout(resolve, 250));
+ state = await getTabVideoState(tabId);
+ }
+ return state;
+}
+
+async function simulateRemoteSeek(delta) {
+ if (!currentTabId) return { status: 'no_tab' };
+ const tabId = parseInt(currentTabId);
+ if (isNaN(tabId)) return { status: 'no_tab' };
+
+ const state = await getReadyTabVideoState(tabId);
+ if (!state || state.error) return { status: 'error', message: state?.error || 'No video state' };
+ if (!state.found || !Number.isFinite(state.currentTime)) return { status: 'no_video' };
+
+ let targetTime = Math.max(0, state.currentTime + delta);
+ if (Number.isFinite(state.duration) && state.duration > 0) {
+ targetTime = Math.min(targetTime, Math.max(0, state.duration - 0.1));
+ }
+
+ const senderId = 'KoalaDev';
+ const timestamp = Date.now();
+ const payload = {
+ senderId,
+ actionTimestamp: timestamp,
+ currentTime: targetTime,
+ targetTime
+ };
+
+ addToHistory(EVENTS.SEEK, senderId);
+ showNotification(senderId, EVENTS.SEEK);
+ updateLastAction(EVENTS.SEEK, senderId, timestamp);
+ lastActionState.targetTime = targetTime;
+ if (storageInitialized) chrome.storage.session.set({ lastActionState });
+ updateLocalPeerState(senderId, { currentTime: targetTime });
+ routeToContent(EVENTS.SEEK, payload);
+
+ return { status: 'ok', targetTime };
+}
+
function isNetflixUrl(url) {
try {
const host = new URL(url).hostname.toLowerCase();
@@ -2056,6 +2112,16 @@ async function handleAsyncMessage(message, sender, sendResponse) {
sendResponse(res);
}
});
+ } else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
+ const delta = Number(message.delta);
+ if (!Number.isFinite(delta)) {
+ sendResponse({ status: 'invalid_delta' });
+ return;
+ }
+ simulateRemoteSeek(delta).then(sendResponse).catch(err => {
+ addLog(`Remote seek simulation failed: ${err.message}`, 'warn');
+ sendResponse({ status: 'error', message: err.message });
+ });
} else if (message.type === 'CONTENT_EVENT') {
const processEvent = async () => {
// Host Control Mode (sender-side): a non-controller in host-only mode must
diff --git a/extension/popup.html b/extension/popup.html
index 23a2080..5df9ef9 100644
--- a/extension/popup.html
+++ b/extension/popup.html
@@ -391,6 +391,7 @@
+
@@ -692,6 +693,14 @@
+
+
+
+
+
+
+
+
diff --git a/extension/popup.js b/extension/popup.js
index 7ecead6..7ea0322 100644
--- a/extension/popup.js
+++ b/extension/popup.js
@@ -71,7 +71,10 @@ const elements = {
settingsVersion: document.getElementById('settingsVersion'),
devSupportLink: document.getElementById('devSupportLink'),
devReviewLink: document.getElementById('devReviewLink'),
- syncTabCopyInvite: document.getElementById('syncTabCopyInvite')
+ syncTabCopyInvite: document.getElementById('syncTabCopyInvite'),
+ devToolsTabBtn: document.getElementById('devToolsTabBtn'),
+ remoteSeekBack: document.getElementById('remoteSeekBack'),
+ remoteSeekForward: document.getElementById('remoteSeekForward')
};
let localPeerId = null;
@@ -88,6 +91,19 @@ let errorToken = 0;
let forceSyncDone = false;
let connectionErrorTimer = null;
let pendingConnectionErrorMsg = null;
+
+function devToolsEnabled() {
+ return elements.username && elements.username.value.trim() === 'KoalaDev';
+}
+
+function syncDevToolsVisibility() {
+ if (!elements.devToolsTabBtn) return;
+ const enabled = devToolsEnabled();
+ elements.devToolsTabBtn.style.display = enabled ? '' : 'none';
+ if (!enabled && document.getElementById('tab-devtools')?.classList.contains('active')) {
+ document.querySelector('.tab-btn[data-tab="tab-settings"]')?.click();
+ }
+}
let roomListRefreshTimer = null;
let roomListRefreshInterval = null;
const ROOM_LIST_REFRESH_COOLDOWN_MS = 11000;
@@ -210,6 +226,7 @@ async function init() {
elements.roomId.value = localData.roomId || '';
elements.password.value = localData.password || '';
elements.username.value = username;
+ syncDevToolsVisibility();
if (elements.filterNoise) elements.filterNoise.checked = localData.filterNoise !== false;
if (elements.autoSyncNextEpisode) elements.autoSyncNextEpisode.checked = localData.autoSyncNextEpisode !== false;
const legacyTitlePrivacyMode = Object.values(TITLE_PRIVACY_MODES).includes(localData.titlePrivacyMode) ? localData.titlePrivacyMode : TITLE_PRIVACY_MODES.FULL;
@@ -277,13 +294,13 @@ async function init() {
const syncTabBtn = document.querySelector('.tab-btn[data-tab="tab-sync"]');
if (syncTabBtn) syncTabBtn.click();
showSelectVideoHint();
- } else if (localData.activeTab) {
+ } else if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
if (btn) btn.click();
}
} else {
await populateTabs();
- if (localData.activeTab) {
+ if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
if (btn) btn.click();
}
@@ -1275,8 +1292,10 @@ elements.serverUrl.addEventListener('input', () => {
chrome.storage.local.set({ serverUrl: elements.serverUrl.value });
});
+elements.username.addEventListener('input', syncDevToolsVisibility);
elements.username.addEventListener('change', () => {
chrome.storage.local.set({ username: elements.username.value });
+ syncDevToolsVisibility();
});
if (elements.langSelector) {
@@ -1689,6 +1708,24 @@ elements.pauseBtn.addEventListener('click', () => {
}, 2500);
});
+function simulateRemoteSeek(delta) {
+ chrome.runtime.sendMessage({ type: 'DEV_SIMULATE_REMOTE_SEEK', delta }, (res) => {
+ if (chrome.runtime.lastError || !res || res.status !== 'ok') {
+ showToast(res?.message || 'Remote seek failed', 'error');
+ return;
+ }
+ showToast(`Remote seek -> ${formatTime(res.targetTime)}`, 'success', 1200);
+ refreshDebugInfo();
+ });
+}
+
+if (elements.remoteSeekBack) {
+ elements.remoteSeekBack.addEventListener('click', () => simulateRemoteSeek(-30));
+}
+if (elements.remoteSeekForward) {
+ elements.remoteSeekForward.addEventListener('click', () => simulateRemoteSeek(30));
+}
+
elements.clearLogs.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' }, () => {
elements.logList.innerHTML = '';
From 52265e84eb9d1f55513f6d64d57ba6c67ff902f7 Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Thu, 2 Jul 2026 09:57:57 +0200
Subject: [PATCH 03/22] Generalize page API seek overrides
---
extension/background.js | 82 +++++++++++++++++++---------
extension/content.js | 13 +++--
extension/page-api-seek-overrides.js | 23 ++++++++
3 files changed, 86 insertions(+), 32 deletions(-)
create mode 100644 extension/page-api-seek-overrides.js
diff --git a/extension/background.js b/extension/background.js
index d46b479..b346e71 100644
--- a/extension/background.js
+++ b/extension/background.js
@@ -4,6 +4,7 @@ import { loadLocale, getMessage, getSystemLanguage } from './i18n.js';
import { sameEpisode } from './episode-utils.js';
import { applyTitlePrivacyToPayload, sanitizeSharedTitle, sanitizeTabTitle, normalizeSendTabTitle, normalizeTitlePrivacyMode } from './title-privacy.js';
import { initTabManager } from './modules/tab-manager.js';
+import './page-api-seek-overrides.js';
// --- Uninstall URL Initialization ---
let uninstallURLInitPromise = null;
@@ -1648,62 +1649,87 @@ async function simulateRemoteSeek(delta) {
return { status: 'ok', targetTime };
}
-function isNetflixUrl(url) {
- try {
- const host = new URL(url).hostname.toLowerCase();
- return host === 'netflix.com' || host.endsWith('.netflix.com');
- } catch (_e) {
- return false;
- }
+async function devRemoteToolsAllowed() {
+ const data = await chrome.storage.local.get(['username']);
+ return data.username === 'KoalaDev';
}
-function installNetflixSeekBridge() {
- if (window.__koalaNetflixSeekBridgeInstalled) return;
- window.__koalaNetflixSeekBridgeInstalled = true;
+function shouldUsePageApiSeek(url) {
+ return typeof globalThis.koalaFindPageApiSeekProvider === 'function' &&
+ !!globalThis.koalaFindPageApiSeekProvider(url);
+}
+
+function installPageApiSeekBridge() {
+ if (window.__koalaPageApiSeekBridgeInstalled) return;
+ window.__koalaPageApiSeekBridgeInstalled = true;
+
+ function seekWithPageApi(time) {
+ const match = typeof window.koalaFindPageApiSeekProvider === 'function'
+ ? window.koalaFindPageApiSeekProvider(window.location.hostname)
+ : null;
+ if (!match) return;
- function getNetflixPlayer() {
try {
- const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
- const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
- return sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
+ if (match.provider === 'netflix') {
+ const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
+ const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
+ const player = sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
+ player?.seek(Math.round(time * 1000));
+ }
} catch (_e) {
- return null;
+ // Player not ready or private API changed; the next sync tick can retry.
}
}
window.addEventListener('message', (event) => {
if (event.source !== window) return;
const data = event.data;
- if (!data || data.__koalaNetflixSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
- try {
- getNetflixPlayer()?.seek(Math.round(data.time * 1000));
- } catch (_e) {
- // Netflix player not ready or private API changed; the next sync tick can retry.
- }
+ if (!data || data.__koalaPageApiSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
+ seekWithPageApi(data.time);
});
}
+function setPageApiSeekEnabled(enabled) {
+ window.KOALA_PAGE_API_SEEK_ENABLED = enabled === true;
+}
+
async function injectContentScript(tabId) {
- let isNetflix = false;
+ let needsPageApiSeek = false;
+ let pageApiSeekReady = false;
try {
const tab = await chrome.tabs.get(tabId);
- isNetflix = isNetflixUrl(tab?.url || '');
+ needsPageApiSeek = shouldUsePageApiSeek(tab?.url || '');
} catch (_e) {
// Fall through to the generic content script injection.
}
- if (isNetflix) {
+ if (needsPageApiSeek) {
try {
await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
- func: installNetflixSeekBridge
+ files: ['page-api-seek-overrides.js']
});
+ await chrome.scripting.executeScript({
+ target: { tabId },
+ world: 'MAIN',
+ func: installPageApiSeekBridge
+ });
+ pageApiSeekReady = true;
} catch (err) {
- addLog(`Netflix seek bridge injection failed: ${err.message}`, 'warn');
+ addLog(`Page API seek bridge injection failed: ${err.message}`, 'warn');
}
}
+ await chrome.scripting.executeScript({
+ target: { tabId },
+ files: ['page-api-seek-overrides.js']
+ });
+ await chrome.scripting.executeScript({
+ target: { tabId },
+ func: setPageApiSeekEnabled,
+ args: [pageApiSeekReady]
+ });
return chrome.scripting.executeScript({
target: { tabId },
files: ['content.js']
@@ -2113,6 +2139,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
}
});
} else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
+ if (!(await devRemoteToolsAllowed())) {
+ sendResponse({ status: 'forbidden' });
+ return;
+ }
const delta = Number(message.delta);
if (!Number.isFinite(delta)) {
sendResponse({ status: 'invalid_delta' });
diff --git a/extension/content.js b/extension/content.js
index 01a491e..ef59983 100644
--- a/extension/content.js
+++ b/extension/content.js
@@ -57,17 +57,18 @@
let seekDebounceTimer = null; // debounce timer for rapid seek events
let expectedSeekTime = null; // strictly track programmatic seeks
- const NETFLIX_SEEK_BRIDGE = 1;
+ const PAGE_API_SEEK_BRIDGE = 1;
- function isNetflixHost() {
- const host = window.location.hostname.toLowerCase();
- return host === 'netflix.com' || host.endsWith('.netflix.com');
+ function shouldUsePageApiSeek() {
+ return window.KOALA_PAGE_API_SEEK_ENABLED === true &&
+ typeof window.koalaFindPageApiSeekProvider === 'function' &&
+ !!window.koalaFindPageApiSeekProvider(window.location.hostname);
}
function seekVideo(video, targetTime) {
expectedSeekTime = targetTime;
- if (isNetflixHost()) {
- window.postMessage({ __koalaNetflixSeek: NETFLIX_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
+ if (shouldUsePageApiSeek()) {
+ window.postMessage({ __koalaPageApiSeek: PAGE_API_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
return;
}
video.currentTime = targetTime;
diff --git a/extension/page-api-seek-overrides.js b/extension/page-api-seek-overrides.js
new file mode 100644
index 0000000..58532ed
--- /dev/null
+++ b/extension/page-api-seek-overrides.js
@@ -0,0 +1,23 @@
+(function(root) {
+ const PAGE_API_SEEK_PROVIDERS = [
+ { domain: 'netflix.com', provider: 'netflix' } // Avoids M7375 when seeking via video.currentTime.
+ ];
+
+ function normalizeHost(input) {
+ try {
+ return new URL(input).hostname.toLowerCase();
+ } catch (_e) {
+ return String(input || '').toLowerCase();
+ }
+ }
+
+ function matchesDomain(host, domain) {
+ return host === domain || host.endsWith(`.${domain}`);
+ }
+
+ root.KOALA_PAGE_API_SEEK_PROVIDERS = PAGE_API_SEEK_PROVIDERS;
+ root.koalaFindPageApiSeekProvider = (input) => {
+ const host = normalizeHost(input);
+ return PAGE_API_SEEK_PROVIDERS.find(entry => matchesDomain(host, entry.domain)) || null;
+ };
+})(globalThis);
From 0af22998c8890e6d36028255d752f6a14e108d27 Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Thu, 2 Jul 2026 10:02:31 +0200
Subject: [PATCH 04/22] Handle target tab navigation reinjection
---
extension/background.js | 1 +
extension/modules/tab-manager.js | 6 ++----
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/extension/background.js b/extension/background.js
index b346e71..0c22ffa 100644
--- a/extension/background.js
+++ b/extension/background.js
@@ -2565,6 +2565,7 @@ initTabManager({
getSettings,
emit,
applyAudioSettingsToTab,
+ injectContentScript,
ensureState,
EVENTS
});
diff --git a/extension/modules/tab-manager.js b/extension/modules/tab-manager.js
index 063a7be..8fcbc22 100644
--- a/extension/modules/tab-manager.js
+++ b/extension/modules/tab-manager.js
@@ -12,6 +12,7 @@ export function initTabManager({
getSettings,
emit,
applyAudioSettingsToTab,
+ injectContentScript,
ensureState,
EVENTS
}) {
@@ -83,10 +84,7 @@ export function initTabManager({
await ensureState();
const curTabId = getCurrentTabId();
if (curTabId && tabId === parseInt(curTabId) && changeInfo.status === 'complete') {
- chrome.scripting.executeScript({
- target: { tabId },
- files: ['content.js']
- })
+ injectContentScript(tabId)
.then(() => applyAudioSettingsToTab(tabId))
.catch(() => {});
}
From 01762b0c2fff1f2702930cc548871751417cdfc8 Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Thu, 2 Jul 2026 10:20:25 +0200
Subject: [PATCH 05/22] Normalize shared tab titles
---
docs/ROADMAP.md | 17 ++++++++++++++++-
extension/popup.js | 4 ++--
extension/title-privacy.js | 8 +++++++-
scripts/test-title-privacy.mjs | 6 ++++++
4 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index d667bcf..d55cba6 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -60,6 +60,22 @@
- **Legal/moderation:** Unclear what moderation requirements would apply if users can exchange chat messages. Could be relevant depending on jurisdiction.
- **Status:** Under evaluation, may come later.
+### Cross-frame video detection and control
+
+- **Priority:** P3
+- **Category:** Compatibility / Embedded Players
+- **Background:** KoalaSync currently injects on demand into the selected tab's top frame. This works for normal top-frame players, including current Emby/Jellyfin usage, but does not cover cases where the real `