youtube: fall back to VISIONOS primary when ANDROID is bot-walled
Some checks failed
gitleaks / scan (push) Failing after 5s
Some checks failed
gitleaks / scan (push) Failing after 5s
YouTube's 2026 poToken/BotGuard enforcement now rejects the pot-less ANDROID client outright (playabilityStatus "sign in to confirm you're not a bot"), which aborted the whole extraction since ANDROID was the required primary. stream_info_with() now cascades ANDROID -> VISIONOS for the primary player response: when ANDROID is unusable (fetch error, failed playability, or decoy), retry with VISIONOS (client 101) as the primary. VISIONOS is pot-free, visitorData-based, and needs no JS player -- the current survivor (yt-dlp default; NewPipeExtractor #1508). Only if VISIONOS also fails do we surface the ORIGINAL ANDROID error (keeps the real bot-wall reason visible). The fallback drops the (poisoned) visitorData cache first so VISIONOS mints a fresh one (the ingredient that passes attestation). On the VISIONOS-primary path the primary source carries no poToken (null) and uses the VISIONOS cpn, so no ANDROID pot/cpn is appended to a visionOS URL (would 403); the redundant add-on VISIONOS fetch is skipped. Output shape is unchanged (still the muxed itag-18 the app plays) -- no app or delivery-labeling change. 5 new unit tests.
This commit is contained in:
parent
a21a6e14ab
commit
dd964407cc
3 changed files with 421 additions and 23 deletions
50
examples/live_extract.rs
Normal file
50
examples/live_extract.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Throwaway live diagnostic (playback-outage triage). Not shipped.
|
||||
use std::sync::Arc;
|
||||
use strawcore_core::downloader::ReqwestDownloader;
|
||||
use strawcore_core::youtube::stream_extractor::{stream_info_with, ExtractOptions};
|
||||
use strawcore_core::NewPipe;
|
||||
|
||||
fn client_of(url: &str) -> String {
|
||||
url.split('&')
|
||||
.find_map(|kv| kv.strip_prefix("c="))
|
||||
.unwrap_or("?")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn report(label: &str, vid: &str, visionos: bool) {
|
||||
let opts = ExtractOptions {
|
||||
fetch_visionos_client: visionos,
|
||||
..Default::default()
|
||||
};
|
||||
match stream_info_with(vid, opts) {
|
||||
Ok(i) => {
|
||||
let a = i.audio_streams.first().map(|s| client_of(&s.url));
|
||||
let v = i
|
||||
.video_streams
|
||||
.first()
|
||||
.or_else(|| i.video_only_streams.first())
|
||||
.map(|s| client_of(&s.url));
|
||||
println!(
|
||||
"[{label}] OK audio={} video={} video_only={} dash={} hls={} | first audio c={:?} first video c={:?}",
|
||||
i.audio_streams.len(),
|
||||
i.video_streams.len(),
|
||||
i.video_only_streams.len(),
|
||||
i.dash_manifest_url.is_some(),
|
||||
i.hls_manifest_url.is_some(),
|
||||
a,
|
||||
v,
|
||||
);
|
||||
}
|
||||
Err(e) => println!("[{label}] ERR {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let dl = Arc::new(ReqwestDownloader::new().expect("downloader"));
|
||||
NewPipe::init(dl);
|
||||
let vid = std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "dQw4w9WgXcQ".into());
|
||||
report("visionOS ON (shipped)", &vid, true);
|
||||
report("visionOS OFF (like 91) ", &vid, false);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue