Harden room discovery and health metrics

This commit is contained in:
Koala
2026-06-03 10:48:49 +02:00
parent bf0fb5741f
commit 81c50eff16
16 changed files with 379 additions and 46 deletions
+13 -14
View File
@@ -80,27 +80,26 @@
// --- Helper: find the best video element on the page ---
// Prefers larger, visible videos over tiny preview/trailer elements.
function findVideo(root = document) {
const allVideos = root.querySelectorAll('video');
if (allVideos.length === 0) {
// Optimize: scan only potential player, video, media, and stream hosts by matching typical keywords (case-insensitive)
// or common custom element tags. This prevents recursive scanning of thousands of standard DOM nodes (div, span, a, etc.)
// while guaranteeing 100% airtight compatibility with all video web components in the wild.
const potentialHosts = root.querySelectorAll('[id*="player" i], [class*="player" i], [id*="video" i], [class*="video" i], [id*="media" i], [class*="media" i], [id*="stream" i], [class*="stream" i], ytd-player, netflix-player, emby-player, jellyfin-player, video-player');
for (const el of potentialHosts) {
if (el.shadowRoot) {
const found = findVideo(el.shadowRoot);
if (found) return found;
}
const candidates = Array.from(root.querySelectorAll('video'));
// Scan likely media hosts even when light-DOM videos exist; many players
// expose a tiny preview/ad video outside Shadow DOM and the real player inside.
const potentialHosts = root.querySelectorAll('[id*="player" i], [class*="player" i], [id*="video" i], [class*="video" i], [id*="media" i], [class*="media" i], [id*="stream" i], [class*="stream" i], ytd-player, netflix-player, emby-player, jellyfin-player, video-player');
for (const el of potentialHosts) {
if (el.shadowRoot) {
const found = findVideo(el.shadowRoot);
if (found) candidates.push(found);
}
return null;
}
if (candidates.length === 0) return null;
// Multiple videos found → pick the best one
if (allVideos.length === 1) return allVideos[0];
if (candidates.length === 1) return candidates[0];
let best = null;
let bestScore = -1;
for (const v of allVideos) {
for (const v of candidates) {
if (v.tagName !== 'VIDEO') continue;
// Score: visible area + bonus for unmuted + bonus for longer duration
const area = (v.videoWidth || v.offsetWidth || 0) * (v.videoHeight || v.offsetHeight || 0);
+85 -6
View File
@@ -66,21 +66,32 @@ let popupIntervals = [];
let populateTabsToken = null;
let errorToken = 0;
let forceSyncDone = false;
let connectionErrorTimer = null;
let pendingConnectionErrorMsg = null;
let roomListRefreshTimer = null;
const ROOM_LIST_REFRESH_COOLDOWN_MS = 11000;
// --- Helpers ---
function clearConnectionErrorTimer() {
if (connectionErrorTimer) {
clearTimeout(connectionErrorTimer);
connectionErrorTimer = null;
}
pendingConnectionErrorMsg = null;
}
// --- Initialization ---
async function init() {
// Load Settings
const data = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'filterNoise', 'username', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale']);
let activeLang = data.locale;
if (!activeLang) {
const systemLang = (navigator.language || chrome.i18n.getUILanguage()).split('-')[0];
activeLang = ['en', 'de', 'fr', 'es', 'pt', 'ru'].includes(systemLang) ? (systemLang === 'pt' ? 'pt-BR' : systemLang) : 'en';
chrome.storage.sync.set({ locale: activeLang });
}
await loadLocale(activeLang);
translateDOM();
@@ -148,8 +159,8 @@ async function init() {
// Check for invite link on landing page
checkInviteLink();
// Initial room list fetch
chrome.runtime.sendMessage({ type: 'GET_ROOM_LIST' });
// Initialize public rooms placeholder
renderEmpty(elements.publicRooms, 'rooms');
// Debug Info Refresh
popupIntervals.push(setInterval(refreshDebugInfo, 2000));
@@ -952,7 +963,12 @@ function showToast(message, type = 'info', duration = 3000) {
if (!container) return;
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.textContent = message || '';
toast.style.whiteSpace = 'pre-wrap';
const delay = Math.max(0, duration - 600);
toast.style.animation = `toastSlideIn 0.3s ease-out, toastFadeOut 0.3s ease-in ${delay}ms forwards`;
container.appendChild(toast);
setTimeout(() => toast.remove(), duration);
}
@@ -981,6 +997,7 @@ elements.roomId.addEventListener('input', () => {
});
elements.joinBtn.addEventListener('click', async () => {
clearConnectionErrorTimer();
if (elements.joinBtn.disabled) return;
const roomIdInput = elements.roomId.value.trim();
const isCreating = !roomIdInput;
@@ -1040,6 +1057,7 @@ elements.joinBtn.addEventListener('click', async () => {
});
elements.leaveBtn.addEventListener('click', async () => {
clearConnectionErrorTimer();
chrome.runtime.sendMessage({ type: 'LEAVE_ROOM' });
await chrome.storage.sync.set({ roomId: '', password: '' });
elements.roomId.value = '';
@@ -1080,6 +1098,13 @@ if (syncTabCreateRoomBtn) syncTabCreateRoomBtn.addEventListener('click', () => {
});
elements.refreshRooms.addEventListener('click', () => {
if (elements.refreshRooms.disabled) return;
elements.refreshRooms.disabled = true;
roomListRefreshTimer = setTimeout(() => {
elements.refreshRooms.disabled = false;
roomListRefreshTimer = null;
}, ROOM_LIST_REFRESH_COOLDOWN_MS);
elements.publicRooms.replaceChildren();
const el = document.createElement('div');
el.style.cssText = 'text-align:center; color: var(--text-muted); font-size: 11px; padding: 10px;';
@@ -1310,7 +1335,25 @@ chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'LOG_UPDATE') {
refreshLogs();
if (msg.log && msg.log.type === 'error') {
showError(msg.log.message);
const errMsg = msg.log.message || '';
const isConnErr = typeof errMsg === 'string' && (
errMsg.toLowerCase().includes('connection') ||
errMsg.toLowerCase().includes('websocket')
);
if (isConnErr) {
pendingConnectionErrorMsg = msg.log.message;
if (!connectionErrorTimer) {
connectionErrorTimer = setTimeout(() => {
if (pendingConnectionErrorMsg) {
showError(pendingConnectionErrorMsg);
}
connectionErrorTimer = null;
pendingConnectionErrorMsg = null;
}, 5000);
}
} else {
showError(msg.log.message);
}
}
} else if (msg.type === 'ACTION_UPDATE') {
const state = msg.state;
@@ -1366,6 +1409,9 @@ chrome.runtime.onMessage.addListener((msg) => {
updatePeerList(msg.peers);
if (msg.peers) detectPeerChanges(msg.peers);
} else if (msg.type === 'CONNECTION_STATUS') {
if (msg.status === 'connected') {
clearConnectionErrorTimer();
}
if (msg.status !== 'reconnecting') {
applyConnectionStatus(msg.status);
reconnectSlowMode = false;
@@ -1397,6 +1443,13 @@ chrome.runtime.onMessage.addListener((msg) => {
} else if (msg.type === 'ROOM_LIST') {
updateRoomList(msg.rooms);
} else if (msg.type === 'JOIN_STATUS') {
if (joinBtnTimeout) {
clearTimeout(joinBtnTimeout);
joinBtnTimeout = null;
}
elements.joinBtn.disabled = false;
elements.joinBtn.textContent = getMessage('BTN_JOIN_ROOM');
if (msg.success) {
// Final confirmation of join from background
chrome.storage.sync.get(['roomId', 'password', 'useCustomServer', 'serverUrl'], (data) => {
@@ -1589,6 +1642,28 @@ elements.copyLogs.addEventListener('click', () => {
const original = elements.copyLogs.textContent;
elements.copyLogs.textContent = getMessage('TOAST_LOGS_COPIED');
setTimeout(() => { elements.copyLogs.textContent = original; }, 2000);
// Construct rich multiline toast summary
const verStr = safe(status.version, '?');
const logsCount = logs.length;
const historyCount = history.length;
const peersCount = Array.isArray(status.peers) ? status.peers.length : 0;
let summary = `📋 Debug Report Copied!\n`;
summary += `• Version: v${verStr}\n`;
summary += `• System Logs: ${logsCount} entries\n`;
summary += `• Sync Actions: ${historyCount} captured\n`;
if (status.roomId) {
summary += `• Peers in Room: ${peersCount}\n`;
}
if (rawVideo && vs.found) {
const stateStr = vs.paused === true ? 'Paused' : (vs.paused === false ? 'Playing' : 'Unknown');
const timeStr = typeof vs.currentTime === 'number' ? `${Math.round(vs.currentTime)}s` : '?';
summary += `• Video: ${stateStr} at ${timeStr}\n`;
}
summary += `Paste it in markdown to view!`;
showToast(summary, 'success', 5000);
}).catch(() => {
showToast(getMessage('TOAST_COPY_FAILED'), 'error');
});
@@ -1753,6 +1828,10 @@ window.addEventListener('unload', () => {
clearTimeout(forceSyncResetTimer);
forceSyncResetTimer = null;
}
if (roomListRefreshTimer) {
clearTimeout(roomListRefreshTimer);
roomListRefreshTimer = null;
}
});
// --- Episode Lobby UI ---