fix(player): nie twórz playera, gdy apka jest w tle

expo-video wola w init konstruktora VideoPlayer bezwarunkowy startService dla
androidx.media3 MediaSessionService (VideoPlayer.kt:233), a Android od 8.0
zabrania startu zwyklego serwisu z tla. Gdy ekran playera montowal sie juz w tle
(user wyszedl w trakcie resolve albo system wznowil stos ekranow), konstruktor
rzucal IllegalStateException i zamiast wideo pokazywal sie ekran bledu.
GOON-12: 63 wystapienia, 28 osob, wszystkie z in_foreground=false.

Konfiguracja tego nie wylacza, wiec bramkujemy montowanie: hooka nie da sie
warunkowac, stad wlasciwy ekran wyjechal do PlayerScreenActive, a eksportowany
PlayerScreen czeka na AppState 'active'. Bramka dziala tylko przy montowaniu,
wiec zejscie w tlo z juz dzialajacym playerem nie przerywa odtwarzania.

Opublikowane OTA na 1.2 i 1.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-08-20 12:16:33 +02:00
parent 544de9c5ba
commit b4c1216133
2 changed files with 48 additions and 0 deletions

View file

@ -16,6 +16,13 @@ export type ChangelogEntry = {
}; };
export const CHANGELOG: 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', id: '2026-08-20',
date: 'August 2026', date: 'August 2026',

View file

@ -15,6 +15,7 @@ import {
ActivityIndicator, ActivityIndicator,
Alert, Alert,
Animated, Animated,
AppState,
LayoutChangeEvent, LayoutChangeEvent,
Pressable, Pressable,
StatusBar, StatusBar,
@ -133,7 +134,47 @@ function detectMode(url: string): 'video' | 'webview' {
return 'video'; 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() { 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 (
<View style={gateStyles.wait}>
<ActivityIndicator color="#fff" />
</View>
);
}
return <PlayerScreenActive />;
}
const gateStyles = StyleSheet.create({
wait: { flex: 1, backgroundColor: '#000', alignItems: 'center', justifyContent: 'center' },
});
function PlayerScreenActive() {
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>(); const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
const params = route.params as RouteParams; const params = route.params as RouteParams;
const mode = params.mode ?? detectMode(params.url); const mode = params.mode ?? detectMode(params.url);