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] 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 = '';