diff --git a/extension/background.js b/extension/background.js index d0c8543..a33ca9c 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1654,6 +1654,42 @@ async function devRemoteToolsAllowed() { return data.username === 'KoalaDev'; } +const mediaSessionInterceptorHosts = [ + 'disneyplus.com' +]; + +function shouldInstallMediaSessionInterceptor(url) { + try { + const host = new URL(url).hostname.toLowerCase(); + return mediaSessionInterceptorHosts.some(h => host === h || host.endsWith('.' + h)); + } catch (_e) { + return false; + } +} + +function installMediaSessionInterceptor() { + if (window.__koalaMediaSessionInterceptorInstalled) return; + window.__koalaMediaSessionInterceptorInstalled = true; + if (!navigator.mediaSession || typeof navigator.mediaSession.setPositionState !== 'function') return; + + const originalSet = navigator.mediaSession.setPositionState; + navigator.mediaSession.setPositionState = function(state) { + try { + window.postMessage({ + __koalaMediaSessionCapture: 1, + state: { + duration: state?.duration, + playbackRate: state?.playbackRate, + position: state?.position + } + }, '*'); + } catch (_e) { + // Suppress errors to not interfere with player + } + return originalSet.apply(navigator.mediaSession, arguments); + }; +} + function shouldUsePageApiSeek(url) { return typeof globalThis.koalaFindPageApiSeekProvider === 'function' && !!globalThis.koalaFindPageApiSeekProvider(url); @@ -1697,9 +1733,12 @@ function setPageApiSeekEnabled(enabled) { async function injectContentScript(tabId) { let needsPageApiSeek = false; let pageApiSeekReady = false; + let isMediaSessionCapable = false; try { const tab = await chrome.tabs.get(tabId); - needsPageApiSeek = shouldUsePageApiSeek(tab?.url || ''); + const url = tab?.url || ''; + needsPageApiSeek = shouldUsePageApiSeek(url); + isMediaSessionCapable = shouldInstallMediaSessionInterceptor(url); } catch (_e) { // Fall through to the generic content script injection. } @@ -1722,6 +1761,18 @@ async function injectContentScript(tabId) { } } + if (isMediaSessionCapable) { + try { + await chrome.scripting.executeScript({ + target: { tabId }, + world: 'MAIN', + func: installMediaSessionInterceptor + }); + } catch (err) { + addLog(`Media Session interceptor injection failed: ${err.message}`, 'warn'); + } + } + await chrome.scripting.executeScript({ target: { tabId }, files: ['page-api-seek-overrides.js'] diff --git a/extension/content.js b/extension/content.js index 70d7315..a64d1d0 100644 --- a/extension/content.js +++ b/extension/content.js @@ -1396,6 +1396,7 @@ inShadowDom, platform, siteQuirk: getSiteQuirkDebug(video), + mediaSessionPosition: window.__koalaLastCapturedMediaPosition || null, allVideos }); } else { @@ -1406,7 +1407,8 @@ platform, allVideos, url: window.location.href, - pageTitle: document.title, + pageTitle: document.title, + mediaSessionPosition: window.__koalaLastCapturedMediaPosition || null, metadata: (navigator.mediaSession && navigator.mediaSession.metadata) ? { title: navigator.mediaSession.metadata.title, artist: navigator.mediaSession.metadata.artist, @@ -1804,6 +1806,13 @@ // If the badge is already showing (early desync), refresh its text in place. // Re-creating the host element nukes the click target mid-poll and can drop a // click that landed between remove() and the re-create (L-4). + window.addEventListener('message', (event) => { + if (event.source !== window) return; + const data = event.data; + if (data && data.__koalaMediaSessionCapture === 1) { + window.__koalaLastCapturedMediaPosition = data.state; + } + }); if (hcmBadgeHost) { const span = hcmBadgeHost.shadowRoot && hcmBadgeHost.shadowRoot.querySelector('span'); if (span) span.textContent = '● ' + hcmStrings.badge; diff --git a/extension/popup.js b/extension/popup.js index 6cbb31e..cd183f5 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -2283,6 +2283,10 @@ function refreshDebugInfo() { if (state.nativeCurrentTime != null || state.nativeDuration != null) { addField('Native Time', `${state.nativeCurrentTime ?? '?'}s / ${state.nativeDuration ?? '?'}s`); } + if (state.mediaSessionPosition) { + const pos = state.mediaSessionPosition; + addField('MediaSession Captured', `${pos.position ?? '?'}s / ${pos.duration ?? '?'}s (@ ${pos.playbackRate ?? 1}x)`, '#a855f7'); + } if (state.siteQuirk) { const quirk = state.siteQuirk; const label = quirk.name || quirk.key || 'Site';