tonhowtf/omniget · error · anyhow::Error
Failed to parse m3u8: neither master nor media playlist
Error message
Failed to parse m3u8: neither master nor media playlist
What it means
After fetching the m3u8 text, download_with_quality tries to interpret it as a master playlist and, failing that, as a media playlist. If neither parse succeeds, it concludes the response is not a usable HLS playlist and bails with this error. This is the library's guard against downloading from URLs that return non-HLS content.
Solutions
- Confirm the URL is the real .m3u8 playlist (inspect browser devtools Network tab for the media/manifest request) and use that URL.
- Fetch the URL manually (curl with the same headers/referer/User-Agent) and check the body starts with #EXTM3U; if you get HTML, the endpoint needs authentication, different headers, or is blocking the client.
- If the stream requires cookies or referer headers, supply them to the downloader so the origin returns the playlist rather than an error page.
- Check the m3u8 file for corruption/encoding issues (BOM, HTML entities) if you control the source.
Example fix
// before
hls.download("https://example.com/watch/123", quality, token).await?;
// -> "Failed to parse m3u8: neither master nor media playlist" (page is HTML)
// after: use the playlist URL discovered from the page/devtools
hls.download("https://cdn.example.com/hls/123/index.m3u8", quality, token).await?; Defensive patterns
Strategy: validation
Validate before calling
let text = reqwest::get(url).await?.text().await?;
let is_playlist = text.trim_start().starts_with("#EXTM3U");
if !is_playlist {
eprintln!("URL does not return an m3u8 playlist");
} else {
hls.download(url, quality, token).await?;
} Type guard
fn looks_like_m3u8(body: &str) -> bool {
body.trim_start().starts_with("#EXTM3U")
} Try / catch
match hls.download(url, quality, token).await {
Err(e) if e.to_string().starts_with("Failed to parse m3u8") => {
// re-resolve the real playlist URL from the page, then retry once
},
other => other?,
} Prevention
- Extract the playlist URL from the page/devtools rather than pasting the watch-page URL.
- Send required referer/cookies/User-Agent so CDNs don't return HTML challenge pages.
- Pre-fetch and check for #EXTM3U before handing the URL to the downloader.
- Handle login-gated streams: import cookies before downloading.
When it happens
Trigger: Calling download()/download_with_quality with a URL whose response body is neither a master (#EXTM3U with #EXT-X-STREAM-INF) nor a media playlist: HTML error/login pages, JSON API responses, plain text, binary content, or an m3u8 so malformed the parsers reject it.
Common situations: Passing a webpage URL instead of the actual playlist URL; the origin serves an anti-bot/Cloudflare interstitial or auth page instead of the playlist; expired stream links returning error bodies with 200 status; CDN serving a captive-portal style response.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Parse media playlist
- Failed to fetch m3u8 after
- No valid cookies found in file (expected Netscape format)
- Extension playlist is
- Download cancelled by user
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/fbc7a5e5813eda52.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:195
.await;
}
}
if parse_media_playlist(m3u8_bytes).is_ok() {
return self
.download_media_playlist(
m3u8_url,
output_path,
referer,
bytes_tx,
cancel_token,
max_concurrent,
max_retries,
)
.await;
}
anyhow::bail!("Failed to parse m3u8: neither master nor media playlist")
}
async fn fetch_m3u8_with_retry(
&self,
url: &str,
referer: &str,
max_retries: u32,
) -> anyhow::Result<String> {
if let Some(text) = self.prefetched_for(url) {
tracing::info!(
"[hls] using prefetched playlist text ({} bytes)",
text.len()
);
return Ok(text.to_string());
}
let mut last_err = None;
for attempt in 0..max_retries {View on GitHub (pinned to 8600b91f42)