diff --git a/mobile/App.tsx b/mobile/App.tsx index 958ade6..68fe1cd 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -35,6 +35,7 @@ import { isAccepted as isAgeGateAccepted } from './src/lib/agegate'; import { APP_VERSION, NATIVE_RUNTIME, isNewerVersion, nativeBuildNumber } from './src/lib/appVersion'; import { DEFAULT_API_KEY, DEFAULT_BACKEND_URL } from './src/lib/backend'; import { getSettings as getLockSettings } from './src/lib/applock'; +import { unlockOrientationOnTablet } from './src/lib/tablet'; import { AppNavigator } from './src/navigation'; import { AgeGateScreen } from './src/screens/AgeGateScreen'; import { AppLockScreen } from './src/screens/AppLockScreen'; @@ -163,6 +164,11 @@ export default function App() { const [installing, setInstalling] = useState(false); const backgroundedAt = useRef(null); + // Tablet: obrot odblokowany od startu, zanim cokolwiek zagra (opis w src/lib/tablet.ts). + useEffect(() => { + unlockOrientationOnTablet(); + }, []); + useEffect(() => { (async () => { try { diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index d242d61..3114cea 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -49,7 +49,7 @@ gdy Chromium probowal podpiac skaner kodow (GOON-3B, 3 crashe od lipca). --> - + @@ -64,7 +64,7 @@ - + diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index bc48d83..86e20ac 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -35,6 +35,14 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-10-12b', + date: 'October 2026', + items: [ + 'Tablets: the app fills the whole screen in landscape instead of a narrow strip in the middle, and videos keep playing when you rotate the tablet or switch to fullscreen.', + 'porndoe: when a video refuses to load in the player on your network, it now plays on the porndoe page itself, with sound. If that works, the app goes straight to the page for that site for the next week.', + ], + }, { id: '2026-10-12a', date: 'October 2026', diff --git a/mobile/src/lib/pageMode.ts b/mobile/src/lib/pageMode.ts new file mode 100644 index 0000000..c5497be --- /dev/null +++ b/mobile/src/lib/pageMode.ts @@ -0,0 +1,50 @@ +/** + * Zrodla grane "w stronie" na tym urzadzeniu: WebView gra sam, bez wyciagania adresu do + * ExoPlayera. + * + * Po co: porndoe podpisuje link pod adres, z ktorego przyszlo zapytanie. Na czesci sieci + * (2026-10-12: 59 urzadzen, zawsze 403, 0 sukcesow) link pobrany na telefonie nie gra + * w ExoPlayerze. Prawdopodobna przyczyna to rozjazd IPv4/IPv6: z porndoe.com telefon laczy sie przez + * IPv6, z ich CDN przez IPv4 (albo odwrotnie). Na emulatorze z IPv6 przez 6to4 to samo trafia + * Chromium, wiec strona tez nie jest pewniakiem: jest zapasem po 403 i dopiero gdy film + * w stronie faktycznie ruszy, kolejne odtworzenia tego zrodla ida od razu do strony. + * Po 7 dniach od tego sukcesu znowu probujemy natywnie (siec moze sie zmienic). + * Telemetria: via='page' (success albo error page_no_play) w playback_events. + */ +import * as SecureStore from 'expo-secure-store'; + +const KEY = 'goon.page_mode_origins'; +const TTL_MS = 7 * 24 * 3600 * 1000; + +/** Zrodla, ktore po padnietym odtwarzaczu przechodza do grania w stronie. */ +export const PAGE_MODE_ORIGINS = ['tube:porndoecom']; + +let _cache: Record | null = null; + +async function load(): Promise> { + if (_cache) return _cache; + try { + const raw = await SecureStore.getItemAsync(KEY); + _cache = raw ? (JSON.parse(raw) as Record) : {}; + } catch { + _cache = {}; + } + return _cache; +} + +export async function prefersPageMode(origin: string): Promise { + if (!PAGE_MODE_ORIGINS.includes(origin)) return false; + const m = await load(); + const at = m[origin]; + return !!at && Date.now() - at < TTL_MS; +} + +export async function rememberPageMode(origin: string): Promise { + const m = await load(); + m[origin] = Date.now(); + try { + await SecureStore.setItemAsync(KEY, JSON.stringify(m)); + } catch { + // zostaje w pamieci do konca sesji + } +} diff --git a/mobile/src/lib/tablet.ts b/mobile/src/lib/tablet.ts new file mode 100644 index 0000000..a6087f2 --- /dev/null +++ b/mobile/src/lib/tablet.ts @@ -0,0 +1,30 @@ +/** + * Tablety: obrot calej aplikacji zamiast pionu na sztywno. + * + * MainActivity ma w manifescie screenOrientation="portrait", wiec na tablecie trzymanym w + * poziomie aplikacja byla waskim pionowym paskiem posrodku ekranu (zgloszenie 923df1ae, + * Samsung SM-X400). Do tego odtwarzacz zwalnial blokade pionu i okno rozszerzalo sie z paska + * na caly ekran. Zmienial sie wtedy najmniejszy wymiar ekranu, ktorego aktywnosc w APK 0.4.2 + * nie obsluguje sama (brak smallestScreenSize w configChanges), wiec Android tworzyl ja od nowa + * i odtwarzacz byl zwalniany po 2 sekundach: "w poziomie film nie gra" (zgloszenie e78a1194, + * Xiaomi Pad 7; odtworzone na emulatorze Pixel Tablet, w logu "relaunch" i Release ExoPlayera). + * + * Odblokowujemy obrot raz, przy starcie aplikacji, kiedy nic jeszcze nie gra (zostaje jedno + * przebudowanie aktywnosci na starcie). Odtwarzacz na tablecie potem niczego nie przestawia, + * a zwykly obrot nie zmienia najmniejszego wymiaru, wiec film gra dalej (sprawdzone na APK + * 0.4.2). Blokade obrotu z ustawien Androida system respektuje (UNSPECIFIED). Manifest ma juz + * smallestScreenSize w configChanges; dojdzie z nastepnym APK i zdejmie tez to przebudowanie + * na starcie oraz przy dzieleniu ekranu. + */ +import { Dimensions } from 'react-native'; +import * as ScreenOrientation from 'expo-screen-orientation'; + +/** Krotszy bok ekranu >= 600 dp, typowa granica tabletu na Androidzie. */ +export const IS_TABLET = (() => { + const { width, height } = Dimensions.get('screen'); + return Math.min(width, height) >= 600; +})(); + +export function unlockOrientationOnTablet(): void { + if (IS_TABLET) ScreenOrientation.unlockAsync().catch(() => {}); +} diff --git a/mobile/src/navigation.tsx b/mobile/src/navigation.tsx index 50a3da5..5527d19 100644 --- a/mobile/src/navigation.tsx +++ b/mobile/src/navigation.tsx @@ -120,6 +120,8 @@ export type RootStackParamList = { // telefonie w SceneDetail), 'backend', 'hoster' (resolver hostera w Playerze), // 'webview' (wyciagniety ze strony), 'proxy' (zapasowe proxy), 'movie'. via?: string; + // WebView gra sam w stronie, bez wyciagania adresu do ExoPlayera (patrz lib/pageMode.ts). + inPage?: boolean; }; }; diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index c62ca8f..e4d0901 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -32,6 +32,8 @@ import { useClient } from '../ClientContext'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { StreamLink } from '../types'; +import { PAGE_MODE_ORIGINS, rememberPageMode } from '../lib/pageMode'; +import { IS_TABLET } from '../lib/tablet'; interface RouteParams { /** Lista jakości tego samego wideo → przełącznik w playerze (user-request 89845707). */ @@ -64,6 +66,8 @@ interface RouteParams { fallbackEmbedUrl?: string; // Skad przyszedl adres, do telemetrii (patrz RootStackParamList.Player.via). via?: string; + // Graj w stronie, bez wyciagania adresu do ExoPlayera (lib/pageMode.ts). + inPage?: boolean; } // Wspólny helper dla native + WebView ekranu błędu: oznacza playback source jako @@ -241,8 +245,15 @@ function PlayerScreenActive() { // (Settings → Playback), player zostaje w pionie, telefon leżący płasko na stole // nie skacze między orientacjami (bug-report 55da1c3f). Przycisk pełnego ekranu dalej // pozwala wejść w poziom ręcznie, więc wyłączenie nie odbiera funkcji. + // Tablet: obrot jest odblokowany od startu aplikacji (src/lib/tablet.ts), a player go nie + // rusza. Kazda zmiana blokady w trakcie filmu przebudowywala aktywnosc i ubijala odtwarzacz. const { autoRotate } = usePreferences(); React.useEffect(() => { + if (IS_TABLET) { + return () => { + ScreenOrientation.unlockAsync().catch(() => {}); + }; + } if (autoRotate) ScreenOrientation.unlockAsync().catch(() => {}); else { ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {}); @@ -618,6 +629,9 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { // Step 2 → 3: proxy też failed, spróbuj WebView z embed URL. if (fallbackEmbedUrl && !didFallbackWebViewRef.current) { didFallbackWebViewRef.current = true; + // porndoe: link podpisany pod adres nie gra w ExoPlayerze na czesci sieci, wiec + // zapasem jest granie w stronie (lib/pageMode.ts), nie wyciaganie adresu z WebView. + const inPage = PAGE_MODE_ORIGINS.includes(playOrigin ?? ''); nav.replace('Player', { url: fallbackEmbedUrl, sceneId, @@ -630,6 +644,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { mode: 'webview', // Droga, ktora wlasnie padla; WebView dopisze do niej '>wv', gdy wyciagnie adres. via: viaRef.current, + inPage, }); } }, [status, fallbackProxyUrl, fallbackEmbedUrl, url, nav, sceneId, durationSec, refererHost, title, player, source, playerError, resolvePageUrl, reResolveApplicable, reResolveDone, playOrigin]); @@ -1000,9 +1015,12 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { const [isLandscape, setIsLandscape] = React.useState(false); const toggleFullscreen = React.useCallback(async () => { if (isLandscape) { - await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch( - () => {}, - ); + if (IS_TABLET) await ScreenOrientation.unlockAsync().catch(() => {}); + else { + await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch( + () => {}, + ); + } setIsLandscape(false); } else { await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch( @@ -1280,6 +1298,41 @@ function formatTime(sec: number): string { // Kontekst: WebView (Chrome engine) jest jedyną drogą dla CF-WAF-blocked hosterów (perverzija // → xtremestream.xyz, watchporn → playmogo). Apka porn-app robi dokładnie to samo, embed // JS hostera zna m3u8 URL bo Chrome JA3 pasuje, a ExoPlayer w naszej apce nie. +// Tryb "w stronie" (lib/pageMode.ts): doklejane PRZED INJECTED_JS. +// - Dzwiek od razu: blokada wyciszenia do pierwszego gestu jest po to, zeby nie bylo slychac +// strony w trakcie wyciagania adresu, a tu strona gra sama. +// - canPlayType('...mpegurl') zwraca pusto. WebView odpowiada "maybe", ale HLS wprost w