mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
Generalize page API seek overrides
This commit is contained in:
+56
-26
@@ -4,6 +4,7 @@ import { loadLocale, getMessage, getSystemLanguage } from './i18n.js';
|
||||
import { sameEpisode } from './episode-utils.js';
|
||||
import { applyTitlePrivacyToPayload, sanitizeSharedTitle, sanitizeTabTitle, normalizeSendTabTitle, normalizeTitlePrivacyMode } from './title-privacy.js';
|
||||
import { initTabManager } from './modules/tab-manager.js';
|
||||
import './page-api-seek-overrides.js';
|
||||
|
||||
// --- Uninstall URL Initialization ---
|
||||
let uninstallURLInitPromise = null;
|
||||
@@ -1648,62 +1649,87 @@ async function simulateRemoteSeek(delta) {
|
||||
return { status: 'ok', targetTime };
|
||||
}
|
||||
|
||||
function isNetflixUrl(url) {
|
||||
try {
|
||||
const host = new URL(url).hostname.toLowerCase();
|
||||
return host === 'netflix.com' || host.endsWith('.netflix.com');
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
async function devRemoteToolsAllowed() {
|
||||
const data = await chrome.storage.local.get(['username']);
|
||||
return data.username === 'KoalaDev';
|
||||
}
|
||||
|
||||
function installNetflixSeekBridge() {
|
||||
if (window.__koalaNetflixSeekBridgeInstalled) return;
|
||||
window.__koalaNetflixSeekBridgeInstalled = true;
|
||||
function shouldUsePageApiSeek(url) {
|
||||
return typeof globalThis.koalaFindPageApiSeekProvider === 'function' &&
|
||||
!!globalThis.koalaFindPageApiSeekProvider(url);
|
||||
}
|
||||
|
||||
function installPageApiSeekBridge() {
|
||||
if (window.__koalaPageApiSeekBridgeInstalled) return;
|
||||
window.__koalaPageApiSeekBridgeInstalled = true;
|
||||
|
||||
function seekWithPageApi(time) {
|
||||
const match = typeof window.koalaFindPageApiSeekProvider === 'function'
|
||||
? window.koalaFindPageApiSeekProvider(window.location.hostname)
|
||||
: null;
|
||||
if (!match) return;
|
||||
|
||||
function getNetflixPlayer() {
|
||||
try {
|
||||
const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
|
||||
const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
|
||||
return sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
|
||||
if (match.provider === 'netflix') {
|
||||
const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
|
||||
const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
|
||||
const player = sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
|
||||
player?.seek(Math.round(time * 1000));
|
||||
}
|
||||
} catch (_e) {
|
||||
return null;
|
||||
// Player not ready or private API changed; the next sync tick can retry.
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== window) return;
|
||||
const data = event.data;
|
||||
if (!data || data.__koalaNetflixSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
|
||||
try {
|
||||
getNetflixPlayer()?.seek(Math.round(data.time * 1000));
|
||||
} catch (_e) {
|
||||
// Netflix player not ready or private API changed; the next sync tick can retry.
|
||||
}
|
||||
if (!data || data.__koalaPageApiSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
|
||||
seekWithPageApi(data.time);
|
||||
});
|
||||
}
|
||||
|
||||
function setPageApiSeekEnabled(enabled) {
|
||||
window.KOALA_PAGE_API_SEEK_ENABLED = enabled === true;
|
||||
}
|
||||
|
||||
async function injectContentScript(tabId) {
|
||||
let isNetflix = false;
|
||||
let needsPageApiSeek = false;
|
||||
let pageApiSeekReady = false;
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId);
|
||||
isNetflix = isNetflixUrl(tab?.url || '');
|
||||
needsPageApiSeek = shouldUsePageApiSeek(tab?.url || '');
|
||||
} catch (_e) {
|
||||
// Fall through to the generic content script injection.
|
||||
}
|
||||
|
||||
if (isNetflix) {
|
||||
if (needsPageApiSeek) {
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
world: 'MAIN',
|
||||
func: installNetflixSeekBridge
|
||||
files: ['page-api-seek-overrides.js']
|
||||
});
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
world: 'MAIN',
|
||||
func: installPageApiSeekBridge
|
||||
});
|
||||
pageApiSeekReady = true;
|
||||
} catch (err) {
|
||||
addLog(`Netflix seek bridge injection failed: ${err.message}`, 'warn');
|
||||
addLog(`Page API seek bridge injection failed: ${err.message}`, 'warn');
|
||||
}
|
||||
}
|
||||
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['page-api-seek-overrides.js']
|
||||
});
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
func: setPageApiSeekEnabled,
|
||||
args: [pageApiSeekReady]
|
||||
});
|
||||
return chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
@@ -2113,6 +2139,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
}
|
||||
});
|
||||
} else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
|
||||
if (!(await devRemoteToolsAllowed())) {
|
||||
sendResponse({ status: 'forbidden' });
|
||||
return;
|
||||
}
|
||||
const delta = Number(message.delta);
|
||||
if (!Number.isFinite(delta)) {
|
||||
sendResponse({ status: 'invalid_delta' });
|
||||
|
||||
@@ -57,17 +57,18 @@
|
||||
let seekDebounceTimer = null; // debounce timer for rapid seek events
|
||||
let expectedSeekTime = null; // strictly track programmatic seeks
|
||||
|
||||
const NETFLIX_SEEK_BRIDGE = 1;
|
||||
const PAGE_API_SEEK_BRIDGE = 1;
|
||||
|
||||
function isNetflixHost() {
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
return host === 'netflix.com' || host.endsWith('.netflix.com');
|
||||
function shouldUsePageApiSeek() {
|
||||
return window.KOALA_PAGE_API_SEEK_ENABLED === true &&
|
||||
typeof window.koalaFindPageApiSeekProvider === 'function' &&
|
||||
!!window.koalaFindPageApiSeekProvider(window.location.hostname);
|
||||
}
|
||||
|
||||
function seekVideo(video, targetTime) {
|
||||
expectedSeekTime = targetTime;
|
||||
if (isNetflixHost()) {
|
||||
window.postMessage({ __koalaNetflixSeek: NETFLIX_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
|
||||
if (shouldUsePageApiSeek()) {
|
||||
window.postMessage({ __koalaPageApiSeek: PAGE_API_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
|
||||
return;
|
||||
}
|
||||
video.currentTime = targetTime;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
(function(root) {
|
||||
const PAGE_API_SEEK_PROVIDERS = [
|
||||
{ domain: 'netflix.com', provider: 'netflix' } // Avoids M7375 when seeking via video.currentTime.
|
||||
];
|
||||
|
||||
function normalizeHost(input) {
|
||||
try {
|
||||
return new URL(input).hostname.toLowerCase();
|
||||
} catch (_e) {
|
||||
return String(input || '').toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function matchesDomain(host, domain) {
|
||||
return host === domain || host.endsWith(`.${domain}`);
|
||||
}
|
||||
|
||||
root.KOALA_PAGE_API_SEEK_PROVIDERS = PAGE_API_SEEK_PROVIDERS;
|
||||
root.koalaFindPageApiSeekProvider = (input) => {
|
||||
const host = normalizeHost(input);
|
||||
return PAGE_API_SEEK_PROVIDERS.find(entry => matchesDomain(host, entry.domain)) || null;
|
||||
};
|
||||
})(globalThis);
|
||||
Reference in New Issue
Block a user