diff --git a/package.json b/package.json index de90d1a..9a2efa9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streambert", - "version": "2.5.0", + "version": "2.6.0", "description": "Streambert Desktop App", "author": { "name": "truelockmc", diff --git a/preload.js b/preload.js index f2b17c3..862098d 100644 --- a/preload.js +++ b/preload.js @@ -184,4 +184,13 @@ contextBridge.exposeInMainWorld("electron", { }, offScheduledBackupRequested: (h) => ipcRenderer.removeListener("scheduled-backup-requested", h), + + fetchReleaseImage: (url) => + ipcRenderer.invoke("fetch-release-image", { url }), }); + +if (process.platform === "darwin") { + navigator.mediaDevices?.addEventListener("devicechange", () => { + ipcRenderer.send("audio-device-changed"); + }); +} diff --git a/src/ipc/player.js b/src/ipc/player.js index bc525ba..270173c 100644 --- a/src/ipc/player.js +++ b/src/ipc/player.js @@ -151,7 +151,7 @@ function register(getMainWindow, { writeSecretMigration }) { : ["/usr/bin/mpv", "/usr/local/bin/mpv", "/snap/bin/mpv", "mpv"]; // ── Validate subtitle paths ─────────────────────────────────────────── - // Each subtitle path is independently validated + // Each subtitle path is independently validated. const subFilePaths = Array.isArray(subtitlePaths) ? subtitlePaths .map((sp) => (typeof sp === "string" ? sp : sp?.path)) @@ -325,6 +325,7 @@ function register(getMainWindow, { writeSecretMigration }) { } // Same check for every trusted source (GitHub, Codeberg, ...) + // see TRUSTED_UPDATE_SOURCES above. const trustedSource = findTrustedUpdateSource(parsed); if (!trustedSource) { return { ok: false, error: "Unauthorized update source" }; @@ -568,6 +569,7 @@ function register(getMainWindow, { writeSecretMigration }) { // ── Proxy release-note images through the main process ─────────────────── // Codeberg (and GitHub) release images are blocked by Electron's renderer // CSP. Fetch them here in the main process and return a base64 data-URI. + const ALLOWED_IMAGE_HOSTS = new Set([ "codeberg.org", "github.com", @@ -691,4 +693,61 @@ function register(getMainWindow, { writeSecretMigration }) { }); } -module.exports = { register }; +// ── Central audio-device-change recovery (macOS HDMI/TV fix) ───────────────── +// When the user switches audio output (e.g. Speakers → HDMI TV), Chromium +// suspends every AudioContext in every session. + +function registerAudioDeviceRecovery() { + if (process.platform !== "darwin") return; // only needed on macOS + + const { session, webContents, ipcMain: ipc } = require("electron"); + + const RECOVERY_JS = ` + (() => { + async function recoverAudio() { + const ctxs = window.__audioContexts || []; + for (const ctx of ctxs) { + try { if (ctx.state === 'suspended') await ctx.resume(); } catch {} + } + const v = document.querySelector('video'); + if (!v || v.paused) return; + const t = v.currentTime; + try { + v.pause(); + await new Promise(r => setTimeout(r, 80)); + v.currentTime = t; + await v.play(); + } catch {} + } + recoverAudio(); + })() + `; + + const recoverAllWebContents = () => { + for (const wc of webContents.getAllWebContents()) { + if (wc.isDestroyed()) continue; + wc.executeJavaScript(RECOVERY_JS).catch(() => {}); + try { + for (const frame of wc.mainFrame?.framesInSubtree ?? []) { + try { + frame.executeJavaScript(RECOVERY_JS); + } catch {} + } + } catch {} + } + }; + + // Hook 1: Chromium's built-in device-selection event + session.defaultSession.on( + "select-audio-device", + (event, details, callback) => { + callback(""); // let Chromium pick the default, don't block + setTimeout(recoverAllWebContents, 150); + }, + ); + + // Hook 2: renderer sends this when navigator.mediaDevices fires "devicechange" + ipc.on("audio-device-changed", () => setTimeout(recoverAllWebContents, 150)); +} + +module.exports = { register, registerAudioDeviceRecovery }; diff --git a/src/styles/global.css b/src/styles/global.css index fbd38ad..48c5283 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1024,6 +1024,20 @@ body.no-anim *::after { display: none !important; } +/* Fix window-dragging on MacOS */ +.window-titlebar { + -webkit-app-region: drag; + cursor: move; +} + +/* Ensure titlebar buttons are clickable and not part of drag region */ +.window-titlebar button, +.window-titlebar .titlebar-btn { + -webkit-app-region: no-drag; + pointer-events: auto; + cursor: default; +} + /* Hide the custom window titlebar on player fullscreen */ [data-player-fullscreen] .window-titlebar { display: none !important;