feat(telemetry): playback_events.via, skad przyszedl adres w odtwarzaczu
Po przejsciu porndoe na resolver po stronie telefonu 35 urzadzen dalej dostawalo wylacznie 403 i nie dalo sie odroznic, czy pada nowa droga, zapas przez WebView, czy stara paczka. Apka wysyla teraz via: phone, backend, hoster, webview, proxy, movie; '-re' po ponownym rozwiazaniu w tym samym ekranie, '<droga>>wv' gdy adres wyciagnal WebView jako zapas. NULL = paczka sprzed tej zmiany. Migracja 0053 (kolumna nullable, sama zmiana metadanych). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
189dbdf17d
commit
2feef620a1
8 changed files with 77 additions and 5 deletions
32
alembic/versions/20261006_0053_playback_events_via.py
Normal file
32
alembic/versions/20261006_0053_playback_events_via.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""playback_events.via: skad przyszedl adres, ktory gral (albo padl) w odtwarzaczu.
|
||||
|
||||
Po przejsciu porndoe na resolver po stronie telefonu (2026-10-02) 35 urzadzen dalej
|
||||
dostawalo wylacznie 403 i nie dalo sie odroznic, czy pada nowa droga (link pobrany na
|
||||
telefonie), zapas przez WebView, czy stara paczka. Apka wysyla teraz `via`
|
||||
('phone', 'backend', 'hoster', 'webview', 'proxy', z koncowka '-re' po ponownym
|
||||
rozwiazaniu); NULL = paczka sprzed tej zmiany.
|
||||
|
||||
Kolumna bez DEFAULT, nullable: ADD COLUMN to sama zmiana metadanych.
|
||||
|
||||
Revision ID: 0053_playback_events_via
|
||||
Revises: 0052_clips_feed_counters
|
||||
Create Date: 2026-10-06
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0053_playback_events_via"
|
||||
down_revision: str | None = "0052_clips_feed_counters"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("SET LOCAL lock_timeout = '5s'")
|
||||
op.execute("ALTER TABLE playback_events ADD COLUMN IF NOT EXISTS via varchar(16)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("SET LOCAL lock_timeout = '5s'")
|
||||
op.execute("ALTER TABLE playback_events DROP COLUMN IF EXISTS via")
|
||||
|
|
@ -32,6 +32,9 @@ class PlaybackEventIn(BaseModel):
|
|||
# padów których nie da się odtworzyć na emulatorze (np. watchporn player_error).
|
||||
error_detail: str | None = Field(default=None, max_length=512)
|
||||
ttff_ms: int | None = Field(default=None, ge=0, le=600_000)
|
||||
# Skad przyszedl adres w odtwarzaczu (patrz PlaybackEvent.via). Opcjonalne:
|
||||
# starsze paczki go nie wysylaja.
|
||||
via: str | None = Field(default=None, max_length=16)
|
||||
|
||||
|
||||
@router.post("", status_code=204)
|
||||
|
|
@ -49,6 +52,7 @@ def post_playback_event(
|
|||
error_detail=body.error_detail if body.status == "error" else None,
|
||||
ttff_ms=body.ttff_ms if body.status == "success" else None,
|
||||
device_id=device_id,
|
||||
via=body.via,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ class PlaybackEvent(Base):
|
|||
error_detail: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
# time-to-first-frame [ms], "szybkość" (tylko przy success; None gdy nie zmierzono).
|
||||
ttff_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
# Skad przyszedl adres w odtwarzaczu: 'phone', 'backend', 'hoster', 'webview',
|
||||
# 'proxy' (z '-re' po ponownym rozwiazaniu). NULL = paczka sprzed 2026-10-06.
|
||||
via: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
||||
device_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), nullable=False
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ export class GoonClient {
|
|||
error_kind?: string;
|
||||
error_detail?: string;
|
||||
ttff_ms?: number;
|
||||
via?: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await this.request('/playback-events', {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ export type RootStackParamList = {
|
|||
// się automatycznie na WebView z tym URL'em, tam JS embed page sam pobiera
|
||||
// m3u8 we własnym kontekście (Cookies + JA3 + IP set ok).
|
||||
fallbackEmbedUrl?: string;
|
||||
// Skad przyszedl adres (telemetria playback_events.via): 'phone' (resolver na
|
||||
// telefonie w SceneDetail), 'backend', 'hoster' (resolver hostera w Playerze),
|
||||
// 'webview' (wyciagniety ze strony), 'proxy' (zapasowe proxy), 'movie'.
|
||||
via?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ function WatchChip({
|
|||
const pDirect = p.direct_url;
|
||||
const pIsDirect = !!pDirect && pDirect !== p.stream_url;
|
||||
navigation.navigate('Player', {
|
||||
via: 'movie',
|
||||
url: pDirect || p.stream_url || p.embed_url || pb.page_url,
|
||||
origin: pb.origin,
|
||||
sceneId: movieId,
|
||||
|
|
@ -239,6 +240,7 @@ function WatchChip({
|
|||
const best = links[0];
|
||||
if (best && (best.direct_url || best.stream_url)) {
|
||||
navigation.navigate('Player', {
|
||||
via: 'movie',
|
||||
url: best.direct_url || best.stream_url!,
|
||||
origin: pb.origin,
|
||||
sceneId: movieId,
|
||||
|
|
@ -290,6 +292,7 @@ function WatchChip({
|
|||
const target = bestDirect || res.best?.stream_url || res.best?.embed_url || pb.page_url;
|
||||
const fallbackEmbed = res.best?.embed_url || pb.embed_url || pb.page_url;
|
||||
navigation.navigate('Player', {
|
||||
via: 'movie',
|
||||
url: target,
|
||||
origin: pb.origin,
|
||||
// sceneId pozostaje nazwą param-u (legacy z kiedy Player obsługiwał tylko sceny),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ interface RouteParams {
|
|||
headers?: Record<string, string>;
|
||||
fallbackProxyUrl?: string;
|
||||
fallbackEmbedUrl?: string;
|
||||
// Skad przyszedl adres, do telemetrii (patrz RootStackParamList.Player.via).
|
||||
via?: string;
|
||||
}
|
||||
|
||||
// Wspólny helper dla native + WebView ekranu błędu: oznacza playback source jako
|
||||
|
|
@ -264,6 +266,8 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
// przyciskow siedzi z boku, stad tez lewy i prawy.
|
||||
const insets = useSafeAreaInsets();
|
||||
const { url, sceneId, origin: playOrigin, resolvePageUrl, entityKind, durationSec, refererHost, title, fallbackEmbedUrl, headers: paramHeaders, fallbackProxyUrl } = params;
|
||||
// Skad przyszedl adres, do telemetrii; ponowne rozwiazanie w tym samym ekranie dopisuje '-re'.
|
||||
const viaRef = React.useRef(params.via || 'other');
|
||||
const { markBroken, canMark, busy: markBusy } = useMarkSourceBroken(params);
|
||||
// 'movie' → /movies/{id}/progress, 'scene' (default) → /scenes/{id}/progress.
|
||||
const upsertProgress = React.useCallback(
|
||||
|
|
@ -517,6 +521,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
if (!cancelled && freshUrl && freshUrl !== url) {
|
||||
player.replace(fresh?.headers ? { uri: freshUrl, headers: fresh.headers } : freshUrl);
|
||||
player.play();
|
||||
viaRef.current = `${viaRef.current.replace(/-re$/, '')}-re`;
|
||||
return; // sukces → status zmieni się z 'error', łańcuch fallback nie ruszy
|
||||
}
|
||||
// Phone-resolve zadziałał, ale dał TEN SAM URL, czyli strona parsuje się dobrze,
|
||||
|
|
@ -534,6 +539,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
.find((u): u is string => !!u && u !== url);
|
||||
if (!cancelled && viaProxy) {
|
||||
player.replace(viaProxy);
|
||||
viaRef.current = 'proxy-re';
|
||||
player.play();
|
||||
return;
|
||||
}
|
||||
|
|
@ -605,6 +611,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
headers: undefined,
|
||||
fallbackProxyUrl: undefined, // już użyty
|
||||
fallbackEmbedUrl, // zostawiamy do step 3
|
||||
via: 'proxy',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -621,6 +628,8 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
refererHost,
|
||||
title,
|
||||
mode: 'webview',
|
||||
// Droga, ktora wlasnie padla; WebView dopisze do niej '>wv', gdy wyciagnie adres.
|
||||
via: viaRef.current,
|
||||
});
|
||||
}
|
||||
}, [status, fallbackProxyUrl, fallbackEmbedUrl, url, nav, sceneId, durationSec, refererHost, title, player, source, playerError, resolvePageUrl, reResolveApplicable, reResolveDone, playOrigin]);
|
||||
|
|
@ -642,6 +651,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
status: 'success',
|
||||
scene_id: sceneId,
|
||||
ttff_ms: Date.now() - (playTelemetryMountRef.current ?? Date.now()),
|
||||
via: viaRef.current,
|
||||
});
|
||||
}
|
||||
}, [status, playOrigin, client, sceneId]);
|
||||
|
|
@ -658,6 +668,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
scene_id: sceneId,
|
||||
error_kind: isGoneError(playerError?.message) ? 'gone' : 'player_error',
|
||||
error_detail: playerError?.message ? String(playerError.message).slice(0, 512) : undefined,
|
||||
via: viaRef.current,
|
||||
});
|
||||
}, [status, playOrigin, fallbackProxyUrl, fallbackEmbedUrl, url, playerError, client, sceneId]);
|
||||
|
||||
|
|
@ -1855,6 +1866,9 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
[client, sceneId, entityKind],
|
||||
);
|
||||
const [extractedUrl, setExtractedUrl] = React.useState<string | null>(null);
|
||||
// Telemetria: adres wyciagniety z tej strony. Gdy WebView byl zapasem po padnietym
|
||||
// odtwarzaczu, zostaje slad poprzedniej drogi (np. 'phone>wv').
|
||||
const webViewVia = params.via ? `${params.via}>wv`.slice(0, 16) : 'webview';
|
||||
const [resolveStatus, setResolveStatus] = React.useState<'idle' | 'pending' | 'failed'>('idle');
|
||||
const [resolveError, setResolveError] = React.useState<string | null>(null);
|
||||
const [skipResolve, setSkipResolve] = React.useState(false);
|
||||
|
|
@ -1968,6 +1982,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
refererHost,
|
||||
headers: result.headers,
|
||||
mode: 'video',
|
||||
via: 'hoster',
|
||||
});
|
||||
} else {
|
||||
// Cichy resolver padl. Zamiast slepego zaulka z przyciskiem wchodzimy OD RAZU do
|
||||
|
|
@ -2098,8 +2113,9 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
durationSec,
|
||||
refererHost: refererHost || (initialHostRef.current || undefined),
|
||||
mode: 'video',
|
||||
via: webViewVia,
|
||||
});
|
||||
}, [extractedUrl, nav, sceneId, durationSec, refererHost, params.origin, params.title, params.playbackId, entityKind]);
|
||||
}, [extractedUrl, nav, sceneId, durationSec, refererHost, params.origin, params.title, params.playbackId, entityKind, webViewVia]);
|
||||
|
||||
React.useEffect(() => {
|
||||
upsertWatchProgress({ position_sec: 0, duration_sec: durationSec ?? undefined }).catch(
|
||||
|
|
@ -2268,6 +2284,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
durationSec,
|
||||
refererHost,
|
||||
mode: 'video',
|
||||
via: webViewVia,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -566,6 +566,8 @@ function PlaybackButton({
|
|||
const { defaultQuality } = usePreferences();
|
||||
const [resolving, setResolving] = React.useState(false);
|
||||
const [qualityLinks, setQualityLinks] = React.useState<StreamLink[] | null>(null);
|
||||
// Skad pochodza linki w pickerze jakosci (telemetria via): resolver na telefonie czy serwer.
|
||||
const [qualityVia, setQualityVia] = React.useState<'phone' | 'backend'>('backend');
|
||||
|
||||
// Auto-quality: 1 link → graj; wiele → spróbuj dopasować domyślną jakość usera
|
||||
// (null gdy 'auto'/brak dopasowania → pokaż picker jak dotąd). markStarted zostaje
|
||||
|
|
@ -616,6 +618,7 @@ function PlaybackButton({
|
|||
link: StreamLink,
|
||||
fallbackEmbedUrl?: string,
|
||||
allLinks?: StreamLink[],
|
||||
via: 'phone' | 'backend' = 'backend',
|
||||
) => {
|
||||
let refererHost: string | undefined;
|
||||
try {
|
||||
|
|
@ -657,6 +660,7 @@ function PlaybackButton({
|
|||
headers: isDirect && link.headers ? link.headers : undefined,
|
||||
fallbackProxyUrl: isDirect ? link.stream_url || undefined : undefined,
|
||||
fallbackEmbedUrl,
|
||||
via,
|
||||
// Pozostałe jakości → przełącznik w playerze (bez powrotu do listy źródeł).
|
||||
qualityLinks: (allLinks && allLinks.length > 1)
|
||||
? allLinks
|
||||
|
|
@ -711,8 +715,11 @@ function PlaybackButton({
|
|||
if (links.length > 0) {
|
||||
markStarted();
|
||||
const pick = pickAuto(links);
|
||||
if (pick) await openAsVideo(pick, source.page_url);
|
||||
else setQualityLinks(links);
|
||||
if (pick) await openAsVideo(pick, source.page_url, undefined, 'phone');
|
||||
else {
|
||||
setQualityVia('phone');
|
||||
setQualityLinks(links);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// pusto → spadnij na backend resolve poniżej (HosterDead → mark dead dla
|
||||
|
|
@ -792,9 +799,10 @@ function PlaybackButton({
|
|||
const autoPick = pickAuto(directLinks);
|
||||
if (autoPick) {
|
||||
markStarted();
|
||||
await openAsVideo(autoPick, fallbackEmbedUrl, directLinks);
|
||||
await openAsVideo(autoPick, fallbackEmbedUrl, directLinks, 'backend');
|
||||
return;
|
||||
}
|
||||
setQualityVia('backend');
|
||||
setQualityLinks(directLinks);
|
||||
} catch (e) {
|
||||
// 410 = backend zaznaczył link jako PERMANENT dead (HosterDead exception
|
||||
|
|
@ -855,7 +863,7 @@ function PlaybackButton({
|
|||
// direct link dał 403. `qualityLinks` zawiera tylko stream_url-e, więc
|
||||
// szukamy embed'a w `links` na resolve response wcześniej; tu już go nie
|
||||
// mamy, ale to kompromis (rzadki przypadek multi-quality + dead direct).
|
||||
await openAsVideo(link, undefined, qualityLinks || undefined);
|
||||
await openAsVideo(link, undefined, qualityLinks || undefined, qualityVia);
|
||||
} else if (link.embed_url) {
|
||||
let refererHost: string | undefined;
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue