mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-09-04 15:05:20 +00:00
Save baseline state with Disney+ seek adjustments and build identifier
This commit is contained in:
+1813
-1538
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,10 @@
|
|||||||
(function(root) {
|
(function(root) {
|
||||||
const PAGE_API_SEEK_PROVIDERS = [
|
const PAGE_API_SEEK_FIXES = [
|
||||||
{ domain: 'netflix.com', provider: 'netflix' } // Avoids M7375 when seeking via video.currentTime.
|
{
|
||||||
|
name: 'netflix-page-api-seek',
|
||||||
|
urls: ['netflix.com'],
|
||||||
|
provider: 'netflix'
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function normalizeHost(input) {
|
function normalizeHost(input) {
|
||||||
@@ -12,12 +16,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchesDomain(host, domain) {
|
function matchesDomain(host, domain) {
|
||||||
return host === domain || host.endsWith(`.${domain}`);
|
const normalizedDomain = normalizeHost(domain);
|
||||||
|
return normalizedDomain && (host === normalizedDomain || host.endsWith(`.${normalizedDomain}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
root.KOALA_PAGE_API_SEEK_PROVIDERS = PAGE_API_SEEK_PROVIDERS;
|
root.KOALA_PAGE_API_SEEK_FIXES = PAGE_API_SEEK_FIXES;
|
||||||
|
root.KOALA_PAGE_API_SEEK_PROVIDERS = PAGE_API_SEEK_FIXES;
|
||||||
root.koalaFindPageApiSeekProvider = (input) => {
|
root.koalaFindPageApiSeekProvider = (input) => {
|
||||||
const host = normalizeHost(input);
|
const host = normalizeHost(input);
|
||||||
return PAGE_API_SEEK_PROVIDERS.find(entry => matchesDomain(host, entry.domain)) || null;
|
return PAGE_API_SEEK_FIXES.find(entry =>
|
||||||
|
Array.isArray(entry.urls) && entry.urls.some(url => matchesDomain(host, url))
|
||||||
|
) || null;
|
||||||
};
|
};
|
||||||
})(globalThis);
|
})(globalThis);
|
||||||
|
|||||||
+41
-5
@@ -74,7 +74,8 @@ const elements = {
|
|||||||
syncTabCopyInvite: document.getElementById('syncTabCopyInvite'),
|
syncTabCopyInvite: document.getElementById('syncTabCopyInvite'),
|
||||||
devToolsTabBtn: document.getElementById('devToolsTabBtn'),
|
devToolsTabBtn: document.getElementById('devToolsTabBtn'),
|
||||||
remoteSeekBack: document.getElementById('remoteSeekBack'),
|
remoteSeekBack: document.getElementById('remoteSeekBack'),
|
||||||
remoteSeekForward: document.getElementById('remoteSeekForward')
|
remoteSeekForward: document.getElementById('remoteSeekForward'),
|
||||||
|
remoteSeekFiveMin: document.getElementById('remoteSeekFiveMin')
|
||||||
};
|
};
|
||||||
|
|
||||||
let localPeerId = null;
|
let localPeerId = null;
|
||||||
@@ -1708,8 +1709,8 @@ elements.pauseBtn.addEventListener('click', () => {
|
|||||||
}, 2500);
|
}, 2500);
|
||||||
});
|
});
|
||||||
|
|
||||||
function simulateRemoteSeek(delta) {
|
function simulateRemoteSeek({ delta = null, targetTime = null }) {
|
||||||
chrome.runtime.sendMessage({ type: 'DEV_SIMULATE_REMOTE_SEEK', delta }, (res) => {
|
chrome.runtime.sendMessage({ type: 'DEV_SIMULATE_REMOTE_SEEK', delta, targetTime }, (res) => {
|
||||||
if (chrome.runtime.lastError || !res || res.status !== 'ok') {
|
if (chrome.runtime.lastError || !res || res.status !== 'ok') {
|
||||||
showToast(res?.message || 'Remote seek failed', 'error');
|
showToast(res?.message || 'Remote seek failed', 'error');
|
||||||
return;
|
return;
|
||||||
@@ -1720,10 +1721,13 @@ function simulateRemoteSeek(delta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (elements.remoteSeekBack) {
|
if (elements.remoteSeekBack) {
|
||||||
elements.remoteSeekBack.addEventListener('click', () => simulateRemoteSeek(-30));
|
elements.remoteSeekBack.addEventListener('click', () => simulateRemoteSeek({ delta: -30 }));
|
||||||
}
|
}
|
||||||
if (elements.remoteSeekForward) {
|
if (elements.remoteSeekForward) {
|
||||||
elements.remoteSeekForward.addEventListener('click', () => simulateRemoteSeek(30));
|
elements.remoteSeekForward.addEventListener('click', () => simulateRemoteSeek({ delta: 30 }));
|
||||||
|
}
|
||||||
|
if (elements.remoteSeekFiveMin) {
|
||||||
|
elements.remoteSeekFiveMin.addEventListener('click', () => simulateRemoteSeek({ targetTime: 300 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.clearLogs.addEventListener('click', () => {
|
elements.clearLogs.addEventListener('click', () => {
|
||||||
@@ -2083,6 +2087,26 @@ elements.copyLogs.addEventListener('click', () => {
|
|||||||
lines.push(`- **ReadyState:** ${readyOk ? '\u2705' : '\u26A0\uFE0F'} ${safe(vs.readyState, '?')} (${readyLabel})`);
|
lines.push(`- **ReadyState:** ${readyOk ? '\u2705' : '\u26A0\uFE0F'} ${safe(vs.readyState, '?')} (${readyLabel})`);
|
||||||
lines.push(`- **Network:** ${safe(vs.networkState, '?')} (${netLabel})`);
|
lines.push(`- **Network:** ${safe(vs.networkState, '?')} (${netLabel})`);
|
||||||
lines.push(`- **Buffered:** ${safe(vs.buffered, '?')}`);
|
lines.push(`- **Buffered:** ${safe(vs.buffered, '?')}`);
|
||||||
|
if (vs.nativeCurrentTime != null || vs.nativeDuration != null) {
|
||||||
|
lines.push(`- **Native Time:** ${safe(vs.nativeCurrentTime, '?')}s / ${safe(vs.nativeDuration, '?')}s`);
|
||||||
|
}
|
||||||
|
if (vs.siteQuirk) {
|
||||||
|
const quirk = vs.siteQuirk;
|
||||||
|
const label = quirk.name || quirk.key || 'site';
|
||||||
|
if (quirk.timeline) {
|
||||||
|
lines.push(`- **${label} Timeline:** ${safe(quirk.timeline.current, '?')}s / ${safe(quirk.timeline.duration, '?')}s`);
|
||||||
|
}
|
||||||
|
const candidates = Array.isArray(quirk.timelineCandidates) ? quirk.timelineCandidates : [];
|
||||||
|
if (candidates.length > 0) {
|
||||||
|
lines.push(`- **${label} Timeline Candidates:**`);
|
||||||
|
candidates.forEach(c => lines.push(` - ${safe(c.source, '?')}: ${safe(c.current, '?')}s / ${safe(c.duration, '?')}s`));
|
||||||
|
}
|
||||||
|
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons : [];
|
||||||
|
if (buttons.length > 0) {
|
||||||
|
lines.push(`- **${label} Buttons:**`);
|
||||||
|
buttons.forEach(label => lines.push(` - ${label}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
lines.push(`- **Dimensions:** ${safe(vs.videoWidth, '?')}x${safe(vs.videoHeight, '?')}${dimOk ? '' : ' \u26A0\uFE0F 0x0'}`);
|
lines.push(`- **Dimensions:** ${safe(vs.videoWidth, '?')}x${safe(vs.videoHeight, '?')}${dimOk ? '' : ' \u26A0\uFE0F 0x0'}`);
|
||||||
lines.push(`- **Muted:** ${safe(vs.muted, '?')} | **Volume:** ${safe(vs.volume, '?')} | **Speed:** ${safe(vs.playbackRate, '?')}x`);
|
lines.push(`- **Muted:** ${safe(vs.muted, '?')} | **Volume:** ${safe(vs.volume, '?')} | **Speed:** ${safe(vs.playbackRate, '?')}x`);
|
||||||
lines.push(`- **Seeking:** ${safe(vs.seeking, '?')} | **Ended:** ${safe(vs.ended, '?')} | **Loop:** ${safe(vs.loop, '?')}`);
|
lines.push(`- **Seeking:** ${safe(vs.seeking, '?')} | **Ended:** ${safe(vs.ended, '?')} | **Loop:** ${safe(vs.loop, '?')}`);
|
||||||
@@ -2256,6 +2280,18 @@ function refreshDebugInfo() {
|
|||||||
addField('Network', `${state.networkState} (${state.networkStateLabel || '?'})`,
|
addField('Network', `${state.networkState} (${state.networkStateLabel || '?'})`,
|
||||||
state.networkState === 1 ? '#22c55e' : state.networkState === 3 ? '#ef4444' : 'var(--text-muted)');
|
state.networkState === 1 ? '#22c55e' : state.networkState === 3 ? '#ef4444' : 'var(--text-muted)');
|
||||||
addField('Buffered', state.buffered || '?');
|
addField('Buffered', state.buffered || '?');
|
||||||
|
if (state.nativeCurrentTime != null || state.nativeDuration != null) {
|
||||||
|
addField('Native Time', `${state.nativeCurrentTime ?? '?'}s / ${state.nativeDuration ?? '?'}s`);
|
||||||
|
}
|
||||||
|
if (state.siteQuirk) {
|
||||||
|
const quirk = state.siteQuirk;
|
||||||
|
const label = quirk.name || quirk.key || 'Site';
|
||||||
|
if (quirk.timeline) {
|
||||||
|
addField(`${label} Timeline`, `${quirk.timeline.current ?? '?'}s / ${quirk.timeline.duration ?? '?'}s`);
|
||||||
|
}
|
||||||
|
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons.slice(0, 4).join(' | ') : '';
|
||||||
|
if (buttons) addField(`${label} Buttons`, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
addSection('Properties');
|
addSection('Properties');
|
||||||
addField('Seeking', String(state.seeking));
|
addField('Seeking', String(state.seeking));
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ function extractFunction(name, text) {
|
|||||||
throw new Error(`${name} body did not terminate`);
|
throw new Error(`${name} body did not terminate`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeSeekable(ranges = []) {
|
||||||
|
return {
|
||||||
|
length: ranges.length,
|
||||||
|
start(i) { return ranges[i][0]; },
|
||||||
|
end(i) { return ranges[i][1]; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function makeVideo(name, width, height, options = {}) {
|
function makeVideo(name, width, height, options = {}) {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -28,7 +36,9 @@ function makeVideo(name, width, height, options = {}) {
|
|||||||
offsetWidth: width,
|
offsetWidth: width,
|
||||||
offsetHeight: height,
|
offsetHeight: height,
|
||||||
muted: options.muted ?? true,
|
muted: options.muted ?? true,
|
||||||
duration: options.duration ?? 0
|
duration: options.duration ?? 0,
|
||||||
|
currentTime: options.currentTime ?? 0,
|
||||||
|
seekable: options.seekable ?? makeSeekable()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,4 +71,100 @@ assert.strictEqual(
|
|||||||
'findVideo should score Shadow DOM videos together with light DOM videos'
|
'findVideo should score Shadow DOM videos together with light DOM videos'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function makeDocument(nodes = []) {
|
||||||
|
return {
|
||||||
|
querySelectorAll() { return nodes; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeNode(attrs = {}, textContent = '') {
|
||||||
|
return {
|
||||||
|
textContent,
|
||||||
|
value: attrs.value,
|
||||||
|
max: attrs.max,
|
||||||
|
clicked: 0,
|
||||||
|
click() { this.clicked += 1; },
|
||||||
|
getAttribute(name) { return attrs[name] ?? null; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTimelineFns(hostname, document = makeDocument()) {
|
||||||
|
return Function('window', 'document', [
|
||||||
|
'let lastDisneyPlusTimelineCandidates = [];',
|
||||||
|
extractFunction('hostMatchesUrl', source),
|
||||||
|
extractFunction('matchesPlayerUrls', source),
|
||||||
|
extractFunction('isDisneyPlusHost', source),
|
||||||
|
extractFunction('getSeekableRange', source),
|
||||||
|
extractFunction('parseClockTime', source),
|
||||||
|
extractFunction('parseTimelineText', source),
|
||||||
|
extractFunction('getDisneyPlusUiTimeline', source),
|
||||||
|
extractFunction('getElementLabel', source),
|
||||||
|
extractFunction('getDisneyPlusSeekButtonLabels', source),
|
||||||
|
extractFunction('clickDisneyPlusRelativeSeek', source),
|
||||||
|
extractFunction('getDisneyPlusTimeline', source),
|
||||||
|
extractFunction('getSiteQuirkAdapters', source),
|
||||||
|
extractFunction('getActiveSiteQuirk', source),
|
||||||
|
extractFunction('getSiteQuirkTimeline', source),
|
||||||
|
extractFunction('getSiteQuirkDebug', source),
|
||||||
|
extractFunction('getSyncCurrentTime', source),
|
||||||
|
extractFunction('getSyncDuration', source),
|
||||||
|
extractFunction('toNativeSeekTime', source),
|
||||||
|
'return { getActiveSiteQuirk, getSyncCurrentTime, getSyncDuration, toNativeSeekTime, clickDisneyPlusRelativeSeek };'
|
||||||
|
].join('\n'))({ location: { hostname } }, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPlayerFixFns(hostname) {
|
||||||
|
return Function('window', [
|
||||||
|
extractFunction('hostMatchesUrl', source),
|
||||||
|
extractFunction('matchesPlayerUrls', source),
|
||||||
|
extractFunction('getPlayerActionFixes', source),
|
||||||
|
extractFunction('getActivePlayerActionFix', source),
|
||||||
|
'return { getPlayerActionFixes, getActivePlayerActionFix };'
|
||||||
|
].join('\n'))({ location: { hostname } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const disneyUiDocument = makeDocument([
|
||||||
|
makeNode({ 'aria-valuetext': '0:09 / 180:00' })
|
||||||
|
]);
|
||||||
|
const disneyFns = loadTimelineFns('www.disneyplus.com', disneyUiDocument);
|
||||||
|
assert.equal(disneyFns.getActiveSiteQuirk().name, 'disneyplus-timeline-and-buttons');
|
||||||
|
assert.deepEqual(disneyFns.getActiveSiteQuirk().urls, ['disneyplus.com']);
|
||||||
|
const disneyVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||||
|
currentTime: 29,
|
||||||
|
duration: 0,
|
||||||
|
seekable: makeSeekable([[0, 32400]])
|
||||||
|
});
|
||||||
|
assert.equal(disneyFns.getSyncCurrentTime(disneyVideo), 9);
|
||||||
|
assert.equal(disneyFns.getSyncDuration(disneyVideo), 10800);
|
||||||
|
assert.equal(disneyFns.toNativeSeekTime(disneyVideo, 39), 119);
|
||||||
|
|
||||||
|
const disneySeekableFallbackFns = loadTimelineFns('www.disneyplus.com');
|
||||||
|
const disneyOffsetVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||||
|
currentTime: 29,
|
||||||
|
duration: 0,
|
||||||
|
seekable: makeSeekable([[20, 10820]])
|
||||||
|
});
|
||||||
|
assert.equal(disneySeekableFallbackFns.getSyncCurrentTime(disneyOffsetVideo), 9);
|
||||||
|
assert.equal(disneySeekableFallbackFns.getSyncDuration(disneyOffsetVideo), 10800);
|
||||||
|
assert.equal(disneySeekableFallbackFns.toNativeSeekTime(disneyOffsetVideo, 39), 59);
|
||||||
|
|
||||||
|
const genericFns = loadTimelineFns('example.com');
|
||||||
|
assert.equal(genericFns.getActiveSiteQuirk(), null);
|
||||||
|
assert.equal(genericFns.getSyncCurrentTime(disneyVideo), 29);
|
||||||
|
assert.equal(genericFns.getSyncDuration(disneyVideo), 0);
|
||||||
|
assert.equal(genericFns.toNativeSeekTime(disneyVideo, 39), 39);
|
||||||
|
|
||||||
|
const backButton = makeNode({ 'aria-label': '10 Sekunden zurück' });
|
||||||
|
const forwardButton = makeNode({ 'aria-label': '10 Sekunden vorspulen' });
|
||||||
|
const disneyButtonFns = loadTimelineFns('www.disneyplus.com', makeDocument([backButton, forwardButton]));
|
||||||
|
assert.equal(disneyButtonFns.clickDisneyPlusRelativeSeek(-30), true);
|
||||||
|
assert.equal(disneyButtonFns.clickDisneyPlusRelativeSeek(30), true);
|
||||||
|
|
||||||
|
const twitchFixFns = loadPlayerFixFns('player.twitch.tv');
|
||||||
|
assert.equal(twitchFixFns.getActivePlayerActionFix().name, 'twitch-player-buttons');
|
||||||
|
assert.deepEqual(twitchFixFns.getActivePlayerActionFix().urls, ['twitch.tv']);
|
||||||
|
|
||||||
|
const genericFixFns = loadPlayerFixFns('example.com');
|
||||||
|
assert.equal(genericFixFns.getActivePlayerActionFix(), null);
|
||||||
|
|
||||||
console.log('content video finder tests passed');
|
console.log('content video finder tests passed');
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user