tonhowtf/omniget · error · anyhow::Error
Parse media playlist
Error message
Parse media playlist: {:?} What it means
After downloading the media playlist text, download_media_playlist parses it with parse_media_playlist. If the bytes are not a valid HLS media playlist, the parse error is wrapped with this message. It usually means the served content is not the expected m3u8 (e.g., an HTML error page or a master playlist where a media playlist was required).
Solutions
- Log the first ~200 bytes of `text` — if it's HTML, the URL/token is wrong or expired; re-fetch a fresh URL.
- If the content is a master playlist (#EXT-X-STREAM-INF), select a variant and re-run with that media playlist URL.
- Check Content-Encoding handling; ensure the client decompresses gzip responses.
- Retry with a fresh playlist after a short delay; live playlists can be temporarily inconsistent.
Example fix
// before
let (_, playlist) = parse_media_playlist(text.as_bytes())
.map_err(|e| anyhow::anyhow!("Parse media playlist: {:?}", e))?;
// after
if text.trim_start().starts_with("<") {
anyhow::bail!("playlist URL returned HTML (token expired or blocked)");
}
let (_, playlist) = parse_media_playlist(text.as_bytes())
.map_err(|e| anyhow::anyhow!("Parse media playlist: {e:?}; head: {:.120}", text))?; Defensive patterns
Strategy: validation
Validate before calling
fn looks_like_media_playlist(text: &str) -> bool {
let t = text.trim_start();
t.starts_with("#EXTM3U") && !t.contains("#EXT-X-STREAM-INF")
}
// call before parsing:
if !looks_like_media_playlist(&text) {
anyhow::bail!("not a media playlist (HTML or master playlist?), head: {:.120}", text);
} Try / catch
let (_, playlist) = parse_media_playlist(text.as_bytes())
.map_err(|e| anyhow::anyhow!("Parse media playlist: {e:?}"))
.with_context(|| format!("playlist head: {:.200}", text))?; Prevention
- Validate the body starts with #EXTM3U before parsing.
- Log a snippet of unexpected bodies to spot HTML error/token pages.
- Ensure gzip/deflate responses are decompressed before parsing.
- Handle master playlists by selecting a variant and re-fetching.
When it happens
Trigger: URL returns HTML (error/login page), returns a master playlist to a media-playlist parser, or the m3u8 content is truncated/malformed or gzip-encoded but not decoded.
Common situations: Tokenized URL expired and server returned an HTML 200 page; wrong playlist level passed (EXT-X-STREAM-INF vs segments); CDN returning an interstitial; mid-stream playlist variant changes.
Related errors
- Failed to parse m3u8: neither master nor media playlist
- HTTP fetching playlist
- Failed to fetch m3u8 after
- No valid cookies found in file (expected Netscape format)
- Extension playlist is
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/23f8a065ab468edb.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:278
);
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();
p.push(".part");
PathBuf::from(p)
};
if let Some(parent) = output.parent() {
std::fs::create_dir_all(parent)?;
}
let (seg_tx, seg_rx) = mpsc::channel::<(usize, Vec<u8>)>(max_concurrent as usize);View on GitHub (pinned to 8600b91f42)