{"record":{"id":"fbc7a5e5813eda52","repo":"tonhowtf/omniget","slug":"failed-to-parse-m3u8-neither-master-nor-media-playlist","errorCode":null,"errorMessage":"Failed to parse m3u8: neither master nor media playlist","messagePattern":"Failed to parse m3u8: neither master nor media playlist","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":195,"sourceCode":"                    .await;\n            }\n        }\n\n        if parse_media_playlist(m3u8_bytes).is_ok() {\n            return self\n                .download_media_playlist(\n                    m3u8_url,\n                    output_path,\n                    referer,\n                    bytes_tx,\n                    cancel_token,\n                    max_concurrent,\n                    max_retries,\n                )\n                .await;\n        }\n\n        anyhow::bail!(\"Failed to parse m3u8: neither master nor media playlist\")\n    }\n\n    async fn fetch_m3u8_with_retry(\n        &self,\n        url: &str,\n        referer: &str,\n        max_retries: u32,\n    ) -> anyhow::Result<String> {\n        if let Some(text) = self.prefetched_for(url) {\n            tracing::info!(\n                \"[hls] using prefetched playlist text ({} bytes)\",\n                text.len()\n            );\n            return Ok(text.to_string());\n        }\n\n        let mut last_err = None;\n        for attempt in 0..max_retries {","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L177-L213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nhls.download(\"https://example.com/watch/123\", quality, token).await?;\n// -> \"Failed to parse m3u8: neither master nor media playlist\" (page is HTML)\n// after: use the playlist URL discovered from the page/devtools\nhls.download(\"https://cdn.example.com/hls/123/index.m3u8\", quality, token).await?;","handlingStrategy":"validation","validationCode":"let text = reqwest::get(url).await?.text().await?;\nlet is_playlist = text.trim_start().starts_with(\"#EXTM3U\");\nif !is_playlist {\n    eprintln!(\"URL does not return an m3u8 playlist\");\n} else {\n    hls.download(url, quality, token).await?;\n}","typeGuard":"fn looks_like_m3u8(body: &str) -> bool {\n    body.trim_start().starts_with(\"#EXTM3U\")\n}","tryCatchPattern":"match hls.download(url, quality, token).await {\n    Err(e) if e.to_string().starts_with(\"Failed to parse m3u8\") => {\n        // re-resolve the real playlist URL from the page, then retry once\n    },\n    other => other?,\n}","preventionTips":["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."],"tags":["hls","m3u8","parse-error","unexpected-content"],"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"}