diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts
index bde74ad..a6f0049 100644
--- a/mobile/src/changelog.ts
+++ b/mobile/src/changelog.ts
@@ -16,6 +16,13 @@ export type ChangelogEntry = {
};
export const CHANGELOG: ChangelogEntry[] = [
+ {
+ id: '2026-08-20b',
+ date: 'August 2026',
+ items: [
+ 'Fixed a crash that replaced the video with an error screen. If you left the app while a scene was still opening, or Android restored the player by itself, the player refused to start. It now waits until the app is back on screen and then plays normally.',
+ ],
+ },
{
id: '2026-08-20',
date: 'August 2026',
diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx
index 7b6a852..39d9e34 100644
--- a/mobile/src/screens/PlayerScreen.tsx
+++ b/mobile/src/screens/PlayerScreen.tsx
@@ -15,6 +15,7 @@ import {
ActivityIndicator,
Alert,
Animated,
+ AppState,
LayoutChangeEvent,
Pressable,
StatusBar,
@@ -133,7 +134,47 @@ function detectMode(url: string): 'video' | 'webview' {
return 'video';
}
+/** Bramka na konstruowanie playera.
+ *
+ * `new VideoPlayer(...)` w expo-video BEZWARUNKOWO wola w `init` startService dla
+ * androidx.media3 MediaSessionService (VideoPlayer.kt:233), a Android od 8.0 zabrania
+ * startu zwyklego serwisu z tla. Efekt: gdy ekran playera zamontuje sie, kiedy apka
+ * jest juz w tle (user wyszedl w trakcie resolve albo system wznowil stos ekranow),
+ * konstruktor rzuca IllegalStateException i uzytkownik dostaje ekran bledu zamiast
+ * wideo. GOON-12: 63 wystapienia, 28 osob, wszystkie z in_foreground=false.
+ *
+ * Nie da sie tego wylaczyc konfiguracja, wiec po prostu nie tworzymy playera, dopoki
+ * nie jestesmy na wierzchu. Hookow nie da sie warunkowac, stad osobny komponent.
+ * Bramka dziala TYLKO przy montowaniu: gdy player juz istnieje, zejscie w tlo jest
+ * bezpieczne (serwis jest zbindowany) i odtwarzania nie przerywamy.
+ */
export function PlayerScreen() {
+ const [foreground, setForeground] = React.useState(
+ () => AppState.currentState === 'active',
+ );
+ React.useEffect(() => {
+ if (foreground) return;
+ const sub = AppState.addEventListener('change', (next) => {
+ if (next === 'active') setForeground(true);
+ });
+ return () => sub.remove();
+ }, [foreground]);
+
+ if (!foreground) {
+ return (
+
+
+
+ );
+ }
+ return ;
+}
+
+const gateStyles = StyleSheet.create({
+ wait: { flex: 1, backgroundColor: '#000', alignItems: 'center', justifyContent: 'center' },
+});
+
+function PlayerScreenActive() {
const route = useRoute>();
const params = route.params as RouteParams;
const mode = params.mode ?? detectMode(params.url);