From ed790dc3cd3deac82e58804efb4eee3ab1af4210 Mon Sep 17 00:00:00 2001 From: Andre <200930841+Andre-Bzk@users.noreply.github.com> Date: Tue, 12 May 2026 21:02:13 +0200 Subject: [PATCH] fix: surface playback errors and stabilize disk cache size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playback errors in the global overlay (direct-start from Browse/Category/ StreamList screens) were silently discarded — the stream disappeared with no user feedback. Now shows an error overlay with the streamlink message and a dismiss hint; hls.js fatal errors use the same path. Chromium computes disk-cache-size dynamically from free disk space, so the value differs between runs. The cache index then contains a size that no longer matches, producing "Invalid cache (current) size" on shutdown. A fixed 500 MB cap keeps the value stable across restarts. The cache classification thresholds (500 MB / 1 GB) were designed for an uncapped cache. With a fixed 500 MB limit, the cache can never enter those states, leaving dead UI states and unused logic behind. --- src/main/index.ts | 3 ++ src/renderer/src/screens/AppShell.tsx | 38 ++++++++++++++++++--- src/renderer/src/screens/SettingsScreen.tsx | 19 +---------- src/renderer/src/styles/global.css | 22 ------------ 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index a2afcfa..aa3f8f9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -12,6 +12,9 @@ const isDev = !app.isPackaged // Erlaubt video.play() ohne vorangehende User-Geste (nötig, weil play() in hls.js-Callbacks // aufgerufen wird, die asynchron weit nach dem ursprünglichen Click-Event stattfinden). app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required') +// Explizite Cache-Größe verhindert Chromiums dynamische Größenberechnung, die bei +// wechselndem freiem Speicherplatz zu "Invalid cache (current) size"-Fehlern führt. +app.commandLine.appendSwitch('disk-cache-size', String(500 * 1024 * 1024)) function createWindow(): BrowserWindow { const isLinux = process.platform === 'linux' diff --git a/src/renderer/src/screens/AppShell.tsx b/src/renderer/src/screens/AppShell.tsx index 5fa01fe..c667b4c 100644 --- a/src/renderer/src/screens/AppShell.tsx +++ b/src/renderer/src/screens/AppShell.tsx @@ -14,7 +14,7 @@ import type { FollowedChannelInfo, GameInfo, HlsUrlPayload } from '../types/t4sd type Region = 'sidebar' | 'main' type QuitChoice = 'yes' | 'no' -type GlobalPlayState = 'idle' | 'starting' | 'playing' | 'paused' +type GlobalPlayState = 'idle' | 'starting' | 'playing' | 'paused' | 'error' interface Props { onLogout: () => void @@ -42,6 +42,7 @@ export default function AppShell({ onLogout }: Props): JSX.Element { const [liveCurrentQuality, setLiveCurrentQuality] = useState('best') const [liveQualityPanelOpen, setLiveQualityPanelOpen] = useState(false) const [liveQualityFocusedIndex, setLiveQualityFocusedIndex] = useState(0) + const [liveErrorMsg, setLiveErrorMsg] = useState('') const isGlobalPlaying = livePlayState !== 'idle' @@ -56,7 +57,7 @@ export default function AppShell({ onLogout }: Props): JSX.Element { if (!isGlobalPlaybackInitiated.current) return if (ev.kind === 'started') { setLivePlayState('playing') - } else if (ev.kind === 'stopped' || ev.kind === 'error') { + } else if (ev.kind === 'stopped') { isGlobalPlaybackInitiated.current = false setLiveHlsPayload(null) setLivePlayState('idle') @@ -65,6 +66,14 @@ export default function AppShell({ onLogout }: Props): JSX.Element { setLiveAvailableQualities(undefined) setLiveCurrentQuality('best') setLiveQualityPanelOpen(false) + setLiveErrorMsg('') + } else if (ev.kind === 'error') { + isGlobalPlaybackInitiated.current = false + setLiveHlsPayload(null) + setLivePlayState('error') + setLiveErrorMsg(ev.message ?? 'Stream konnte nicht gestartet werden.') + setLiveAvailableQualities(undefined) + setLiveQualityPanelOpen(false) } }) return () => { unsubHls(); unsubEvent() } @@ -106,6 +115,7 @@ export default function AppShell({ onLogout }: Props): JSX.Element { setLiveAvailableQualities(undefined) setLiveCurrentQuality('best') setLiveQualityPanelOpen(false) + setLiveErrorMsg('') void window.t4sd.playback.startLive(ch.broadcasterLogin).then(() => { window.t4sd.playback.getQualities(`twitch.tv/${ch.broadcasterLogin}`) .then((qs) => setLiveAvailableQualities(qs.length > 0 ? qs : [])) @@ -114,10 +124,15 @@ export default function AppShell({ onLogout }: Props): JSX.Element { }, []) const handleStopLive = useCallback(() => { + isGlobalPlaybackInitiated.current = false liveVideoRef.current?.stop() + setLiveHlsPayload(null) + setLivePlayState('idle') + setLiveChannel(null) setLiveAvailableQualities(undefined) setLiveCurrentQuality('best') setLiveQualityPanelOpen(false) + setLiveErrorMsg('') void window.t4sd.playback.stop() }, []) @@ -352,6 +367,21 @@ export default function AppShell({ onLogout }: Props): JSX.Element { Starte Wiedergabe… )} + {/* Fehler-Overlay */} + {livePlayState === 'error' && ( +
+ {liveErrorMsg} +
++ B / Esc — Schließen +
+