mobile: resolve get_file redirect client-side (kills hdporngg flicker)
hdporn.gg/fullmovies.xxx return an unresolved get_file direct_url that 302-redirects to fpvcdn.com with the requester IP baked in. The backend can't resolve it (would bind fpvcdn to the VPS IP -> mobile 403), so the phone must follow the redirect. But ExoPlayer errors on that cross-domain get_file->fpvcdn redirect (drops Referer / won't complete it) -> the native player falls back to the proxy via nav.replace, which the user sees as a screen-reload "flicker" before playback (and means it's actually playing through the VPS proxy, not direct). Fix: resolve the get_file 302 in JS on the phone (so fpvcdn binds to the phone IP) before navigating to the player, and hand ExoPlayer the final fpvcdn URL directly — no redirect, no error, no flicker, no proxy. Uses the same redirect:'manual' + Location-header pattern as the doodstream resolver (works on RN Android). On resolve failure it keeps the original get_file URL (current behaviour with proxy fallback). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e780e1ae6f
commit
e5b6e8968c
2 changed files with 76 additions and 1 deletions
62
mobile/src/lib/getfileResolver.ts
Normal file
62
mobile/src/lib/getfileResolver.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Mobile-side get_file → final CDN resolver.
|
||||||
|
*
|
||||||
|
* Tuby typu hdporn.gg / fullmovies.xxx serwują `<source>` = `.../get_file/...mp4`,
|
||||||
|
* które 302-redirectuje na fpvcdn.com z IP FETCHERA wbitym w URL (`ip=<kto-pobrał>`).
|
||||||
|
* Backend NIE może tego zresolwować (związałby fpvcdn z IP VPS → telefon 403), więc
|
||||||
|
* oddaje get_file URL niezresolwowany. Ale ExoPlayer wywala się na tym cross-domain
|
||||||
|
* redirekcie (gubi Referer / nie domyka) → fallback na proxy → „mignięcie".
|
||||||
|
*
|
||||||
|
* Fix: resolvujemy redirect TU, w JS na telefonie (fpvcdn bindje się do IP telefonu),
|
||||||
|
* i podajemy ExoPlayerowi finalny CDN URL — bez redirectu, bez błędu, bez migotania,
|
||||||
|
* bez proxy. Ten sam wzorzec co doodstream.ts (`redirect: 'manual'` + Location header,
|
||||||
|
* potwierdzone że działa na Android RN).
|
||||||
|
*/
|
||||||
|
const UA =
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
|
/** get_file URL który wymaga in-session resolve (nie nasz proxy URL). */
|
||||||
|
export function isGetFileUrl(url: string): boolean {
|
||||||
|
return /\/get_file\//.test(url) && !/\/proxy\//.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow get_file 302 → finalny CDN URL (z IP telefonu). Zwraca finalny URL gdy
|
||||||
|
* dojdzie do nie-get_file Location, oryginalny URL gdy get_file serwuje direct (2xx),
|
||||||
|
* lub null przy błędzie (caller wtedy gra oryginał = obecne zachowanie z fallbackiem).
|
||||||
|
*/
|
||||||
|
export async function resolveGetFile(url: string, referer?: string): Promise<string | null> {
|
||||||
|
let cur = url;
|
||||||
|
for (let hop = 0; hop < 4; hop++) {
|
||||||
|
let r: Response;
|
||||||
|
try {
|
||||||
|
r = await fetch(cur, {
|
||||||
|
method: 'GET',
|
||||||
|
redirect: 'manual',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': UA,
|
||||||
|
Range: 'bytes=0-1',
|
||||||
|
...(referer ? { Referer: referer } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (r.status >= 300 && r.status < 400) {
|
||||||
|
const loc = r.headers.get('location');
|
||||||
|
if (!loc) return null;
|
||||||
|
try {
|
||||||
|
cur = new URL(loc, cur).toString();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isGetFileUrl(cur)) return cur; // dotarliśmy do finalnego CDN
|
||||||
|
continue; // kolejny get_file hop (rzadkie)
|
||||||
|
}
|
||||||
|
if (r.status >= 200 && r.status < 300) {
|
||||||
|
return cur; // get_file serwuje direct, bez redirectu — grywalne
|
||||||
|
}
|
||||||
|
return null; // 4xx/5xx
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { useClient } from '../ClientContext';
|
import { useClient } from '../ClientContext';
|
||||||
|
import { isGetFileUrl, resolveGetFile } from '../lib/getfileResolver';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import type { PlaybackSource, SceneOut, StreamLink } from '../types';
|
import type { PlaybackSource, SceneOut, StreamLink } from '../types';
|
||||||
|
|
@ -474,8 +475,20 @@ function PlaybackButton({
|
||||||
}
|
}
|
||||||
// Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy URL (jeśli direct
|
// Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy URL (jeśli direct
|
||||||
// fails). Backend dostarcza oba w StreamLink. Headers tylko dla direct path.
|
// fails). Backend dostarcza oba w StreamLink. Headers tylko dla direct path.
|
||||||
const initialUrl = link.direct_url || link.stream_url!;
|
let initialUrl = link.direct_url || link.stream_url!;
|
||||||
const isDirect = !!link.direct_url && initialUrl === link.direct_url;
|
const isDirect = !!link.direct_url && initialUrl === link.direct_url;
|
||||||
|
|
||||||
|
// hdporn.gg/fullmovies.xxx: direct_url to `.../get_file/...` które 302-redirectuje
|
||||||
|
// na fpvcdn z IP fetchera. ExoPlayer wywala się na tym cross-domain redirekcie →
|
||||||
|
// fallback proxy → „mignięcie". Resolvujemy redirect TU (na telefonie → fpvcdn z IP
|
||||||
|
// telefonu) i podajemy ExoPlayerowi finalny URL — bez błędu/migotania/proxy. Fail =
|
||||||
|
// gramy oryginał (obecne zachowanie z fallbackiem). ~100-300ms (get_file 302 szybki).
|
||||||
|
if (isDirect && isGetFileUrl(initialUrl)) {
|
||||||
|
const ref = link.headers?.Referer || (refererHost ? `https://${refererHost}/` : undefined);
|
||||||
|
const resolved = await resolveGetFile(initialUrl, ref);
|
||||||
|
if (resolved) initialUrl = resolved;
|
||||||
|
}
|
||||||
|
|
||||||
nav.navigate('Player', {
|
nav.navigate('Player', {
|
||||||
url: initialUrl,
|
url: initialUrl,
|
||||||
sceneId,
|
sceneId,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue