mobile: page-side get_file resolve for hdporngg/fullmovies (native, no proxy/flicker)
Device logs (not assumptions) pinned the real cause of the hdporngg/fullmovies flicker: the backend returns a get_file URL, but get_file is bound to the IP that loaded the *page*. The backend (VPS) loads the page, so the get_file is VPS-bound; the phone fetching that get_file gets HTTP 410 -> ExoPlayer errors -> falls back to the proxy via nav.replace (the "flicker"), and ends up streaming through the proxy. (My earlier "stateless/portable" test was from the VPS — same IP as the page load — so it wrongly showed 206.) Fix: when the direct_url is a get_file, the phone re-fetches the *page* itself (resolveGetFilePage on source.page_url) so the get_file is bound to the phone IP, picks the requested quality skipping 4K (dead on fpvcdn), follows to the CDN, and hands ExoPlayer a working URL. On failure it keeps the original (proxy fallback). Verified on device: [getfile] page-resolve -> get_file 206 -> ExoPlayer PLAYING, position advancing, no error/proxy/flicker, real video frame rendered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5b6e8968c
commit
a5ec6ca991
2 changed files with 62 additions and 4 deletions
|
|
@ -20,6 +20,59 @@ export function isGetFileUrl(url: string): boolean {
|
||||||
return /\/get_file\//.test(url) && !/\/proxy\//.test(url);
|
return /\/get_file\//.test(url) && !/\/proxy\//.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _SOURCE_RE = /<source\s+src=['"]([^'"]+\/get_file\/[^'"]+\.mp4[^'"]*)['"][^>]*?(?:title|label)=['"]?([^'">]*)/gi;
|
||||||
|
const _SKIP_Q = /2160|1440|4k/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pełny resolve dla hdporn.gg/fullmovies.xxx OD STRONY (nie od get_file URL).
|
||||||
|
*
|
||||||
|
* KLUCZOWE (z logów urządzenia 2026-06-06): get_file jest zbindowany do IP tego kto
|
||||||
|
* załadował STRONĘ. Backend (VPS) ładuje stronę → get_file dla IP VPS → telefon fetchuje
|
||||||
|
* → 410. Dlatego telefon musi sam załadować stronę (phone IP) → dostać phone-bound
|
||||||
|
* get_file → follow 302 → fpvcdn z IP telefonu → ExoPlayer gra. Pomija 4K (martwe na fpvcdn).
|
||||||
|
*
|
||||||
|
* `quality` = preferowana etykieta z backendu (np. "720p"); fallback = pierwszy nie-4K.
|
||||||
|
* Zwraca finalny CDN URL lub null (caller wtedy gra oryginał = proxy fallback).
|
||||||
|
*/
|
||||||
|
export async function resolveGetFilePage(
|
||||||
|
pageUrl: string,
|
||||||
|
quality?: string | null,
|
||||||
|
referer?: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
let html: string;
|
||||||
|
try {
|
||||||
|
const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
|
||||||
|
if (!r.ok) return null;
|
||||||
|
html = await r.text();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const wantQ = (quality || '').replace(/\s+/g, '').toLowerCase();
|
||||||
|
let pick: string | null = null;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
_SOURCE_RE.lastIndex = 0;
|
||||||
|
while ((m = _SOURCE_RE.exec(html)) !== null) {
|
||||||
|
const q = (m[2] || '').trim();
|
||||||
|
if (_SKIP_Q.test(q)) continue;
|
||||||
|
let u = m[1];
|
||||||
|
if (u.startsWith('//')) u = 'https:' + u;
|
||||||
|
if (wantQ && q && q.replace(/\s+/g, '').toLowerCase() === wantQ) {
|
||||||
|
pick = u;
|
||||||
|
break; // dokładne dopasowanie jakości
|
||||||
|
}
|
||||||
|
if (!pick) pick = u; // pierwszy nie-4K jako fallback
|
||||||
|
}
|
||||||
|
if (!pick) return null;
|
||||||
|
const ref = referer || (() => {
|
||||||
|
try {
|
||||||
|
return new URL(pageUrl).origin + '/';
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return resolveGetFile(pick, ref);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Follow get_file 302 → finalny CDN URL (z IP telefonu). Zwraca finalny URL gdy
|
* 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),
|
* dojdzie do nie-get_file Location, oryginalny URL gdy get_file serwuje direct (2xx),
|
||||||
|
|
@ -42,8 +95,8 @@ export async function resolveGetFile(url: string, referer?: string): Promise<str
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const loc = r.headers.get('location');
|
||||||
if (r.status >= 300 && r.status < 400) {
|
if (r.status >= 300 && r.status < 400) {
|
||||||
const loc = r.headers.get('location');
|
|
||||||
if (!loc) return null;
|
if (!loc) return null;
|
||||||
try {
|
try {
|
||||||
cur = new URL(loc, cur).toString();
|
cur = new URL(loc, cur).toString();
|
||||||
|
|
@ -56,7 +109,9 @@ export async function resolveGetFile(url: string, referer?: string): Promise<str
|
||||||
if (r.status >= 200 && r.status < 300) {
|
if (r.status >= 200 && r.status < 300) {
|
||||||
return cur; // get_file serwuje direct, bez redirectu — grywalne
|
return cur; // get_file serwuje direct, bez redirectu — grywalne
|
||||||
}
|
}
|
||||||
return null; // 4xx/5xx
|
// status 0 / opaqueredirect (RN okhttp czasem dla redirect:'manual') albo 4xx/5xx —
|
||||||
|
// nie da się odczytać celu, caller zagra oryginał (proxy fallback).
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return cur;
|
return cur;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +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 { isGetFileUrl, resolveGetFilePage } 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';
|
||||||
|
|
@ -483,9 +483,12 @@ function PlaybackButton({
|
||||||
// fallback proxy → „mignięcie". Resolvujemy redirect TU (na telefonie → fpvcdn z IP
|
// fallback proxy → „mignięcie". Resolvujemy redirect TU (na telefonie → fpvcdn z IP
|
||||||
// telefonu) i podajemy ExoPlayerowi finalny URL — bez błędu/migotania/proxy. Fail =
|
// telefonu) i podajemy ExoPlayerowi finalny URL — bez błędu/migotania/proxy. Fail =
|
||||||
// gramy oryginał (obecne zachowanie z fallbackiem). ~100-300ms (get_file 302 szybki).
|
// gramy oryginał (obecne zachowanie z fallbackiem). ~100-300ms (get_file 302 szybki).
|
||||||
|
// hdporn.gg/fullmovies.xxx: backendowy get_file jest zbindowany do IP VPS (page-loader)
|
||||||
|
// → telefon dostaje 410 → fallback proxy (mignięcie). Re-fetchujemy STRONĘ na telefonie
|
||||||
|
// (phone-bound get_file) → fpvcdn z IP telefonu → ExoPlayer gra direct (bez proxy/migotania).
|
||||||
if (isDirect && isGetFileUrl(initialUrl)) {
|
if (isDirect && isGetFileUrl(initialUrl)) {
|
||||||
const ref = link.headers?.Referer || (refererHost ? `https://${refererHost}/` : undefined);
|
const ref = link.headers?.Referer || (refererHost ? `https://${refererHost}/` : undefined);
|
||||||
const resolved = await resolveGetFile(initialUrl, ref);
|
const resolved = await resolveGetFilePage(source.page_url, link.quality, ref);
|
||||||
if (resolved) initialUrl = resolved;
|
if (resolved) initialUrl = resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue