feat:local-blacklist-and-audio-boost

This commit is contained in:
Timo
2026-08-12 02:07:34 +02:00
parent 3d78af1e95
commit 48d6c1dc0b
28 changed files with 670 additions and 98 deletions
+90 -54
View File
@@ -250,6 +250,11 @@
!!window.koalaFindPageApiSeekProvider(window.location.hostname);
}
function seekVideo(video, targetTime) {
// Prefer a precise page-level seek API when available (Netflix, Disney+);
// for those players the DOM/button seek path is imprecise or impossible.
if (shouldUsePageApiSeek()) {
expectedSeekTime = targetTime;
window.postMessage({ __koalaPageApiSeek: PAGE_API_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
return;
}
@@ -605,24 +610,32 @@
if (ready || Date.now() >= deadline) {
hcmDeferredSnapPending = false;
hcmRequestHostSyncWithRetry(); // fresh host position + snap once
hcmDeferredSnapPending = false;
hcmRequestHostSyncWithRetry(); // fresh host position + snap once
return;
}
scheduleLifecycleTimeout(poll, 300);
};
};
poll();
}
// Entry point: background told us our local action was blocked in host-only.
function hcmHandleBlocked(action, target) {
// Entry point: background told us our local action was blocked in host-only.
// HOST_BLOCKED is only ever sent to a gated guest (background verifies
// host-only + !host before sending), so it's authoritative. Adopt the
// role/mode from it in case our CONTROL_MODE broadcast hasn't landed yet
// (join race, EC-5) — otherwise we'd miss the dialog/snap-back.
@@ -665,21 +678,31 @@
// deferred and "Stay in sync" paths).
if (target && Number.isFinite(target.targetTime)) hcmSnapBackToHost(target);
// usable, otherwise re-query+retry (host state may not be known yet)
// so we never leave the guest silently stuck (consistent with the
// deferred and "Stay in sync" paths).
if (target && Number.isFinite(target.targetTime)) hcmSnapBackToHost(target);
else hcmRequestHostSyncWithRetry();
} else {
hcmDeferredSnapBack(); // buffering → wait for ready, then snap once (#3)
else hcmRequestHostSyncWithRetry();
} else {
hcmDeferredSnapBack(); // buffering → wait for ready, then snap once (#3)
}
return;
}
// Deliberate: offer the choice (Teleparty-style), default = snap back.
hcmShowDesyncDialog(action, target);
}
// --- In-page UI (dialog + persistent desync badge) ---
// Built with the DOM API (CSSOM .style is CSP-safe; inline style="" in innerHTML
// is stripped by strict style-src on Netflix/YouTube/Disney+). Hosted in a
// Shadow DOM so the page's CSS can't restyle or hide our controls.
@@ -699,46 +722,59 @@
if (css) el.style.cssText = css; // CSSOM assignment — not gated by CSP
if (text != null) el.textContent = text;
// Shadow DOM so the page's CSS can't restyle or hide our controls.
let hcmDialogHost = null; // shadow host element for the dialog
return el;
}
function hcmRemoveDialog() {
// Cancel any pending auto-stay timer so a replaced dialog's stale closure
// can't later remove its successor / snap to an outdated target (H-4).
if (hcmDialogTimer) { clearTimeout(hcmDialogTimer); hcmDialogTimer = null; }
function hcmEl(tag, css, text) {
const el = document.createElement(tag);
if (css) el.style.cssText = css; // CSSOM assignment — not gated by CSP
if (hcmDialogHost) { hcmDialogHost.remove(); hcmDialogHost = null; }
}
function hcmShowDesyncDialog(action, target) {
if (!document.body) { hcmSnapBackToHost(target); return; }
hcmRemoveDialog();
}
function hcmRemoveDialog() {
// Cancel any pending auto-stay timer so a replaced dialog's stale closure
// can't later remove its successor / snap to an outdated target (H-4).
if (hcmDialogTimer) { clearTimeout(hcmDialogTimer); hcmDialogTimer = null; }
if (hcmDialogHost) { hcmDialogHost.remove(); hcmDialogHost = null; }
}
function hcmShowDesyncDialog(action, target) {
const host = hcmEl('div', 'all:initial');
const root = host.attachShadow({ mode: 'open' });
const wrap = hcmEl('div', 'position:fixed;z-index:2147483647;left:50%;bottom:32px;transform:translateX(-50%);background:#212a17;color:#f4f2ea;font:14px/1.4 system-ui,sans-serif;padding:16px 18px;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.45);max-width:360px;border:1px solid #2f3625');
wrap.setAttribute('role', 'dialog');
const title = hcmEl('div', 'font-weight:600;margin-bottom:6px', hcmStrings.title);
const body = hcmEl('div', 'margin-bottom:12px;color:#bcb7a9', hcmStrings.body);
const btnRow = hcmEl('div', 'display:flex;gap:8px;justify-content:flex-end');
const soloBtn = hcmEl('button', 'background:#2f3625;color:#f4f2ea;border:0;padding:8px 12px;border-radius:8px;cursor:pointer', hcmStrings.solo);
const stayBtn = hcmEl('button', 'background:#56ae6c;color:#0a1a0d;border:0;padding:8px 12px;border-radius:8px;cursor:pointer;font-weight:600', hcmStrings.stay);
btnRow.append(soloBtn, stayBtn);
wrap.append(title, body, btnRow);
root.appendChild(wrap);
document.body.appendChild(host);
hcmDialogHost = host;