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); _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) { function isNetflixUrl(url) {
try { try {
const host = new URL(url).hostname.toLowerCase(); const host = new URL(url).hostname.toLowerCase();
@@ -2056,6 +2112,16 @@ async function handleAsyncMessage(message, sender, sendResponse) {
sendResponse(res); 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') { } else if (message.type === 'CONTENT_EVENT') {
const processEvent = async () => { const processEvent = async () => {
// Host Control Mode (sender-side): a non-controller in host-only mode must // 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-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-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 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> </div>
<!-- Room Tab --> <!-- Room Tab -->
@@ -692,6 +693,14 @@
</div> </div>
</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> <script src="popup.js" type="module"></script>
<!-- Onboarding Overlay --> <!-- Onboarding Overlay -->
+40 -3
View File
@@ -71,7 +71,10 @@ const elements = {
settingsVersion: document.getElementById('settingsVersion'), settingsVersion: document.getElementById('settingsVersion'),
devSupportLink: document.getElementById('devSupportLink'), devSupportLink: document.getElementById('devSupportLink'),
devReviewLink: document.getElementById('devReviewLink'), 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; let localPeerId = null;
@@ -88,6 +91,19 @@ let errorToken = 0;
let forceSyncDone = false; let forceSyncDone = false;
let connectionErrorTimer = null; let connectionErrorTimer = null;
let pendingConnectionErrorMsg = 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 roomListRefreshTimer = null;
let roomListRefreshInterval = null; let roomListRefreshInterval = null;
const ROOM_LIST_REFRESH_COOLDOWN_MS = 11000; const ROOM_LIST_REFRESH_COOLDOWN_MS = 11000;
@@ -210,6 +226,7 @@ async function init() {
elements.roomId.value = localData.roomId || ''; elements.roomId.value = localData.roomId || '';
elements.password.value = localData.password || ''; elements.password.value = localData.password || '';
elements.username.value = username; elements.username.value = username;
syncDevToolsVisibility();
if (elements.filterNoise) elements.filterNoise.checked = localData.filterNoise !== false; if (elements.filterNoise) elements.filterNoise.checked = localData.filterNoise !== false;
if (elements.autoSyncNextEpisode) elements.autoSyncNextEpisode.checked = localData.autoSyncNextEpisode !== 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; 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"]'); const syncTabBtn = document.querySelector('.tab-btn[data-tab="tab-sync"]');
if (syncTabBtn) syncTabBtn.click(); if (syncTabBtn) syncTabBtn.click();
showSelectVideoHint(); showSelectVideoHint();
} else if (localData.activeTab) { } else if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`); const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
if (btn) btn.click(); if (btn) btn.click();
} }
} else { } else {
await populateTabs(); await populateTabs();
if (localData.activeTab) { if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`); const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
if (btn) btn.click(); if (btn) btn.click();
} }
@@ -1275,8 +1292,10 @@ elements.serverUrl.addEventListener('input', () => {
chrome.storage.local.set({ serverUrl: elements.serverUrl.value }); chrome.storage.local.set({ serverUrl: elements.serverUrl.value });
}); });
elements.username.addEventListener('input', syncDevToolsVisibility);
elements.username.addEventListener('change', () => { elements.username.addEventListener('change', () => {
chrome.storage.local.set({ username: elements.username.value }); chrome.storage.local.set({ username: elements.username.value });
syncDevToolsVisibility();
}); });
if (elements.langSelector) { if (elements.langSelector) {
@@ -1689,6 +1708,24 @@ elements.pauseBtn.addEventListener('click', () => {
}, 2500); }, 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', () => { elements.clearLogs.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' }, () => { chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' }, () => {
elements.logList.innerHTML = ''; elements.logList.innerHTML = '';