mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-30 12:29:27 +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 { sameEpisode } from './episode-utils.js';
|
||||||
import { applyTitlePrivacyToPayload, sanitizeSharedTitle, sanitizeTabTitle, normalizeSendTabTitle, normalizeTitlePrivacyMode } from './title-privacy.js';
|
import { applyTitlePrivacyToPayload, sanitizeSharedTitle, sanitizeTabTitle, normalizeSendTabTitle, normalizeTitlePrivacyMode } from './title-privacy.js';
|
||||||
import { initTabManager } from './modules/tab-manager.js';
|
import { initTabManager } from './modules/tab-manager.js';
|
||||||
|
import './page-api-seek-overrides.js';
|
||||||
|
|
||||||
// --- Uninstall URL Initialization ---
|
// --- Uninstall URL Initialization ---
|
||||||
let uninstallURLInitPromise = null;
|
let uninstallURLInitPromise = null;
|
||||||
@@ -1648,62 +1649,87 @@ async function simulateRemoteSeek(delta) {
|
|||||||
return { status: 'ok', targetTime };
|
return { status: 'ok', targetTime };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNetflixUrl(url) {
|
async function devRemoteToolsAllowed() {
|
||||||
try {
|
const data = await chrome.storage.local.get(['username']);
|
||||||
const host = new URL(url).hostname.toLowerCase();
|
return data.username === 'KoalaDev';
|
||||||
return host === 'netflix.com' || host.endsWith('.netflix.com');
|
|
||||||
} catch (_e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function installNetflixSeekBridge() {
|
function shouldUsePageApiSeek(url) {
|
||||||
if (window.__koalaNetflixSeekBridgeInstalled) return;
|
return typeof globalThis.koalaFindPageApiSeekProvider === 'function' &&
|
||||||
window.__koalaNetflixSeekBridgeInstalled = true;
|
!!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 {
|
try {
|
||||||
const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
|
if (match.provider === 'netflix') {
|
||||||
const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
|
const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
|
||||||
return sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
|
const sessionId = videoPlayer?.getAllPlayerSessionIds?.()[0];
|
||||||
|
const player = sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
|
||||||
|
player?.seek(Math.round(time * 1000));
|
||||||
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return null;
|
// Player not ready or private API changed; the next sync tick can retry.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.source !== window) return;
|
if (event.source !== window) return;
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
if (!data || data.__koalaNetflixSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
|
if (!data || data.__koalaPageApiSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
|
||||||
try {
|
seekWithPageApi(data.time);
|
||||||
getNetflixPlayer()?.seek(Math.round(data.time * 1000));
|
|
||||||
} catch (_e) {
|
|
||||||
// Netflix player not ready or private API changed; the next sync tick can retry.
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPageApiSeekEnabled(enabled) {
|
||||||
|
window.KOALA_PAGE_API_SEEK_ENABLED = enabled === true;
|
||||||
|
}
|
||||||
|
|
||||||
async function injectContentScript(tabId) {
|
async function injectContentScript(tabId) {
|
||||||
let isNetflix = false;
|
let needsPageApiSeek = false;
|
||||||
|
let pageApiSeekReady = false;
|
||||||
try {
|
try {
|
||||||
const tab = await chrome.tabs.get(tabId);
|
const tab = await chrome.tabs.get(tabId);
|
||||||
isNetflix = isNetflixUrl(tab?.url || '');
|
needsPageApiSeek = shouldUsePageApiSeek(tab?.url || '');
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
// Fall through to the generic content script injection.
|
// Fall through to the generic content script injection.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNetflix) {
|
if (needsPageApiSeek) {
|
||||||
try {
|
try {
|
||||||
await chrome.scripting.executeScript({
|
await chrome.scripting.executeScript({
|
||||||
target: { tabId },
|
target: { tabId },
|
||||||
world: 'MAIN',
|
world: 'MAIN',
|
||||||
func: installNetflixSeekBridge
|
files: ['page-api-seek-overrides.js']
|
||||||
});
|
});
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
world: 'MAIN',
|
||||||
|
func: installPageApiSeekBridge
|
||||||
|
});
|
||||||
|
pageApiSeekReady = true;
|
||||||
} catch (err) {
|
} 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({
|
return chrome.scripting.executeScript({
|
||||||
target: { tabId },
|
target: { tabId },
|
||||||
files: ['content.js']
|
files: ['content.js']
|
||||||
@@ -2113,6 +2139,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
|
} else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
|
||||||
|
if (!(await devRemoteToolsAllowed())) {
|
||||||
|
sendResponse({ status: 'forbidden' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const delta = Number(message.delta);
|
const delta = Number(message.delta);
|
||||||
if (!Number.isFinite(delta)) {
|
if (!Number.isFinite(delta)) {
|
||||||
sendResponse({ status: 'invalid_delta' });
|
sendResponse({ status: 'invalid_delta' });
|
||||||
|
|||||||
@@ -57,17 +57,18 @@
|
|||||||
let seekDebounceTimer = null; // debounce timer for rapid seek events
|
let seekDebounceTimer = null; // debounce timer for rapid seek events
|
||||||
let expectedSeekTime = null; // strictly track programmatic seeks
|
let expectedSeekTime = null; // strictly track programmatic seeks
|
||||||
|
|
||||||
const NETFLIX_SEEK_BRIDGE = 1;
|
const PAGE_API_SEEK_BRIDGE = 1;
|
||||||
|
|
||||||
function isNetflixHost() {
|
function shouldUsePageApiSeek() {
|
||||||
const host = window.location.hostname.toLowerCase();
|
return window.KOALA_PAGE_API_SEEK_ENABLED === true &&
|
||||||
return host === 'netflix.com' || host.endsWith('.netflix.com');
|
typeof window.koalaFindPageApiSeekProvider === 'function' &&
|
||||||
|
!!window.koalaFindPageApiSeekProvider(window.location.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
function seekVideo(video, targetTime) {
|
function seekVideo(video, targetTime) {
|
||||||
expectedSeekTime = targetTime;
|
expectedSeekTime = targetTime;
|
||||||
if (isNetflixHost()) {
|
if (shouldUsePageApiSeek()) {
|
||||||
window.postMessage({ __koalaNetflixSeek: NETFLIX_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
|
window.postMessage({ __koalaPageApiSeek: PAGE_API_SEEK_BRIDGE, kind: 'seek', time: targetTime }, '*');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
video.currentTime = targetTime;
|
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