From 7e2807b42e83638dd74244f5997ef579929ce944 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Sat, 11 Jul 2026 19:49:52 +0200 Subject: [PATCH] feat(supjav): resolve voe hoster to direct stream instead of WebView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit supjav is an embed-aggregator (voe/streamtape/turbovid/fc2stream). It was playing through the WebView fallback because none of its hosters had a native phone-side resolver wired into the scene player. voe.sx does have one (voeResolver.ts, already used for movies) — it just was not wired for scenes. - PlayerScreen: wire isVoeUrl/resolveVoePage into the hoster routing (like dood/filemoon), so a voe embed resolves on the phone to a direct m3u8 (token bound to the phone /16, plays direct, no WebView, no proxy). - supjav extractor: return voe first (sorted by URL), and stop putting the raw hostname in `quality` (the digit in "fc2stream" was parsed as quality 2 and won _pick_best) — use a clean digit-free server label instead. - SceneDetailScreen: for hoster-only scenes, open the first embed (the extractor puts the natively-resolvable one first) rather than _pick_best's choice. Verified: supjav's voe embed resolves to a real HLS manifest (200, application/vnd.apple.mpegurl, #EXTM3U). WebView stays only as last-resort fallback. Co-Authored-By: Claude Opus 4.8 --- app/extractors/tubes/supjav.py | 10 +++++++++- mobile/src/changelog.ts | 7 +++++++ mobile/src/screens/PlayerScreen.tsx | 10 ++++++++++ mobile/src/screens/SceneDetailScreen.tsx | 6 +++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/extractors/tubes/supjav.py b/app/extractors/tubes/supjav.py index bace464..38c172c 100644 --- a/app/extractors/tubes/supjav.py +++ b/app/extractors/tubes/supjav.py @@ -98,15 +98,23 @@ def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | Non continue seen.add(hoster) host = hoster.split("/")[2] if "://" in hoster else hoster + # Label serwera bez cyfr (host "fc2stream.tv" psułby _quality_to_int → "2" jako + # jakość → zły `best`). Zostawiamy skróconą, bezcyfrową nazwę do pickera. + label = re.sub(r"\.[a-z]+$", "", host.split(".")[0]) if host else None + label = re.sub(r"\d+", "", label).upper() if label else None out.append( StreamSource( link=hoster, type="hoster", - quality=host, + quality=label or None, referer="https://supjav.com/", ) ) if not out: log.info("supjav: żaden data-link nie rozwiązał się na hoster (%s)", page_url) return None + # voe.sx PIERWSZY (sort po URL, niezależnie od label) — telefon resolwuje go do + # bezpośredniego m3u8 (voeResolver.ts, phone-IP-bound → gra direct, zero WebView). + # Reszta (turbovid/fc2stream/streamtape) bez phone-side resolvera → WebView, więc niżej. + out.sort(key=lambda s: 0 if "//voe." in s.link or ".voe." in s.link else 1) return out diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 1b41963..504bb16 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -16,6 +16,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-07-11', + date: 'July 2026', + items: [ + 'JAV and other voe-hosted videos now play directly in the built-in player instead of falling back to the web view, faster start and smoother playback.', + ], + }, { id: '2026-07-10b', date: 'July 2026', diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index bc61a82..70c82d6 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -1389,6 +1389,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { const { isDoodStream, resolveDoodStream } = await import('../lib/doodstream'); const { isPackerHoster, resolvePackerHoster } = await import('../lib/packerHoster'); const { isFilemoonHoster, resolveFilemoonHoster } = await import('../lib/filemoonHoster'); + const { isVoeUrl, resolveVoePage } = await import('../lib/voeResolver'); let result: { url?: string; headers?: Record; error?: string } | null = null; if (isDoodStream(url)) { @@ -1400,6 +1401,15 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { } else if (isFilemoonHoster(url)) { setResolveStatus('pending'); result = await resolveFilemoonHoster(url, source); + } else if (isVoeUrl(url)) { + // voe.sx (supjav i inne): resolwujemy NA TELEFONIE do direct m3u8 (token bound + // do /16 telefonu → gra direct, zero WebView/proxy). Pusto → WebView fallback. + setResolveStatus('pending'); + const voeLinks = await resolveVoePage(url); + const best = voeLinks[0]; + if (best) { + result = { url: best.direct_url || best.stream_url, headers: best.headers }; + } } setResolveAttempted(true); diff --git a/mobile/src/screens/SceneDetailScreen.tsx b/mobile/src/screens/SceneDetailScreen.tsx index 61f7ec0..3d632e0 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -665,7 +665,11 @@ function PlaybackButton({ // Player JS hostera może też ujawnić finalny m3u8 — auto-extract w PlayerScreen // pokaże banner "Otwórz w native playerze". if (directLinks.length === 0) { - const target = res.best?.embed_url || embedLinks[0].embed_url!; + // Preferuj PIERWSZY embed z listy (ekstraktor sortuje resolvowalne natywnie + // hostery na przód, np. supjav → voe.sx pierwszy → Player resolwuje go do direct + // m3u8 zamiast WebView). res.best bywa gorszy (multi-hoster: _pick_best nie wie + // który jest phone-resolvowalny). Pozostałe hostery są w quality-pickerze. + const target = embedLinks[0]?.embed_url || res.best?.embed_url!; let refererHost: string | undefined; try { refererHost = new URL(source.page_url).hostname;