From 6a64742ea390e61102b0af382decb2fa1ba98af3 Mon Sep 17 00:00:00 2001 From: Hanshal Bobate Date: Wed, 20 May 2026 03:53:17 +0530 Subject: [PATCH] Fix Windows media session metadata and hardware controls --- index.js | 26 ++++- package-lock.json | 39 ------- preload.js | 4 + src/pages/MoviePage.jsx | 82 ++++++++++++++ src/pages/TVPage.jsx | 97 ++++++++++++++++ src/utils/mediaSession.js | 225 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 432 insertions(+), 41 deletions(-) create mode 100644 src/utils/mediaSession.js diff --git a/index.js b/index.js index 0e8d89a..50984df 100644 --- a/index.js +++ b/index.js @@ -19,10 +19,15 @@ app.commandLine.appendSwitch( ); app.commandLine.appendSwitch( "disable-features", - "HardwareMediaKeyHandling,MediaSessionService,UseSandboxedXdgPortal", + "UseSandboxedXdgPortal", ); // Run the network stack in the browser process → one less utility process -app.commandLine.appendSwitch("enable-features", "NetworkServiceInProcess2"); +// Also enable hardware media key handling so Bluetooth earbuds, keyboard +// media keys and the Windows media flyout (FluentFlyout) can control playback. +app.commandLine.appendSwitch( + "enable-features", + "HardwareMediaKeyHandling,MediaSessionService,NetworkServiceInProcess2", +); // NOTE: enable-low-end-device-mode removed, it cuts the GPU texture tile budget // and causes visible seams/stripes/dots on large images. @@ -326,6 +331,23 @@ blockStats.init(getMainWindow); // get-block-stats lives with its data ipcMain.handle("get-block-stats", () => blockStats.getBlockStats()); +// -- Media Session: inject JS into ALL webview frames ------------------------- +// The renderer's executeJavaScript() only reaches the top-level frame. +// VidSrc (and similar) load their video player in a cross-origin iframe, so +// we need the main process to walk the WebFrameMain tree and inject into every +// frame — including cross-origin ones that the renderer cannot touch. +ipcMain.handle("inject-all-frames", async (_, { webContentsId, script }) => { + try { + const wc = webContents.fromId(webContentsId); + if (!wc || wc.isDestroyed()) return; + const injectFrame = async (frame) => { + try { await frame.executeJavaScript(script); } catch {} + for (const child of frame.frames ?? []) await injectFrame(child); + }; + await injectFrame(wc.mainFrame); + } catch {} +}); + // -- Player memory cleanup --------------------------------------------- // Called by MoviePage / TVPage on component unmount. // Destroys the player webview WebContents by tracked ID, then flushes caches and GCs. diff --git a/package-lock.json b/package-lock.json index 23f8dfd..ad738d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1443,9 +1443,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1460,9 +1457,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1477,9 +1471,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1494,9 +1485,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1511,9 +1499,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1528,9 +1513,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1545,9 +1527,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1562,9 +1541,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1579,9 +1555,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1596,9 +1569,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1613,9 +1583,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1630,9 +1597,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1647,9 +1611,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/preload.js b/preload.js index f2b17c3..6037577 100644 --- a/preload.js +++ b/preload.js @@ -184,4 +184,8 @@ contextBridge.exposeInMainWorld("electron", { }, offScheduledBackupRequested: (h) => ipcRenderer.removeListener("scheduled-backup-requested", h), + + // Webframe main injection for frames + injectAllFrames: (webContentsId, script) => + ipcRenderer.invoke("inject-all-frames", { webContentsId, script }), }); diff --git a/src/pages/MoviePage.jsx b/src/pages/MoviePage.jsx index 3cee4fe..11f8d58 100644 --- a/src/pages/MoviePage.jsx +++ b/src/pages/MoviePage.jsx @@ -50,6 +50,13 @@ import { getAgeLimitSetting, getRatingCountry, } from "../utils/ageRating"; +import { + setMediaSessionMetadata, + buildWebviewMetadataScript, + registerMediaSessionHandlers, + clearMediaSession, + syncPlaybackState, +} from "../utils/mediaSession"; export default function MoviePage({ item, @@ -478,6 +485,9 @@ export default function MoviePage({ if (result && result.duration > 0) { const ct = result.currentTime; + // Sync OS playback state on every progress tick + syncPlaybackState("playing"); + // ── Resolution-change reset detection ────────────────────────── // Videasy resets to 0 on quality change. We only seek back if: // - ct is near zero (≤5s) @@ -544,6 +554,78 @@ export default function MoviePage({ onHistory({ ...d, media_type: "movie" }); }, [d, onHistory]); + // ── Media Session API integration ───────────────────────────────────────── + // The OS reads media metadata from the renderer that OWNS the playing + //