mobile: recover from mid-playback decode/seek errors (doply NAL)
Bug f6c86847/b1b5e1a2: doply/playmogo plays fine but seeking throws "source error, invalid NAL length" in ExoPlayer. Investigation (cross-IP, 2026-06-01) showed the stream is well-formed — faststart MP4 (moov before mdat) on cloudatacdn.com which fully supports HTTP range (206, correct content-range, repeatable token, no redirect). So it is an ExoPlayer-internal seek failure, not an HTTP/container problem, and expo-video exposes no extractor/MIME hint to influence it. Mitigation: when the native player errors *after* it had already loaded (i.e. a mid-playback/seek failure, not an initial-load failure) and the error is not a 404/410, recreate the source via player.replace() and resume at the last known position — this opens a fresh connection and re-parses moov, which typically clears the transient decode error. Hard-capped at 2 attempts per mount to avoid any auto-reload loop; if it still fails it falls through to the existing proxy/WebView fallback and error UI. Initial-load errors are untouched, so the resolver and the ~59k working doply sources are unaffected. Also thread playbackId/entityKind through the resolved-hoster and proxy/WebView nav.replace calls so those paths get the 404 "Mark broken" affordance too, and complete the local RouteParams type with headers/fallbackProxyUrl. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
967123d5d6
commit
7b2f093d85
1 changed files with 48 additions and 1 deletions
|
|
@ -39,6 +39,8 @@ interface RouteParams {
|
|||
refererHost?: string;
|
||||
title?: string;
|
||||
mode?: 'video' | 'webview';
|
||||
headers?: Record<string, string>;
|
||||
fallbackProxyUrl?: string;
|
||||
fallbackEmbedUrl?: string;
|
||||
}
|
||||
|
||||
|
|
@ -179,14 +181,53 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
// Każdy step ma osobną ref żeby nie loopować.
|
||||
const didFallbackProxyRef = React.useRef(false);
|
||||
const didFallbackWebViewRef = React.useRef(false);
|
||||
// Seek/decode recovery (bug f6c86847: doply/playmogo „invalid NAL length” przy
|
||||
// przewijaniu). Stream jest poprawny — faststart MP4, CDN wspiera Range 206
|
||||
// (zweryfikowane 2026-06-01 cross-IP) — więc to wewnętrzny błąd seeka ExoPlayera,
|
||||
// nie HTTP. `replace(source)` otwiera świeże połączenie + re-parsuje moov; resume
|
||||
// na ostatniej dobrej pozycji ratuje playback. Twardy limit 2 prób / mount żeby
|
||||
// NIE wpaść w auto-loop. Tylko gdy player JUŻ grał (post-load, czyli seek-error,
|
||||
// nie initial-load) i błąd nie jest 404/410.
|
||||
const loadedOnceRef = React.useRef(false);
|
||||
const lastGoodPositionRef = React.useRef(0);
|
||||
const seekRecoveryRef = React.useRef(0);
|
||||
React.useEffect(() => {
|
||||
if (status === 'readyToPlay') loadedOnceRef.current = true;
|
||||
}, [status]);
|
||||
React.useEffect(() => {
|
||||
if (status !== 'error') return;
|
||||
// Step 0: post-load decode/seek error → recover in-place (przed proxy/WebView,
|
||||
// które są dla INITIAL-load errorów IP-bound CDN).
|
||||
if (
|
||||
loadedOnceRef.current &&
|
||||
seekRecoveryRef.current < 2 &&
|
||||
!isGoneError(playerError?.message)
|
||||
) {
|
||||
seekRecoveryRef.current += 1;
|
||||
const resumeAt = lastGoodPositionRef.current;
|
||||
try {
|
||||
player.replace(source);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (resumeAt > 0) player.currentTime = resumeAt;
|
||||
player.play();
|
||||
} catch {
|
||||
// disposed/za wcześnie — następny error tick spróbuje znów (do limitu)
|
||||
}
|
||||
}, 700);
|
||||
} catch {
|
||||
// replace failed — przepuść do fallback chain niżej
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Step 1 → 2: direct fail (403/410/etc), spróbuj proxy URL.
|
||||
if (fallbackProxyUrl && !didFallbackProxyRef.current && url !== fallbackProxyUrl) {
|
||||
didFallbackProxyRef.current = true;
|
||||
nav.replace('Player', {
|
||||
url: fallbackProxyUrl,
|
||||
sceneId,
|
||||
playbackId: params.playbackId,
|
||||
entityKind,
|
||||
durationSec,
|
||||
refererHost,
|
||||
title,
|
||||
|
|
@ -203,13 +244,15 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
nav.replace('Player', {
|
||||
url: fallbackEmbedUrl,
|
||||
sceneId,
|
||||
playbackId: params.playbackId,
|
||||
entityKind,
|
||||
durationSec,
|
||||
refererHost,
|
||||
title,
|
||||
mode: 'webview',
|
||||
});
|
||||
}
|
||||
}, [status, fallbackProxyUrl, fallbackEmbedUrl, url, nav, sceneId, durationSec, refererHost, title]);
|
||||
}, [status, fallbackProxyUrl, fallbackEmbedUrl, url, nav, sceneId, durationSec, refererHost, title, player, source, playerError]);
|
||||
|
||||
const lastReportedRef = React.useRef(0);
|
||||
// Lokalny tick co 500ms — driver dla custom scrubber + time labels. expo-video
|
||||
|
|
@ -221,6 +264,8 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
try {
|
||||
const pos = player.currentTime || 0;
|
||||
setPosition(pos);
|
||||
// Ostatnia „dobra” pozycja dla seek-recovery (tylko gdy faktycznie gra).
|
||||
if (player.playing && pos > 0) lastGoodPositionRef.current = pos;
|
||||
const dur = player.duration || 0;
|
||||
if (dur > 0 && Math.abs(dur - knownDuration) > 0.5) setKnownDuration(dur);
|
||||
const posInt = Math.floor(pos);
|
||||
|
|
@ -1124,6 +1169,8 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
nav.replace('Player', {
|
||||
url: result.url,
|
||||
sceneId,
|
||||
playbackId: params.playbackId,
|
||||
entityKind,
|
||||
durationSec,
|
||||
refererHost,
|
||||
headers: result.headers,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue