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
index.html is revalidated on every load, but the assets it names are not
under our control once they leave the origin: Cloudflare rewrites our
`Cache-Control: no-cache` on /static/* to `max-age=14400`, so a phone --
an iOS home-screen app above all, which keeps running whatever it has --
can execute four-hour-old JavaScript after a deploy.
The URLs now carry the file's content hash (static/js/main.js?v=<hash>),
reusing the manifest /api/version already computes, so every deploy asks
for URLs no cache can answer from an old copy. index.html itself gets an
explicit no-cache, must-revalidate.
Verified: served HTML carries per-file hashes, a changed file yields a
new URL, the app boots clean, and the refresh button's update path still
hot-swaps CSS and reloads for JS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
Streaming a page URL makes /api/stream re-run yt-dlp on every request --
slow, and a 500 on some sites -- so the player and the reels feed now
wait for App.videos.ensureFormats() when an item has no formats
(favorites, or a card clicked before its hover-resolve landed) and play a
real media URL with the extractor's headers, the same path a hovered card
takes. Favorites' download does the same. The page URL survives only as a
last resort when resolution yields nothing.
Two things in the proxy kept this site broken either way:
heavyfetish serves media from paths with a trailing slash
(/get_file/.../11097_720p.mp4/), which missed every extension test in
stream_video and sent even a resolved media URL down the yt-dlp branch --
a full extraction per request, including every seek.
Its CDN (st17.heavyfetish.com) also serves a certificate that expired
2026-02-16, so the upstream fetch failed verification and returned 500.
A browser can't play such a host at all, which is much of why this proxy
exists, so impersonate_get() now retries once without verification, logs
it, and remembers the host so the doomed handshake isn't repeated for
every range request. STREAM_TLS_VERIFY_ONLY=1 restores the hard failure.
Verified in headless Chrome against the real site: a favorite holding
only the page URL now resolves, streams (206, video/mp4, duration
3167.8s, readyState 4, no error) and shows "720p mp4 | 480p mp4" in the
quality menu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd