Add hidden remote seek test controls

This commit is contained in:
KoalaDev
2026-07-02 09:50:39 +02:00
parent 8a21fbe07f
commit 57cd071d58
3 changed files with 115 additions and 3 deletions
+66
View File
@@ -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
+9
View File
@@ -391,6 +391,7 @@
<button class="tab-btn" data-tab="tab-sync" data-i18n="TAB_SYNC" data-i18n-title="TAB_SYNC_TOOLTIP" title="Video sync controls and remote actions">Sync</button>
<button class="tab-btn" data-tab="tab-settings" data-i18n="TAB_SETTINGS" data-i18n-title="TAB_SETTINGS_TOOLTIP" title="Extension preferences">Settings</button>
<button class="tab-btn" data-tab="tab-dev" data-i18n="TAB_STATUS" data-i18n-title="TAB_STATUS_TOOLTIP" title="Advanced Diagnostics & Logs">Status</button>
<button id="devToolsTabBtn" class="tab-btn" data-tab="tab-devtools" style="display:none;">Dev</button>
</div>
<!-- Room Tab -->
@@ -692,6 +693,14 @@
</div>
</div>
<div id="tab-devtools" class="tab-content">
<label>Remote Seek</label>
<div class="info-card" style="display:flex; gap:8px; margin-bottom:15px;">
<button id="remoteSeekBack" class="secondary" style="flex:1; font-size:12px;">-30s</button>
<button id="remoteSeekForward" class="secondary" style="flex:1; font-size:12px;">+30s</button>
</div>
</div>
<script src="popup.js" type="module"></script>
<!-- Onboarding Overlay -->
+40 -3
View File
@@ -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 = '';