Don't re-resolve a favorite whose URL is already the media file
Some channels hand back the media URL itself as an item's url. Since the favorites fix, opening one of those sent it to /api/resolve first, so yt-dlp fetched the media just to report the URL we already had. On a signed link (`?secure=<ts>-<token>`) that is a second request against something that may be single-use or IP-bound, and the request that matters -- the playback fetch -- is then refused. Such URLs now play directly, with no resolve round trip, as they did before. Alongside that, three things that make expiry survivable: /api/stream, after its existing referer-less retry, now retries a 403 completely bare (Range only). Signed CDN links are routinely served to a plain browser request and refused when it carries extras -- a `Sec-Fetch-Mode: navigate` on a media subresource, say, which is what yt-dlp's generic extractor hands back and no real player would send. When every source fails, the player re-resolves once and retries instead of giving up, since the likeliest cause is that signed URLs went stale in a long-open tab rather than the video being gone. A manual quality pick is dropped for that retry, as it names one of the URLs that just failed. Favorites stored by older versions still carry a `meta` blob of resolved formats, long expired; it's now stripped on read so nothing can reach for one. Verified: a favorite whose url is a .mp4 plays with zero /api/resolve calls, straight from that URL; playback, prefetch, feed paging, HUD, rotation, momentum and the version check all still pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
@@ -644,6 +644,19 @@ def stream_video():
|
||||
resp.close()
|
||||
referer_less = {k: v for k, v in safe_request_headers.items() if k not in ('Referer', 'Origin')}
|
||||
resp = impersonate_get(target_url, headers=referer_less, stream=True, timeout=30, allow_redirects=True)
|
||||
# Still refused: strip everything the extractor asked us to relay and go
|
||||
# in bare (Range only, plus whatever impersonation supplies). Signed CDN
|
||||
# links are often served fine to a plain browser request and refused when
|
||||
# it carries extras -- a `Sec-Fetch-Mode: navigate` on a media
|
||||
# subresource, say, which is exactly what yt-dlp's generic extractor
|
||||
# hands back and what a real player would never send.
|
||||
if resp.status_code == 403 and len(safe_request_headers) > (1 if 'Range' in safe_request_headers else 0):
|
||||
dbg("upstream still 403; retrying bare (range only)")
|
||||
resp.close()
|
||||
bare = {}
|
||||
if 'Range' in safe_request_headers:
|
||||
bare['Range'] = safe_request_headers['Range']
|
||||
resp = impersonate_get(target_url, headers=bare, stream=True, timeout=30, allow_redirects=True)
|
||||
if debug_enabled:
|
||||
dbg(f"upstream status={resp.status_code} content_type={resp.headers.get('Content-Type')} content_length={resp.headers.get('Content-Length')}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user