mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
Remove Disney+ DOM scraping fallback
This commit is contained in:
+31
-374
@@ -57,13 +57,7 @@
|
||||
};
|
||||
|
||||
// --- SHARED_EVENTS_INJECT_END ---
|
||||
|
||||
|
||||
|
||||
// Suppresses native event reporting after a programmatic action.
|
||||
|
||||
// Each entry is a per-type timer (key = 'playing'|'paused'|'seek').
|
||||
|
||||
|
||||
|
||||
|
||||
// Suppresses native event reporting after a programmatic action.
|
||||
@@ -96,284 +90,34 @@
|
||||
|
||||
if (_suppressTimers[state]) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Seek Relay Filtering ---
|
||||
|
||||
// Minimum seek delta (seconds) to report. Prevents HLS/DASH buffering micro-seeks
|
||||
|
||||
// from being relayed to peers as user-initiated seeks.
|
||||
|
||||
const MIN_SEEK_DELTA = 2.0;
|
||||
|
||||
let lastReportedSeekTime = null; // last currentTime we relayed as a SEEK
|
||||
|
||||
let seekDebounceTimer = null; // debounce timer for rapid seek events
|
||||
|
||||
let expectedSeekTime = null; // strictly track programmatic seeks
|
||||
|
||||
|
||||
|
||||
const PAGE_API_SEEK_BRIDGE = 1;
|
||||
let lastDisneyPlusTimelineCandidates = [];
|
||||
let lastKnownDisneyPlusDuration = 0;
|
||||
let lastKnownDisneyPlusScale = 1;
|
||||
let lastKnownDisneyPlusStart = 0;
|
||||
let lastDisneyPlusUiCurrent = null;
|
||||
let lastDisneyPlusNativeAtUi = null;
|
||||
// Accurate Disney+ playhead pushed by the MAIN-world page-API bridge
|
||||
// (background.js installPageApiSeekBridge). The isolated content world
|
||||
// can't read the page's media player directly.
|
||||
let disneyPageApiTime = null;
|
||||
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== window) return;
|
||||
const data = event.data;
|
||||
if (data && data.__koalaPlayerTime === 1 && data.provider === 'disney'
|
||||
&& Number.isFinite(data.position) && Number.isFinite(data.duration) && data.duration > 0) {
|
||||
disneyPageApiTime = { position: data.position, duration: data.duration, at: Date.now() };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hostMatchesUrl(host, url) {
|
||||
const normalized = String(url || '')
|
||||
.replace(/^https?:\/\//i, '')
|
||||
.split('/')[0]
|
||||
.toLowerCase();
|
||||
return normalized && (host === normalized || host.endsWith(`.${normalized}`));
|
||||
}
|
||||
|
||||
function matchesPlayerUrls(urls) {
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
return Array.isArray(urls) && urls.some(url => hostMatchesUrl(host, url));
|
||||
}
|
||||
|
||||
function isDisneyPlusHost() {
|
||||
return matchesPlayerUrls(['disneyplus.com']);
|
||||
}
|
||||
|
||||
function getSeekableRange(video) {
|
||||
try {
|
||||
const ranges = video.seekable;
|
||||
if (!ranges || ranges.length === 0) return null;
|
||||
const current = video.currentTime;
|
||||
let index = ranges.length - 1;
|
||||
if (Number.isFinite(current)) {
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
if (current >= ranges.start(i) && current <= ranges.end(i)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const start = ranges.start(index);
|
||||
const end = ranges.end(index);
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
|
||||
return { start, end, duration: end - start };
|
||||
} catch (_e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseClockTime(value) {
|
||||
const parts = String(value || '').trim().split(':').map(Number);
|
||||
if (parts.length < 2 || parts.length > 3 || parts.some(n => !Number.isFinite(n))) return null;
|
||||
return parts.length === 2
|
||||
? parts[0] * 60 + parts[1]
|
||||
: parts[0] * 3600 + parts[1] * 60 + parts[2];
|
||||
}
|
||||
|
||||
function parseTimelineText(text) {
|
||||
const matches = String(text || '').match(/\b\d{1,3}:\d{2}(?::\d{2})?\b/g);
|
||||
if (!matches || matches.length < 2) return null;
|
||||
const current = parseClockTime(matches[0]);
|
||||
const duration = parseClockTime(matches[matches.length - 1]);
|
||||
if (!Number.isFinite(current) || !Number.isFinite(duration) || duration < 60 || current > duration + 5) return null;
|
||||
return { current, duration };
|
||||
}
|
||||
|
||||
function getDisneyPlusUiTimeline() {
|
||||
if (typeof document === 'undefined') return null;
|
||||
const selectors = [
|
||||
'[role="slider"]',
|
||||
'[role="progressbar"]',
|
||||
'[aria-valuenow][aria-valuemax]',
|
||||
'[aria-valuetext]',
|
||||
'[aria-label]',
|
||||
'[data-testid]',
|
||||
'[data-test]',
|
||||
'[class*="time" i]',
|
||||
'[class*="progress" i]',
|
||||
'time',
|
||||
'output',
|
||||
'button',
|
||||
'span',
|
||||
'p',
|
||||
'div'
|
||||
].join(',');
|
||||
const nodes = querySelectorAllShadow(selectors).slice(0, 2000);
|
||||
const textParts = [];
|
||||
const candidates = [];
|
||||
let best = null;
|
||||
let bestScore = -1;
|
||||
let remainingSeconds = null;
|
||||
|
||||
function considerTimeline(parsed, source, weight = 0) {
|
||||
if (!parsed) return;
|
||||
const score = weight +
|
||||
(parsed.duration >= 20 * 60 ? 200 : 0) +
|
||||
(parsed.current > 0 ? 50 : 0) -
|
||||
Math.abs((parsed.duration / 2) - parsed.current) / 100;
|
||||
candidates.push({ ...parsed, source, score: Math.round(score) });
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
best = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const now = Number(node.getAttribute?.('aria-valuenow') ?? node.value);
|
||||
const max = Number(node.getAttribute?.('aria-valuemax') ?? node.max);
|
||||
if (Number.isFinite(now) && Number.isFinite(max) && max >= 60 && now >= 0 && now <= max + 5) {
|
||||
considerTimeline({ current: now, duration: max }, 'aria-value', 300);
|
||||
}
|
||||
|
||||
for (const attr of ['aria-valuetext', 'aria-label', 'title', 'data-testid', 'data-test']) {
|
||||
const parsed = parseTimelineText(node.getAttribute?.(attr));
|
||||
considerTimeline(parsed, attr, 200);
|
||||
}
|
||||
|
||||
const text = String(node.textContent || '').trim();
|
||||
if (text && text.length < 200) {
|
||||
const parsed = parseTimelineText(text);
|
||||
considerTimeline(parsed, 'text', 100);
|
||||
textParts.push(text);
|
||||
}
|
||||
// Disney's scrubber aria-valuenow can lag several seconds behind
|
||||
// real playback; a "time remaining" indicator updates live, so
|
||||
// capture it and later derive current = duration - remaining.
|
||||
const remClass = String(node.getAttribute?.('class') || '') + ' ' + String(node.getAttribute?.('data-testid') || '');
|
||||
if (/remain/i.test(remClass)) {
|
||||
const remMatch = String(node.textContent || '').match(/\d{1,3}:\d{2}(?::\d{2})?/);
|
||||
const rem = remMatch ? parseClockTime(remMatch[0]) : null;
|
||||
if (Number.isFinite(rem) && rem >= 0) remainingSeconds = rem;
|
||||
}
|
||||
}
|
||||
|
||||
if (best && remainingSeconds !== null && best.duration > 0) {
|
||||
const liveCurrent = best.duration - remainingSeconds;
|
||||
if (liveCurrent >= 0 && liveCurrent <= best.duration + 5) {
|
||||
considerTimeline({ current: Math.min(liveCurrent, best.duration), duration: best.duration }, 'time-remaining', 1000);
|
||||
}
|
||||
}
|
||||
considerTimeline(parseTimelineText(textParts.join(' ')), 'combined-text', 25);
|
||||
lastDisneyPlusTimelineCandidates = candidates
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 8)
|
||||
.map(({ source, current, duration }) => ({ source, current, duration }));
|
||||
return best;
|
||||
}
|
||||
|
||||
function getElementLabel(el) {
|
||||
return [
|
||||
el.getAttribute?.('aria-label'),
|
||||
el.getAttribute?.('title'),
|
||||
el.getAttribute?.('data-testid'),
|
||||
el.getAttribute?.('data-test'),
|
||||
el.textContent
|
||||
].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function getDisneyPlusSeekButtonLabels() {
|
||||
if (!isDisneyPlusHost() || typeof document === 'undefined') return [];
|
||||
return querySelectorAllShadow('button,[role="button"]')
|
||||
clearTimeout(_suppressTimers[state]);
|
||||
|
||||
.filter(Boolean)
|
||||
.slice(0, 20);
|
||||
delete _suppressTimers[state];
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
const backward = delta < 0;
|
||||
const candidates = querySelectorAllShadow('button,[role="button"]');
|
||||
const backRe = /rewind|backward|back\b|zurück|zurueck|rück|rueck|retour|recul|retroced|atrás|voltar|indietro/i;
|
||||
const forwardRe = /forward|ahead|skip|vorwärts|vorwaerts|vorsp|weiter|avancer|adelant|avançar|avancar|avanti/i;
|
||||
const timeRe = /\b(5|10|15|30)\b|sec|sek|second/i;
|
||||
|
||||
const button = candidates.find(btn => {
|
||||
const label = getElementLabel(btn);
|
||||
const matchesDirection = backward ? backRe.test(label) : forwardRe.test(label);
|
||||
return matchesDirection && timeRe.test(label);
|
||||
}) || candidates.find(btn => {
|
||||
const label = getElementLabel(btn);
|
||||
return backward ? backRe.test(label) : forwardRe.test(label);
|
||||
});
|
||||
|
||||
if (button && typeof button.click === 'function') {
|
||||
// Disney offers no absolute seek on its blob <video>, so we click its
|
||||
// relative skip button. Derive the step from the button's own label
|
||||
// (usually 10s; some UIs 5/15/30) and issue enough clicks to cover the
|
||||
// full delta. The previous 120s cap truncated any larger seek, which
|
||||
// broke absolute-position sync (e.g. seeking to 5:00 from far away).
|
||||
const stepMatch = getElementLabel(button).match(/\b(5|10|15|30)\b/);
|
||||
const step = stepMatch ? Number(stepMatch[1]) : 10;
|
||||
const clicks = Math.max(1, Math.min(90, Math.round(Math.abs(delta) / step)));
|
||||
for (let i = 0; i < clicks; i++) {
|
||||
setTimeout(() => button.click(), i * 60);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: Simulate ArrowLeft / ArrowRight keyboard events
|
||||
const clicks = Math.max(1, Math.min(12, Math.round(Math.abs(delta) / 10)));
|
||||
const eventInit = {
|
||||
key: backward ? 'ArrowLeft' : 'ArrowRight',
|
||||
code: backward ? 'ArrowLeft' : 'ArrowRight',
|
||||
keyCode: backward ? 37 : 39,
|
||||
which: backward ? 37 : 39,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window
|
||||
};
|
||||
const target = document.querySelector('.hive-video') || document.activeElement || document.body;
|
||||
for (let i = 0; i < clicks; i++) {
|
||||
setTimeout(() => {
|
||||
target.dispatchEvent(new window.KeyboardEvent('keydown', eventInit));
|
||||
target.dispatchEvent(new window.KeyboardEvent('keyup', eventInit));
|
||||
}, i * 60);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getDisneyPlusTimeline(video) {
|
||||
if (!isDisneyPlusHost()) return null;
|
||||
// Preferred: exact playhead/duration from the page media player,
|
||||
// relayed by the MAIN-world bridge. Authoritative while fresh.
|
||||
if (disneyPageApiTime && (Date.now() - disneyPageApiTime.at) < 2000 && disneyPageApiTime.duration > 0) {
|
||||
|
||||
|
||||
|
||||
// --- Seek Relay Filtering ---
|
||||
|
||||
// Minimum seek delta (seconds) to report. Prevents HLS/DASH buffering micro-seeks
|
||||
|
||||
const range = getSeekableRange(video);
|
||||
const current = video.currentTime;
|
||||
const ui = getDisneyPlusUiTimeline();
|
||||
|
||||
if (ui) {
|
||||
lastKnownDisneyPlusDuration = ui.duration;
|
||||
let nativeScale = 1;
|
||||
if (range && range.duration > ui.duration * 1.2) {
|
||||
nativeScale = range.duration / ui.duration;
|
||||
} else if (Number.isFinite(current) && ui.current > 1 && current > ui.current * 1.5) {
|
||||
nativeScale = current / ui.current;
|
||||
}
|
||||
const nativeStart = Number.isFinite(current)
|
||||
? current - ui.current * nativeScale
|
||||
: (range ? range.start : 0);
|
||||
lastKnownDisneyPlusScale = nativeScale;
|
||||
lastKnownDisneyPlusStart = nativeStart;
|
||||
// from being relayed to peers as user-initiated seeks.
|
||||
|
||||
const MIN_SEEK_DELTA = 2.0;
|
||||
|
||||
let lastReportedSeekTime = null; // last currentTime we relayed as a SEEK
|
||||
|
||||
let seekDebounceTimer = null; // debounce timer for rapid seek events
|
||||
|
||||
let expectedSeekTime = null; // strictly track programmatic seeks
|
||||
|
||||
|
||||
|
||||
const PAGE_API_SEEK_BRIDGE = 1;
|
||||
// Accurate Disney+ playhead pushed by the MAIN-world page-API bridge
|
||||
// (background.js installPageApiSeekBridge). The isolated content world
|
||||
// can't read the page's media player directly.
|
||||
let disneyPageApiTime = null;
|
||||
@@ -419,7 +163,7 @@
|
||||
// Site-specific player exceptions live here. The default HTML5 path stays below.
|
||||
function getSiteQuirkAdapters() {
|
||||
return [{
|
||||
matches() { return matchesPlayerUrls(this.urls); },
|
||||
name: 'disneyplus-page-api',
|
||||
key: 'disneyPlus',
|
||||
urls: ['disneyplus.com'],
|
||||
matches() { return matchesPlayerUrls(this.urls); },
|
||||
@@ -427,21 +171,9 @@
|
||||
getDebug(video) {
|
||||
return {
|
||||
name: this.name,
|
||||
timeline: getDisneyPlusTimeline(video),
|
||||
timelineCandidates: lastDisneyPlusTimelineCandidates,
|
||||
seekButtons: getDisneyPlusSeekButtonLabels()
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
function getActiveSiteQuirk() {
|
||||
return getSiteQuirkAdapters().find(adapter => adapter.matches()) || null;
|
||||
}
|
||||
|
||||
key: 'disneyPlus',
|
||||
urls: this.urls,
|
||||
return adapter ? adapter.getTimeline(video) : null;
|
||||
timeline: getDisneyPlusTimeline(video)
|
||||
};
|
||||
}
|
||||
}];
|
||||
@@ -1501,11 +1233,10 @@
|
||||
|
||||
|
||||
|
||||
function applyAudioSettings(videoEl, settings) {
|
||||
|
||||
const mergedSettings = mergeAudioSettings(settings);
|
||||
|
||||
if (!mergedSettings.enabled || !mergedSettings.compressor?.enabled) {
|
||||
function applyAudioSettings(videoEl, settings) {
|
||||
|
||||
const mergedSettings = mergeAudioSettings(settings);
|
||||
|
||||
if (!mergedSettings.enabled || !mergedSettings.compressor?.enabled) {
|
||||
|
||||
applyAudioBypass(videoEl);
|
||||
@@ -1513,10 +1244,9 @@
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
const chain = setupAudioChain(videoEl);
|
||||
|
||||
|
||||
|
||||
|
||||
const chain = setupAudioChain(videoEl);
|
||||
|
||||
if (!chain) return;
|
||||
@@ -1733,80 +1463,7 @@
|
||||
|
||||
const video = findVideo();
|
||||
|
||||
|
||||
|
||||
if (video && currentTitle && sameEpisode(currentTitle, expectedTitle)
|
||||
|
||||
&& video.currentTime < 5 && video.readyState >= 1) {
|
||||
|
||||
// Match! Pause at start and report ready.
|
||||
|
||||
if (!video.paused) {
|
||||
|
||||
_setSuppress('paused');
|
||||
|
||||
video.pause();
|
||||
|
||||
}
|
||||
|
||||
stopLobbyPoll();
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
|
||||
type: 'EPISODE_READY_LOCAL',
|
||||
|
||||
payload: { title: currentTitle }
|
||||
|
||||
}).catch(() => {});
|
||||
|
||||
reportLog(`Episode lobby: Ready for "${currentTitle}"`, 'success');
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function startLobbyPoll(expectedTitle) {
|
||||
|
||||
stopLobbyPoll();
|
||||
|
||||
_pendingLobbyTitle = expectedTitle;
|
||||
|
||||
|
||||
|
||||
// NOTE: Do NOT pause here. Three callers reach this function:
|
||||
|
||||
// 1. PAUSE_FOR_LOBBY (initiator): already paused by that handler before calling us.
|
||||
|
||||
// 2. EPISODE_LOBBY (non-initiator): peer may still be on the PREVIOUS episode — pausing
|
||||
|
||||
// would freeze them mid-episode. The pause happens inside checkAndReportLobbyReady()
|
||||
|
||||
// only once their title actually matches.
|
||||
|
||||
// 3. CONTENT_BOOT recovery: same reasoning as (2).
|
||||
|
||||
|
||||
|
||||
// Check immediately
|
||||
|
||||
if (checkAndReportLobbyReady(expectedTitle)) return;
|
||||
|
||||
|
||||
|
||||
// Poll every 2 seconds — no log spam, internal only
|
||||
|
||||
lobbyPollTimer = setInterval(() => {
|
||||
|
||||
checkAndReportLobbyReady(expectedTitle);
|
||||
|
||||
}, 2000);
|
||||
|
||||
const currentTitle = getMediaTitle();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -700,12 +700,6 @@
|
||||
<button id="remoteSeekForward" class="secondary" style="flex:1; font-size:12px;">+30s</button>
|
||||
</div>
|
||||
<button id="remoteSeekFiveMin" class="secondary" style="width:100%; font-size:12px; margin-bottom:15px;">Seek 5:00</button>
|
||||
<div style="margin-top: 15px;">
|
||||
<label style="font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:0.5px;">DOM Timestamps (Scraped)</label>
|
||||
<div class="info-card" id="devtoolsDomScraperList" style="font-size:10px; max-height:100px; overflow-y:auto; font-family:monospace; line-height:1.2; padding:6px; background:rgba(0,0,0,0.2); white-space:pre-wrap; border: 1px solid #334155;">
|
||||
No timestamps scraped.
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:10px; color:var(--text-muted); text-align:center; margin-top:10px; opacity:0.7;">
|
||||
Build: <span id="buildIdentifier">__BUILD_TIMESTAMP__</span>
|
||||
</div>
|
||||
|
||||
@@ -2292,13 +2292,6 @@ function refreshDebugInfo() {
|
||||
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons.slice(0, 4).join(' | ') : '';
|
||||
if (buttons) addField(`${label} Buttons`, buttons);
|
||||
}
|
||||
const domScraperEl = document.getElementById('devtoolsDomScraperList');
|
||||
if (domScraperEl && state.scrapedTimestamps) {
|
||||
domScraperEl.textContent = state.scrapedTimestamps.length > 0
|
||||
? state.scrapedTimestamps.join('\n')
|
||||
: 'No timestamps scraped.';
|
||||
}
|
||||
|
||||
addSection('Properties');
|
||||
addField('Seeking', String(state.seeking));
|
||||
addField('Ended', String(state.ended));
|
||||
|
||||
@@ -77,38 +77,15 @@ function makeDocument(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()) {
|
||||
function loadTimelineFns(hostname, document = makeDocument(), pageApiTime = null) {
|
||||
const disneyPageApiTime = pageApiTime
|
||||
? `let disneyPageApiTime = { position: ${pageApiTime.position}, duration: ${pageApiTime.duration}, at: Date.now() - ${pageApiTime.ageMs || 0} };`
|
||||
: 'let disneyPageApiTime = null;';
|
||||
return Function('window', 'document', [
|
||||
'let lastDisneyPlusTimelineCandidates = [];',
|
||||
'let lastKnownDisneyPlusDuration = 0;',
|
||||
'let lastKnownDisneyPlusScale = 1;',
|
||||
'let lastKnownDisneyPlusStart = 0;',
|
||||
'let lastDisneyPlusUiCurrent = null;',
|
||||
'let lastDisneyPlusNativeAtUi = null;',
|
||||
'let disneyPageApiTime = null;',
|
||||
extractFunction('scanShadowDom', source),
|
||||
extractFunction('querySelectorAllShadow', source),
|
||||
disneyPageApiTime,
|
||||
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),
|
||||
@@ -117,7 +94,7 @@ function loadTimelineFns(hostname, document = makeDocument()) {
|
||||
extractFunction('getSyncCurrentTime', source),
|
||||
extractFunction('getSyncDuration', source),
|
||||
extractFunction('toNativeSeekTime', source),
|
||||
'return { getActiveSiteQuirk, getSyncCurrentTime, getSyncDuration, toNativeSeekTime, clickDisneyPlusRelativeSeek };'
|
||||
'return { getActiveSiteQuirk, getSyncCurrentTime, getSyncDuration, toNativeSeekTime };'
|
||||
].join('\n'))({ location: { hostname } }, document);
|
||||
}
|
||||
|
||||
@@ -131,11 +108,11 @@ function loadPlayerFixFns(hostname) {
|
||||
].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');
|
||||
const disneyFns = loadTimelineFns('www.disneyplus.com', makeDocument(), {
|
||||
position: 9,
|
||||
duration: 10800
|
||||
});
|
||||
assert.equal(disneyFns.getActiveSiteQuirk().name, 'disneyplus-page-api');
|
||||
assert.deepEqual(disneyFns.getActiveSiteQuirk().urls, ['disneyplus.com']);
|
||||
const disneyVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||
currentTime: 29,
|
||||
@@ -144,17 +121,17 @@ const disneyVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||
});
|
||||
assert.equal(disneyFns.getSyncCurrentTime(disneyVideo), 9);
|
||||
assert.equal(disneyFns.getSyncDuration(disneyVideo), 10800);
|
||||
assert.equal(disneyFns.toNativeSeekTime(disneyVideo, 39), 119);
|
||||
assert.equal(disneyFns.toNativeSeekTime(disneyVideo, 39), 39);
|
||||
|
||||
const disneySeekableFallbackFns = loadTimelineFns('www.disneyplus.com');
|
||||
const disneyNoPageApiFns = 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);
|
||||
assert.equal(disneyNoPageApiFns.getSyncCurrentTime(disneyOffsetVideo), 29);
|
||||
assert.equal(disneyNoPageApiFns.getSyncDuration(disneyOffsetVideo), 0);
|
||||
assert.equal(disneyNoPageApiFns.toNativeSeekTime(disneyOffsetVideo, 39), 39);
|
||||
|
||||
const genericFns = loadTimelineFns('example.com');
|
||||
assert.equal(genericFns.getActiveSiteQuirk(), null);
|
||||
@@ -162,12 +139,6 @@ 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']);
|
||||
|
||||
Reference in New Issue
Block a user