tonhowtf/omniget · error · anyhow::Error
HTTP fetching playlist
Error message
HTTP {} fetching playlist What it means
When download_media_playlist fetches a media (non-master) playlist directly, it applies referer/User-Agent headers, sends the request, and checks the HTTP status. Any non-success status is turned into this bail, embedding the status code. The library does not retry non-success statuses here, so a failed playlist fetch aborts the media-playlist download path.
Solutions
- Check the status code in the error message: 404/410 means the playlist is gone — re-fetch a fresh playlist URL from the page or master playlist before retrying.
- For 403, supply the correct Referer, User-Agent, and/or cookies (import via the cookies mechanism) so the CDN accepts the request.
- For 429/5xx, retry with backoff after a delay, or re-run the download later once the server recovers.
- Verify the URL with curl -I using the same headers to confirm the failure is server-side versus a missing-header problem in your client configuration.
Example fix
// before: no referer/cookies on a protected stream
download_media_playlist(url, None, ...).await?; // HTTP 403 Forbidden fetching playlist
// after: provide referer and imported cookies
let referer = Some("https://example.com/watch/123".to_string());
// ensure cookies were imported via import_cookies_file beforehand
download_media_playlist(url, referer, ...).await?; Defensive patterns
Strategy: retry
Validate before calling
let resp = reqwest::Client::new().get(url)
.header("Referer", referer)
.send().await?;
if !resp.status().is_success() {
eprintln!("playlist endpoint returned {} — fix headers/URL first", resp.status());
} Try / catch
// retry with backoff on transient statuses
for attempt in 0..3 {
match download_media_playlist(url, referer.as_deref(), ...).await {
Ok(r) => return Ok(r),
Err(e) if is_retryable(&e.to_string()) => tokio::time::sleep(
std::time::Duration::from_secs(2u64.pow(attempt))).await,
Err(e) => return Err(e),
}
}
fn is_retryable(msg: &str) -> bool {
["429", "500", "502", "503", "504"].iter().any(|s| msg.contains(s))
} Prevention
- Always pass the referer the origin expects; most HLS CDNs check it.
- Import cookies for authenticated streams before downloading.
- Re-fetch fresh playlist URLs shortly before use — HLS links expire fast.
- Back off on 429/5xx instead of hammering the CDN.
When it happens
Trigger: download_with_quality resolves the input to a media playlist and calls download_media_playlist, whose GET via apply_referer_headers(self.client.get(m3u8_url)) returns a non-2xx status: 403 (blocked/expired link, missing referer/cookies), 404 (playlist removed or rotated), 401 (auth required), 5xx (origin/CDN failure), or 429 (rate limiting).
Common situations: HLS stream URLs expire quickly so a copied playlist link returns 403/404 moments later; origin requires a Referer or cookies the client didn't send; CDN under load returns 503; scraping too aggressively triggers 429.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- HTTP (fatal) downloading segment
- HTTP downloading segment
- YouTube não retornou URL
- HTTP
- download falhou: HTTP
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/c3fe767cf6f0f37e.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:270
// The media playlist is fetched a second time here, independently of
// `fetch_m3u8_with_retry`. Skipping this branch would throw the
// prefetched text away and hit the network anyway.
let text = match self.prefetched_for(m3u8_url) {
Some(text) => {
tracing::info!(
"[hls] using prefetched media playlist text ({} bytes)",
text.len()
);
text.to_string()
}
None => {
let resp = apply_referer_headers(self.client.get(m3u8_url), referer)
.header("User-Agent", self.effective_user_agent())
.send()
.await?;
if !resp.status().is_success() {
anyhow::bail!("HTTP {} fetching playlist", resp.status());
}
resp.text().await?
}
};
let (_, playlist) = parse_media_playlist(text.as_bytes())
.map_err(|e| anyhow::anyhow!("Parse media playlist: {:?}", e))?;
let total_segments = playlist.segments.len();
let encryption = self
.fetch_encryption_info(&playlist, m3u8_url, referer)
.await?;
let output = PathBuf::from(output_path);
let part_path = {
let mut p = output.as_os_str().to_owned();View on GitHub (pinned to 8600b91f42)