fix(trailer): reject PO-token-gated stream URLs and resolve without the rate-limited watch page - #1831
Open
aneeshtigga wants to merge 1 commit into
Open
fix(trailer): reject PO-token-gated stream URLs and resolve without the rate-limited watch page#1831aneeshtigga wants to merge 1 commit into
aneeshtigga wants to merge 1 commit into
Conversation
…page isUrlReachable probed with range bytes=0-0, which cannot detect PO token gating: ANDROID and IOS adaptive URLs answer the first range and 403 the rest, so dead URLs passed validation and ffmpeg failed with 403 during playback. Probes now check head and tail, gated clients are dropped after one rejection, and resolution falls back to ANDROID progressive itag 18. Extraction also fetched the 1.1-1.4 MB watch page before every player call just to scrape the public INNERTUBE_API_KEY, so a rate limited device failed before reaching a player endpoint that still worked. The constant key is used directly now and the page is a last resort. Also prefers ungated clients, skips the rest once one resolves, refreshes stale client versions, treats UNPLAYABLE as final, and backs off after a 429. Details and measurements in NuvioMedia#1829.
aneeshtigga
force-pushed
the
fix/trailer-resolution
branch
from
August 30, 2026 21:33
5a2901e to
0f6424d
Compare
4 tasks
tapframe
force-pushed
the
cmp-rewrite
branch
from
September 12, 2026 01:09
d177eb5 to
54195bb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Trailers stop failing with
[ffmpeg] https: HTTP error 403 Forbidden, and resolution no longer depends on the watch page that YouTube rate limits first. Reachability probing now detects PO token gated URLs, and resolution falls back through HLS, separate audio/video and finallyANDROIDprogressive itag 18, so a trailer keeps playing at lower quality instead of failing.PR type
Why
Two independent causes, both measured directly and documented in #1857.
1.
isUrlReachableprobes withrange: bytes=0-0, which cannot see PO token gating.ANDROIDandIOSadaptive URLs answer the first range and then reject every later range. This is independent of User-Agent (tested Chrome, Safari,Lavfand each client's own UA) and independent of itag:bytes=0-65535The head range always succeeds, so a dead URL passed validation and mpv/ffmpeg then 403'd on the next range, which is the exact error users see.
2. Extraction required the watch page, which is the first thing YouTube rate limits.
extractPlaybackSourceInternalfetched the watch page (1.1-1.4 MB) before every player call, only to scrapeINNERTUBE_API_KEYandVISITOR_DATA, and threw when that failed. Once the device is flagged the page returns 429 and then a 302 to a 407 byte error page, so extraction died before any player call. Measured at that same moment:MAbjq7ydKDIOKwith and without a key8ZYhuvIv1pAOKwith and without a keyINNERTUBE_API_KEYis a public constant present in every YouTube page (the same value this code already scraped), so it is used directly and the watch page is only consulted if every client returned nothing.Issue or approval
Fixes #1857. Same fix for desktop: NuvioMedia/NuvioDesktop#552 (issue NuvioMedia/NuvioDesktop#584).
(The first filing, #1829, was closed by the unlabeled-issue bot before triage because an outside contributor cannot set labels from the API. #1857 is the same report submitted through the bug template so it carries the
buglabel.)UI / behavior impact
Policy check
CONTRIBUTING.md.What changed
Three files: the shared extractor plus the iOS and Android
TrailerExtractionPlatformactuals.clenis present), so gated URLs are rejected instead of reaching the player. One rejection condemns the rest of that client's adaptive formats, so its siblings are not probed.buildPlaybackSourcenow receives candidate lists rather than one pre-picked candidate per kind, and tries HLS, then separate streams, then progressive, taking the first that probes clean. A video only source is kept as a last resort so a silent trailer never wins over one with audio.visionos,android_vr) are preferred, and the remaining clients are skipped once one of them produced candidates. Typical extraction is one player call instead of three, plus no 1.4 MB page fetch.20.10.35to21.26.364, ios20.10.1to21.26.4, andandroid_vradded. Stale versions attract bot challenges on their own.UNPLAYABLEandERRORare final for that video (geo blocks, removed videos) instead of being retried.Scope boundaries
Intentionally not included:
Testing
Manual, iOS, plus compile checks:
scripts/build-ios-ipa.shand installed it on an iPhone. Trailers play, including titles that previously failed with the ffmpeg 403../gradlew :composeApp:compileKotlinIosArm64and./gradlew :composeApp:compileAndroidMainboth pass.Android was validated by compilation only; I do not have an Android device to hand, and the Android platform change is the same probe and fallback logic as iOS.
Screenshots / Video (UI changes only)
Not a UI change.
Breaking changes
None.
Linked issues
Fixes #1857. Related: NuvioMedia/NuvioDesktop#584, NuvioMedia/NuvioDesktop#552, NuvioMedia/NuvioTV#2451.