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 ─────────────────────────────── */