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.
This commit is contained in:
Andre
2026-06-07 19:40:19 +02:00
parent 7e208b49b0
commit 78d93555e2
6 changed files with 62 additions and 19 deletions
+26 -4
View File
@@ -13,14 +13,23 @@ log.transports.file.level = 'error'
log.transports.console.level = 'debug' log.transports.console.level = 'debug'
const isDev = !app.isPackaged 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 // Allows video.play() without a prior user gesture — play() is called from hls.js callbacks
// that fire asynchronously long after the original click event. // that fire asynchronously long after the original click event.
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
// Explicit cache size prevents Chromium's dynamic sizing, which causes "Invalid cache (current) // Explicit cache size prevents Chromium's dynamic sizing, which causes "Invalid cache (current)
// size" errors when available disk space fluctuates. // 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<void> {
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 { function createWindow(): BrowserWindow {
const isLinux = process.platform === 'linux' const isLinux = process.platform === 'linux'
@@ -105,6 +114,7 @@ function setupTwitchCors(): void {
} }
app.whenReady().then(async () => { app.whenReady().then(async () => {
await clearDefaultSessionCache('startup')
setupTwitchCors() setupTwitchCors()
const clientId = import.meta.env.MAIN_VITE_TWITCH_CLIENT_ID ?? '' const clientId = import.meta.env.MAIN_VITE_TWITCH_CLIENT_ID ?? ''
const auth = new AuthService(clientId) const auth = new AuthService(clientId)
@@ -119,10 +129,22 @@ app.whenReady().then(async () => {
registerIpcHandlers(auth, helix, playback) registerIpcHandlers(auth, helix, playback)
const stopGamepad = startGamepadReader(() => BrowserWindow.getAllWindows()[0] ?? null) const stopGamepad = startGamepadReader(() => BrowserWindow.getAllWindows()[0] ?? null)
let servicesStopped = false
let quitAfterCacheClear = false
app.on('before-quit', () => { app.on('before-quit', (event) => {
stopGamepad() if (!servicesStopped) {
playback.stop() stopGamepad()
playback.stop()
servicesStopped = true
}
if (quitAfterCacheClear) return
event.preventDefault()
void clearDefaultSessionCache('quit').finally(() => {
quitAfterCacheClear = true
app.quit()
})
}) })
if (!clientId) { if (!clientId) {
+1 -1
View File
@@ -1,4 +1,4 @@
let hlsCacheEnabled = false let hlsCacheEnabled = true
export function getHlsCacheEnabled(): boolean { export function getHlsCacheEnabled(): boolean {
return hlsCacheEnabled return hlsCacheEnabled
+19 -4
View File
@@ -13,6 +13,7 @@ export interface AppSettings {
} }
const STORAGE_KEY = 't4sd:settings' const STORAGE_KEY = 't4sd:settings'
const HLS_CACHE_DEFAULT_MIGRATION_KEY = 't4sd:hls-cache-default-enabled-v1'
const SIDEBAR_MIN = 240 const SIDEBAR_MIN = 240
const SIDEBAR_MAX = 400 const SIDEBAR_MAX = 400
const SIDEBAR_DEFAULT = 270 const SIDEBAR_DEFAULT = 270
@@ -23,23 +24,37 @@ const DEFAULTS: AppSettings = {
streamBadgeMode: 'both', streamBadgeMode: 'both',
sidebarWidth: SIDEBAR_DEFAULT, sidebarWidth: SIDEBAR_DEFAULT,
badgeGap: BADGE_GAP_DEFAULT, badgeGap: BADGE_GAP_DEFAULT,
hlsCacheEnabled: false, hlsCacheEnabled: true,
fileLoggingEnabled: false, fileLoggingEnabled: false,
language: 'en' language: 'en'
} }
export { SIDEBAR_MIN, SIDEBAR_MAX, SIDEBAR_DEFAULT, BADGE_GAP_MIN, BADGE_GAP_MAX, BADGE_GAP_DEFAULT } 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 { function loadSettings(): AppSettings {
try { try {
const raw = localStorage.getItem(STORAGE_KEY) const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return DEFAULTS if (!raw) return applyHlsCacheDefaultMigration(DEFAULTS)
const parsed = JSON.parse(raw) as Partial<AppSettings> const parsed = JSON.parse(raw) as Partial<AppSettings>
const modes: StreamBadgeMode[] = ['off', 'language', 'flag', 'both'] const modes: StreamBadgeMode[] = ['off', 'language', 'flag', 'both']
const languages: Language[] = ['de', 'en'] const languages: Language[] = ['de', 'en']
const sw = Number(parsed.sidebarWidth) const sw = Number(parsed.sidebarWidth)
const bg = Number(parsed.badgeGap) const bg = Number(parsed.badgeGap)
return { return applyHlsCacheDefaultMigration({
streamBadgeMode: modes.includes(parsed.streamBadgeMode as StreamBadgeMode) streamBadgeMode: modes.includes(parsed.streamBadgeMode as StreamBadgeMode)
? (parsed.streamBadgeMode as StreamBadgeMode) ? (parsed.streamBadgeMode as StreamBadgeMode)
: DEFAULTS.streamBadgeMode, : DEFAULTS.streamBadgeMode,
@@ -50,7 +65,7 @@ function loadSettings(): AppSettings {
language: languages.includes(parsed.language as Language) language: languages.includes(parsed.language as Language)
? (parsed.language as Language) ? (parsed.language as Language)
: DEFAULTS.language : DEFAULTS.language
} })
} catch { } catch {
return DEFAULTS return DEFAULTS
} }
+4 -4
View File
@@ -152,10 +152,10 @@ export const de = {
'settings.badgeGap.title': 'Flaggen-Badge Abstand', 'settings.badgeGap.title': 'Flaggen-Badge Abstand',
'settings.badgeGap.hint': 'Abstand zwischen Menütext und Flaggen-Badge ({min}–{max} px).', 'settings.badgeGap.hint': 'Abstand zwischen Menütext und Flaggen-Badge ({min}–{max} px).',
'settings.storage.title': 'Speicher', '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.storage.hint': 'Thumbnails und API-Antworten bleiben während der App-Sitzung gecacht. Maximale Cache-Größe: 512 MB.',
'settings.hlsCache.label': 'HLS-Cache (Live & VODs)', 'settings.hlsCache.label': 'HLS-Cache',
'settings.hlsCache.on': 'Aktiv — Segmente werden gecacht', 'settings.hlsCache.on': 'Aktiv — empfohlen für störungsfreie Wiedergabe und schnelles Seeking',
'settings.hlsCache.off': 'Inaktiv — kein Disk-Wachstum (empfohlen)', 'settings.hlsCache.off': 'Inaktiv — kann Puffern und langsameres Seeking verursachen',
'settings.cache.clearing': 'Wird geleert …', 'settings.cache.clearing': 'Wird geleert …',
'settings.cache.clear': 'Cache leeren', 'settings.cache.clear': 'Cache leeren',
'settings.fileLog.label': 'Datei-Logging', 'settings.fileLog.label': 'Datei-Logging',
+4 -4
View File
@@ -153,10 +153,10 @@ export const en: Messages = {
'settings.badgeGap.title': 'Flag badge spacing', 'settings.badgeGap.title': 'Flag badge spacing',
'settings.badgeGap.hint': 'Spacing between menu text and the flag badge ({min}–{max} px).', 'settings.badgeGap.hint': 'Spacing between menu text and the flag badge ({min}–{max} px).',
'settings.storage.title': 'Storage', '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.storage.hint': 'Thumbnails and API responses remain cached during the app session. Maximum cache size: 512 MB.',
'settings.hlsCache.label': 'HLS cache (live & VODs)', 'settings.hlsCache.label': 'HLS cache',
'settings.hlsCache.on': 'Active — segments will be cached', 'settings.hlsCache.on': 'Active — recommended for smooth playback and fast seeking',
'settings.hlsCache.off': 'Inactive — no disk growth (recommended)', 'settings.hlsCache.off': 'Inactive — may cause buffering and slower seeking',
'settings.cache.clearing': 'Clearing…', 'settings.cache.clearing': 'Clearing…',
'settings.cache.clear': 'Clear cache', 'settings.cache.clear': 'Clear cache',
'settings.fileLog.label': 'File logging', 'settings.fileLog.label': 'File logging',
+8 -2
View File
@@ -1299,15 +1299,21 @@ body,
} }
.settings-option__label { .settings-option__label {
flex: 1; align-self: stretch;
display: flex;
align-items: center;
flex: 1 1 auto;
font-weight: 600; font-weight: 600;
min-width: 0;
} }
.settings-option__preview { .settings-option__preview {
flex: 0 1 58%;
font-size: 17px; font-size: 17px;
color: var(--color-fg-dim); color: var(--color-fg-dim);
font-weight: 400; font-weight: 400;
white-space: nowrap; line-height: 1.3;
text-align: right;
} }
/* ── Settings Slider ─────────────────────────────── */ /* ── Settings Slider ─────────────────────────────── */