{"record":{"id":"c3fe767cf6f0f37e","repo":"tonhowtf/omniget","slug":"http-fetching-playlist","errorCode":null,"errorMessage":"HTTP {} fetching playlist","messagePattern":"HTTP (.+?) fetching playlist","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":270,"sourceCode":"        // The media playlist is fetched a second time here, independently of\n        // `fetch_m3u8_with_retry`. Skipping this branch would throw the\n        // prefetched text away and hit the network anyway.\n        let text = match self.prefetched_for(m3u8_url) {\n            Some(text) => {\n                tracing::info!(\n                    \"[hls] using prefetched media playlist text ({} bytes)\",\n                    text.len()\n                );\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();","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L252-L288","documentation":"When download_media_playlist fetches a media (non-master) playlist directly, it applies referer/User-Agent headers, sends the request, and checks the HTTP status. Any non-success status is turned into this bail, embedding the status code. The library does not retry non-success statuses here, so a failed playlist fetch aborts the media-playlist download path.","triggerScenarios":"download_with_quality resolves the input to a media playlist and calls download_media_playlist, whose GET via apply_referer_headers(self.client.get(m3u8_url)) returns a non-2xx status: 403 (blocked/expired link, missing referer/cookies), 404 (playlist removed or rotated), 401 (auth required), 5xx (origin/CDN failure), or 429 (rate limiting).","commonSituations":"HLS stream URLs expire quickly so a copied playlist link returns 403/404 moments later; origin requires a Referer or cookies the client didn't send; CDN under load returns 503; scraping too aggressively triggers 429.","solutions":["Check the status code in the error message: 404/410 means the playlist is gone — re-fetch a fresh playlist URL from the page or master playlist before retrying.","For 403, supply the correct Referer, User-Agent, and/or cookies (import via the cookies mechanism) so the CDN accepts the request.","For 429/5xx, retry with backoff after a delay, or re-run the download later once the server recovers.","Verify the URL with curl -I using the same headers to confirm the failure is server-side versus a missing-header problem in your client configuration."],"exampleFix":"// before: no referer/cookies on a protected stream\ndownload_media_playlist(url, None, ...).await?; // HTTP 403 Forbidden fetching playlist\n// after: provide referer and imported cookies\nlet referer = Some(\"https://example.com/watch/123\".to_string());\n// ensure cookies were imported via import_cookies_file beforehand\ndownload_media_playlist(url, referer, ...).await?;","handlingStrategy":"retry","validationCode":"let resp = reqwest::Client::new().get(url)\n    .header(\"Referer\", referer)\n    .send().await?;\nif !resp.status().is_success() {\n    eprintln!(\"playlist endpoint returned {} — fix headers/URL first\", resp.status());\n}","typeGuard":null,"tryCatchPattern":"// retry with backoff on transient statuses\nfor attempt in 0..3 {\n    match download_media_playlist(url, referer.as_deref(), ...).await {\n        Ok(r) => return Ok(r),\n        Err(e) if is_retryable(&e.to_string()) => tokio::time::sleep(\n            std::time::Duration::from_secs(2u64.pow(attempt))).await,\n        Err(e) => return Err(e),\n    }\n}\nfn is_retryable(msg: &str) -> bool {\n    [\"429\", \"500\", \"502\", \"503\", \"504\"].iter().any(|s| msg.contains(s))\n}","preventionTips":["Always pass the referer the origin expects; most HLS CDNs check it.","Import cookies for authenticated streams before downloading.","Re-fetch fresh playlist URLs shortly before use — HLS links expire fast.","Back off on 429/5xx instead of hammering the CDN."],"tags":["http","network","hls","playlist"],"backgroundTag":"http-error-response","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"}