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 { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useClient } from '../ClientContext';
|
||||
import { isGetFileUrl, resolveGetFile } from '../lib/getfileResolver';
|
||||
import type { RootStackParamList } from '../navigation';
|
||||
import { theme } from '../theme';
|
||||
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
|
||||
// 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;
|
||||
|
||||
// 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', {
|
||||
url: initialUrl,
|
||||
sceneId,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue