Use 0.11.5 instead of 0.11.4-sulkta.1 so the in-workspace
rustypipe-downloader / rustypipe-cli crates (which require
`rustypipe = ^0.11.4`) keep resolving. The original upstream rev
on codeberg is at 0.11.4; we tag this internal release as 0.11.5
to keep cargo happy without needing to bump dependents.
Smoke-tested against current YT player c2f7551f (May 2026):
test ios_player_returns_streams ........... ok
test default_client_order_returns_streams . ok (audio Range-GET 206 Partial Content, 1024 bytes)
test tv_player_returns_streams ............ ok (or env-skipped on IP-banned egress)
Fork changes since upstream v0.11.4:
- client::ClientType::needs_deobf: skip player.js deobf for Android too
- client::player::player_client_order: prefer iOS first (no botguard),
iOS/Android/Tv/Desktop (with botguard)
- deobfuscate::DeobfData::extract_fns: soft-fail sig_fn/nsig_fn extraction
so Tv/Desktop callers keep working when YT rotates player.js to a shape
our regex doesn't recognise — only the load-bearing sig_timestamp is
required for the request payload
- tests/sulkta_smoke.rs: end-to-end sanity covering iOS, Tv, default-order
and a Range-GET probe to confirm YT actually serves the audio bytes
YouTube googlevideo CDN 403s HEAD requests + 403s requests with a
non-client User-Agent. Use the iOS client UA on the probe so the CDN
treats it as the same client that requested the URL.
Exercises the patched default client_order + soft-fail DeobfData
end-to-end against current YouTube. Verifies:
1. iOS player_from_client returns streams (no deobf path).
2. TV player_from_client returns streams (deobf path with soft-fail).
3. default-clients player() picks iOS primary and a returned audio
URL HEADs to a 2xx/3xx (i.e. YouTube CDN accepts it).
Lives alongside the upstream tests/youtube.rs so we don't fork their
big snapshot-based test suite, but stays standalone so a single
`cargo test --test sulkta_smoke` exercises just the load-bearing
playback path for our consumers (straw, future torttube).
When YouTube rotates player.js to a shape our six sig/nsig regex
patterns don't recognise (eg. c2f7551f, May 2026), the whole player
path used to die at extract_fns even for clients that don't need the
sig fn at all (iOS, Android, Tv all get pre-signed stream URLs).
Now sig_fn / nsig_fn extraction is best-effort. Only the signature
timestamp is required — every `needs_deobf` client needs sts in
the request payload, but the actual deobfuscation functions are only
consumed by map_url when a stream URL carries `&s=` or `&n=`.
On failure we log a warning and store an empty string; Deobfuscator
then skips the JS eval, and any deobfuscate_sig/deobfuscate_nsig
call will fail loudly with "sig fn unavailable" instead of crashing
the player.
Keeps the Tv fallback alive even when sig deobf regex breaks.
Android-only path requires Google device attestation (po_token /
botguard signing). iOS path has neither attestation nor sig deobf
requirements, so it's the cleanest "just works" default. Keep
Android in the rotation only when botguard is wired.
YouTube's Android InnerTube path returns pre-signed stream URLs (no `s=`
cipher param, no `n=` throttling param) just like the iOS path. Mark
Android as deobf-exempt and put it first in the default player client
order so the typical playback path stops fetching player.js entirely.
Avoids the `could not extract sig fn name` failure on YouTube's newer
player.js shapes (eg. c2f7551f).
Desktop stays in the rotation behind botguard for completeness; it
will still try to deobf and may fail, but it's only consulted as a
fallback for botguard-signed sessions now.