tonhowtf/omniget · error · anyhow::Error
Failed to fetch m3u8 after
Error message
Failed to fetch m3u8 after {} attempts What it means
This is the terminal error of fetch_m3u8_with_retry: after max_retries attempts all failed, and if no specific last_err was recorded the generic message naming the attempt count is returned. It means the playlist could not be fetched despite retries with exponential-ish backoff and jitter.
Solutions
- Inspect the retried sub-errors (log last_err each attempt) to see the real cause (status vs transport).
- Increase max_retries and backoff for unreliable hosts.
- Re-obtain a fresh playlist URL before retrying the whole operation.
- Verify network/DNS/proxy connectivity to the stream host.
Example fix
// before
let playlist = fetch_m3u8_with_retry(client, url, referer, 3).await?;
// after
let playlist = fetch_m3u8_with_retry(client, url, referer, 5)
.await
.with_context(|| format!("fetching playlist {url}"))?; Defensive patterns
Strategy: retry
Validate before calling
// Ensure retries are configured before calling let max_retries = max_retries.max(3);
Try / catch
let text = fetch_m3u8_with_retry(client, url, referer, 5).await
.with_context(|| format!("m3u8 fetch failed after retries: {url}"))?; Prevention
- Log each attempt's error so the final 'after N attempts' message isn't opaque.
- Refresh the stream URL between retry rounds for expiring tokens.
- Use larger backoff with jitter for rate-limited hosts.
- Check network/proxy health before concluding the host is down.
When it happens
Trigger: All attempts to GET the m3u8 failed (network errors or non-success statuses) across max_retries; last_err was None only in degenerate cases (max_retries=0).
Common situations: Extended network outage; host hard-blocking the client; URL expired before the first attempt; overly small max_retries with a flaky CDN.
Related errors
- Failed to parse m3u8: neither master nor media playlist
- Parse media playlist
- Failed to fetch AES key after
- Extension playlist is
- Download cancelled by user
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/aba1670335fe1475.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:237
last_err =
Some(anyhow::anyhow!("HTTP {} fetching playlist", resp.status()));
} else {
match resp.text().await {
Ok(text) => return Ok(text),
Err(e) => last_err = Some(anyhow::anyhow!(e)),
}
}
}
Err(e) => last_err = Some(anyhow::anyhow!(e)),
}
if attempt < max_retries - 1 {
let base = 500 * (attempt as u64 + 1);
let jitter = rand::random::<u64>() % (base / 2 + 1);
tokio::time::sleep(Duration::from_millis(base + jitter)).await;
}
}
Err(last_err.unwrap_or_else(|| {
anyhow::anyhow!("Failed to fetch m3u8 after {} attempts", max_retries)
}))
}
#[allow(clippy::too_many_arguments)]
async fn download_media_playlist(
&self,
m3u8_url: &str,
output_path: &str,
referer: &str,
bytes_tx: Option<UnboundedSender<u64>>,
cancel_token: CancellationToken,
max_concurrent: u32,
max_retries: u32,
) -> anyhow::Result<HlsDownloadResult> {
// 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) {View on GitHub (pinned to 8600b91f42)