feat(playback): native pornxp.ph via phone-side resolver (kills black screen)
pornxp.ph serves direct <source> mp4 (360/720/1080p) on st.pornxp.sh whose path token is IP-bound to whoever fetched the PAGE (verified 2026-06-07: VPS-resolved URL → 403 cross-IP). Backend resolve was therefore impossible, so pornxpph fell to the WebView fallback which black-screened (bug-report fd06cd86). Fix: resolve on-device (same pattern as getfileResolver/doodstream) — the phone fetches the page, so tokens bind to the phone IP and play natively. New pornxpResolver.ts extracts the <source> mp4s into multi-quality StreamLinks; SceneDetail short-circuits tube:pornxpph to it before backend resolve, feeding the existing quality-picker + native player. Verified on emulator (live OTA): pornxpph scene → quality picker (1080/720/360) → native playback PLAYING (no WebView, no ads, no black screen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8c0edbdf7b
commit
b18f07d90e
2 changed files with 96 additions and 0 deletions
74
mobile/src/lib/pornxpResolver.ts
Normal file
74
mobile/src/lib/pornxpResolver.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/**
|
||||||
|
* Mobile-side pornxp.ph resolver.
|
||||||
|
*
|
||||||
|
* pornxp.ph serwuje fluidPlayer z bezpośrednimi `<source>` mp4 (360/720/1080p) na CDN
|
||||||
|
* `st.pornxp.sh`. CDN token w ścieżce jest IP-bound do tego KTO POBRAŁ STRONĘ (potwierdzone
|
||||||
|
* 2026-06-07: VPS-resolved URL → 403 cross-IP z hosta). Dlatego backend NIE może tego
|
||||||
|
* zresolwować (związałby token z IP VPS → telefon 403), a WebView fallback dawał czarny
|
||||||
|
* ekran (bug-report 2026-06-07, scena fd06cd86).
|
||||||
|
*
|
||||||
|
* Fix: telefon sam pobiera stronę (phone IP) → tokeny bound do IP telefonu → natywne
|
||||||
|
* multi-quality playback bez WebView/reklam. Ten sam wzorzec co getfileResolver.ts /
|
||||||
|
* doodstream.ts (resolve od strony, na urządzeniu).
|
||||||
|
*/
|
||||||
|
import type { StreamLink } from '../types';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
const _SOURCE_RE = /<source\b[^>]*\bsrc=['"]([^'"]+\.mp4[^'"]*)['"]/gi;
|
||||||
|
|
||||||
|
/** True dla page_url pornxp.ph (origin tube:pornxpph). */
|
||||||
|
export function isPornxpUrl(url: string | null | undefined): boolean {
|
||||||
|
return !!url && /(?:^|\/\/|\.)pornxp\.ph\//i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityFromUrl(u: string): string {
|
||||||
|
const m = u.match(/\/(\d{3,4})\.mp4/);
|
||||||
|
return m ? `${m[1]}p` : 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera pornxp.ph stronę NA TELEFONIE i wyciąga `<source>` mp4 jako StreamLink-i
|
||||||
|
* (direct_url == stream_url == finalny CDN mp4; Referer w headers). Token jest
|
||||||
|
* phone-IP-bound, więc gra direct bez proxy. Sort malejąco po jakości. Zwraca [] gdy
|
||||||
|
* nic nie znaleziono (caller spada na backend/WebView).
|
||||||
|
*/
|
||||||
|
export async function resolvePornxpPage(pageUrl: string): Promise<StreamLink[]> {
|
||||||
|
let html: string;
|
||||||
|
try {
|
||||||
|
const r = await fetch(pageUrl, { headers: { 'User-Agent': UA, Accept: 'text/html' } });
|
||||||
|
if (!r.ok) return [];
|
||||||
|
html = await r.text();
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const referer = (() => {
|
||||||
|
try {
|
||||||
|
return new URL(pageUrl).origin + '/';
|
||||||
|
} catch {
|
||||||
|
return 'https://pornxp.ph/';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const links: StreamLink[] = [];
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
_SOURCE_RE.lastIndex = 0;
|
||||||
|
while ((m = _SOURCE_RE.exec(html)) !== null) {
|
||||||
|
let u = m[1];
|
||||||
|
if (u.startsWith('//')) u = 'https:' + u;
|
||||||
|
if (seen.has(u)) continue;
|
||||||
|
seen.add(u);
|
||||||
|
links.push({
|
||||||
|
// oba pola na ten sam CDN mp4: stream_url → przejście przez quality-picker
|
||||||
|
// (filtr `!!stream_url`), direct_url → openAsVideo gra direct z Refererem.
|
||||||
|
stream_url: u,
|
||||||
|
direct_url: u,
|
||||||
|
headers: { Referer: referer, 'User-Agent': UA },
|
||||||
|
quality: qualityFromUrl(u),
|
||||||
|
type: 'mp4',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
links.sort((a, b) => (parseInt(b.quality || '0', 10) || 0) - (parseInt(a.quality || '0', 10) || 0));
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ 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, resolveGetFilePage } from '../lib/getfileResolver';
|
import { isGetFileUrl, resolveGetFilePage } from '../lib/getfileResolver';
|
||||||
|
import { resolvePornxpPage } from '../lib/pornxpResolver';
|
||||||
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';
|
||||||
|
|
@ -518,6 +519,27 @@ function PlaybackButton({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pornxp.ph: CDN token IP-bound (backend 403 cross-IP) → backend oddaje WebView
|
||||||
|
// fallback który czarno-ekranił (bug-report 2026-06-07, fd06cd86). Telefon sam
|
||||||
|
// pobiera stronę (phone-IP-bound mp4) → natywne multi-quality, zero WebView/reklam.
|
||||||
|
if (source.origin === 'tube:pornxpph') {
|
||||||
|
setResolving(true);
|
||||||
|
try {
|
||||||
|
const links = await resolvePornxpPage(source.page_url);
|
||||||
|
if (links.length > 0) {
|
||||||
|
markStarted();
|
||||||
|
if (links.length === 1) await openAsVideo(links[0], source.page_url);
|
||||||
|
else setQualityLinks(links);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// pusto → spadnij na backend resolve (WebView) poniżej
|
||||||
|
} catch {
|
||||||
|
// ignore → backend fallback
|
||||||
|
} finally {
|
||||||
|
setResolving(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tube origin: backend resolve → lista linków: część direct video (stream_url),
|
// Tube origin: backend resolve → lista linków: część direct video (stream_url),
|
||||||
// część hoster embed (embed_url, np. StreamWish/doodporn HTML page).
|
// część hoster embed (embed_url, np. StreamWish/doodporn HTML page).
|
||||||
// - direct → MX Player (intent video/*)
|
// - direct → MX Player (intent video/*)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue