From 78d93555e21d838753a293f5b2609210d50f7a87 Mon Sep 17 00:00:00 2001 From: Andre <200930841+Andre-Bzk@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:29:44 +0200 Subject: [PATCH] fix: enable HLS cache by default Disabling HLS caching makes live playback buffer and VOD seeking slower on Steam Deck. Keeping the performance cache enabled by default while bounding and clearing the cache preserves smooth playback without allowing cached media data to accumulate across sessions. --- src/main/index.ts | 30 +++++++++++++++++--- src/main/prefs/hlsCachePref.ts | 2 +- src/renderer/src/context/SettingsContext.tsx | 23 ++++++++++++--- src/renderer/src/i18n/de.ts | 8 +++--- src/renderer/src/i18n/en.ts | 8 +++--- src/renderer/src/styles/global.css | 10 +++++-- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 44b1389..3bd0edb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,14 +13,23 @@ log.transports.file.level = 'error' log.transports.console.level = 'debug' const isDev = !app.isPackaged +const MAX_CACHE_BYTES = 512 * 1024 * 1024 // Allows video.play() without a prior user gesture — play() is called from hls.js callbacks // that fire asynchronously long after the original click event. app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') // Explicit cache size prevents Chromium's dynamic sizing, which causes "Invalid cache (current) // size" errors when available disk space fluctuates. +app.commandLine.appendSwitch('disk-cache-size', String(MAX_CACHE_BYTES)) -app.commandLine.appendSwitch('disk-cache-size', String(500 * 1024 * 1024)) +async function clearDefaultSessionCache(reason: 'startup' | 'quit'): Promise { + try { + await session.defaultSession.clearCache() + log.info(`[twitch4steamdeck] Cleared Electron cache on ${reason}.`) + } catch (err) { + log.warn(`[twitch4steamdeck] Failed to clear Electron cache on ${reason}.`, err) + } +} function createWindow(): BrowserWindow { const isLinux = process.platform === 'linux' @@ -105,6 +114,7 @@ function setupTwitchCors(): void { } app.whenReady().then(async () => { + await clearDefaultSessionCache('startup') setupTwitchCors() const clientId = import.meta.env.MAIN_VITE_TWITCH_CLIENT_ID ?? '' const auth = new AuthService(clientId) @@ -119,10 +129,22 @@ app.whenReady().then(async () => { registerIpcHandlers(auth, helix, playback) const stopGamepad = startGamepadReader(() => BrowserWindow.getAllWindows()[0] ?? null) + let servicesStopped = false + let quitAfterCacheClear = false - app.on('before-quit', () => { - stopGamepad() - playback.stop() + app.on('before-quit', (event) => { + if (!servicesStopped) { + stopGamepad() + playback.stop() + servicesStopped = true + } + if (quitAfterCacheClear) return + + event.preventDefault() + void clearDefaultSessionCache('quit').finally(() => { + quitAfterCacheClear = true + app.quit() + }) }) if (!clientId) { diff --git a/src/main/prefs/hlsCachePref.ts b/src/main/prefs/hlsCachePref.ts index 5389c53..63987f8 100644 --- a/src/main/prefs/hlsCachePref.ts +++ b/src/main/prefs/hlsCachePref.ts @@ -1,4 +1,4 @@ -let hlsCacheEnabled = false +let hlsCacheEnabled = true export function getHlsCacheEnabled(): boolean { return hlsCacheEnabled diff --git a/src/renderer/src/context/SettingsContext.tsx b/src/renderer/src/context/SettingsContext.tsx index 8bbe11d..f38b0b2 100644 --- a/src/renderer/src/context/SettingsContext.tsx +++ b/src/renderer/src/context/SettingsContext.tsx @@ -13,6 +13,7 @@ export interface AppSettings { } const STORAGE_KEY = 't4sd:settings' +const HLS_CACHE_DEFAULT_MIGRATION_KEY = 't4sd:hls-cache-default-enabled-v1' const SIDEBAR_MIN = 240 const SIDEBAR_MAX = 400 const SIDEBAR_DEFAULT = 270 @@ -23,23 +24,37 @@ const DEFAULTS: AppSettings = { streamBadgeMode: 'both', sidebarWidth: SIDEBAR_DEFAULT, badgeGap: BADGE_GAP_DEFAULT, - hlsCacheEnabled: false, + hlsCacheEnabled: true, fileLoggingEnabled: false, language: 'en' } export { SIDEBAR_MIN, SIDEBAR_MAX, SIDEBAR_DEFAULT, BADGE_GAP_MIN, BADGE_GAP_MAX, BADGE_GAP_DEFAULT } +function applyHlsCacheDefaultMigration(settings: AppSettings): AppSettings { + try { + if (localStorage.getItem(HLS_CACHE_DEFAULT_MIGRATION_KEY) === '1') { + return settings + } + const next = { ...settings, hlsCacheEnabled: true } + saveSettings(next) + localStorage.setItem(HLS_CACHE_DEFAULT_MIGRATION_KEY, '1') + return next + } catch { + return { ...settings, hlsCacheEnabled: true } + } +} + function loadSettings(): AppSettings { try { const raw = localStorage.getItem(STORAGE_KEY) - if (!raw) return DEFAULTS + if (!raw) return applyHlsCacheDefaultMigration(DEFAULTS) const parsed = JSON.parse(raw) as Partial const modes: StreamBadgeMode[] = ['off', 'language', 'flag', 'both'] const languages: Language[] = ['de', 'en'] const sw = Number(parsed.sidebarWidth) const bg = Number(parsed.badgeGap) - return { + return applyHlsCacheDefaultMigration({ streamBadgeMode: modes.includes(parsed.streamBadgeMode as StreamBadgeMode) ? (parsed.streamBadgeMode as StreamBadgeMode) : DEFAULTS.streamBadgeMode, @@ -50,7 +65,7 @@ function loadSettings(): AppSettings { language: languages.includes(parsed.language as Language) ? (parsed.language as Language) : DEFAULTS.language - } + }) } catch { return DEFAULTS } diff --git a/src/renderer/src/i18n/de.ts b/src/renderer/src/i18n/de.ts index da65564..2829ef8 100644 --- a/src/renderer/src/i18n/de.ts +++ b/src/renderer/src/i18n/de.ts @@ -152,10 +152,10 @@ export const de = { 'settings.badgeGap.title': 'Flaggen-Badge Abstand', 'settings.badgeGap.hint': 'Abstand zwischen Menütext und Flaggen-Badge ({min}–{max} px).', 'settings.storage.title': 'Speicher', - 'settings.storage.hint': 'HLS-Segmente (Live & VODs) sind Single-Use — sie werden nach dem Abspielen nie wieder abgerufen. Caching erzeugt auf dem Steam Deck unnötiges Disk-Wachstum und Akku-Belastung. Thumbnails und API-Antworten bleiben immer gecacht. Maximale Cache-Größe: 500 MB.', - 'settings.hlsCache.label': 'HLS-Cache (Live & VODs)', - 'settings.hlsCache.on': 'Aktiv — Segmente werden gecacht', - 'settings.hlsCache.off': 'Inaktiv — kein Disk-Wachstum (empfohlen)', + 'settings.storage.hint': 'Thumbnails und API-Antworten bleiben während der App-Sitzung gecacht. Maximale Cache-Größe: 512 MB.', + 'settings.hlsCache.label': 'HLS-Cache', + 'settings.hlsCache.on': 'Aktiv — empfohlen für störungsfreie Wiedergabe und schnelles Seeking', + 'settings.hlsCache.off': 'Inaktiv — kann Puffern und langsameres Seeking verursachen', 'settings.cache.clearing': 'Wird geleert …', 'settings.cache.clear': 'Cache leeren', 'settings.fileLog.label': 'Datei-Logging', diff --git a/src/renderer/src/i18n/en.ts b/src/renderer/src/i18n/en.ts index db398cb..4653e9a 100644 --- a/src/renderer/src/i18n/en.ts +++ b/src/renderer/src/i18n/en.ts @@ -153,10 +153,10 @@ export const en: Messages = { 'settings.badgeGap.title': 'Flag badge spacing', 'settings.badgeGap.hint': 'Spacing between menu text and the flag badge ({min}–{max} px).', 'settings.storage.title': 'Storage', - 'settings.storage.hint': 'HLS segments (live & VODs) are single-use — they are never fetched again after playback. Caching causes unnecessary disk growth and battery drain on the Steam Deck. Thumbnails and API responses are always cached. Maximum cache size: 500 MB.', - 'settings.hlsCache.label': 'HLS cache (live & VODs)', - 'settings.hlsCache.on': 'Active — segments will be cached', - 'settings.hlsCache.off': 'Inactive — no disk growth (recommended)', + 'settings.storage.hint': 'Thumbnails and API responses remain cached during the app session. Maximum cache size: 512 MB.', + 'settings.hlsCache.label': 'HLS cache', + 'settings.hlsCache.on': 'Active — recommended for smooth playback and fast seeking', + 'settings.hlsCache.off': 'Inactive — may cause buffering and slower seeking', 'settings.cache.clearing': 'Clearing…', 'settings.cache.clear': 'Clear cache', 'settings.fileLog.label': 'File logging', diff --git a/src/renderer/src/styles/global.css b/src/renderer/src/styles/global.css index e8a1c12..7ba5385 100644 --- a/src/renderer/src/styles/global.css +++ b/src/renderer/src/styles/global.css @@ -1299,15 +1299,21 @@ body, } .settings-option__label { - flex: 1; + align-self: stretch; + display: flex; + align-items: center; + flex: 1 1 auto; font-weight: 600; + min-width: 0; } .settings-option__preview { + flex: 0 1 58%; font-size: 17px; color: var(--color-fg-dim); font-weight: 400; - white-space: nowrap; + line-height: 1.3; + text-align: right; } /* ── Settings Slider ─────────────────────────────── */