{"record":{"id":"23f8a065ab468edb","repo":"tonhowtf/omniget","slug":"parse-media-playlist","errorCode":null,"errorMessage":"Parse media playlist: {:?}","messagePattern":"Parse media playlist: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":278,"sourceCode":"                );\n                text.to_string()\n            }\n            None => {\n                let resp = apply_referer_headers(self.client.get(m3u8_url), referer)\n                    .header(\"User-Agent\", self.effective_user_agent())\n                    .send()\n                    .await?;\n\n                if !resp.status().is_success() {\n                    anyhow::bail!(\"HTTP {} fetching playlist\", resp.status());\n                }\n\n                resp.text().await?\n            }\n        };\n\n        let (_, playlist) = parse_media_playlist(text.as_bytes())\n            .map_err(|e| anyhow::anyhow!(\"Parse media playlist: {:?}\", e))?;\n\n        let total_segments = playlist.segments.len();\n\n        let encryption = self\n            .fetch_encryption_info(&playlist, m3u8_url, referer)\n            .await?;\n\n        let output = PathBuf::from(output_path);\n        let part_path = {\n            let mut p = output.as_os_str().to_owned();\n            p.push(\".part\");\n            PathBuf::from(p)\n        };\n        if let Some(parent) = output.parent() {\n            std::fs::create_dir_all(parent)?;\n        }\n\n        let (seg_tx, seg_rx) = mpsc::channel::<(usize, Vec<u8>)>(max_concurrent as usize);","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L260-L296","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet (_, playlist) = parse_media_playlist(text.as_bytes())\n    .map_err(|e| anyhow::anyhow!(\"Parse media playlist: {:?}\", e))?;\n// after\nif text.trim_start().starts_with(\"<\") {\n    anyhow::bail!(\"playlist URL returned HTML (token expired or blocked)\");\n}\nlet (_, playlist) = parse_media_playlist(text.as_bytes())\n    .map_err(|e| anyhow::anyhow!(\"Parse media playlist: {e:?}; head: {:.120}\", text))?;","handlingStrategy":"validation","validationCode":"fn looks_like_media_playlist(text: &str) -> bool {\n    let t = text.trim_start();\n    t.starts_with(\"#EXTM3U\") && !t.contains(\"#EXT-X-STREAM-INF\")\n}\n// call before parsing:\nif !looks_like_media_playlist(&text) {\n    anyhow::bail!(\"not a media playlist (HTML or master playlist?), head: {:.120}\", text);\n}","typeGuard":null,"tryCatchPattern":"let (_, playlist) = parse_media_playlist(text.as_bytes())\n    .map_err(|e| anyhow::anyhow!(\"Parse media playlist: {e:?}\"))\n    .with_context(|| format!(\"playlist head: {:.200}\", text))?;","preventionTips":["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."],"tags":["hls","m3u8","parse-error","playlist"],"backgroundTag":"unexpected-response-shape","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}