{"record":{"id":"aba1670335fe1475","repo":"tonhowtf/omniget","slug":"failed-to-fetch-m3u8-after-attempts","errorCode":null,"errorMessage":"Failed to fetch m3u8 after {} attempts","messagePattern":"Failed to fetch m3u8 after (.+?) attempts","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":237,"sourceCode":"                        last_err =\n                            Some(anyhow::anyhow!(\"HTTP {} fetching playlist\", resp.status()));\n                    } else {\n                        match resp.text().await {\n                            Ok(text) => return Ok(text),\n                            Err(e) => last_err = Some(anyhow::anyhow!(e)),\n                        }\n                    }\n                }\n                Err(e) => last_err = Some(anyhow::anyhow!(e)),\n            }\n            if attempt < max_retries - 1 {\n                let base = 500 * (attempt as u64 + 1);\n                let jitter = rand::random::<u64>() % (base / 2 + 1);\n                tokio::time::sleep(Duration::from_millis(base + jitter)).await;\n            }\n        }\n        Err(last_err.unwrap_or_else(|| {\n            anyhow::anyhow!(\"Failed to fetch m3u8 after {} attempts\", max_retries)\n        }))\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    async fn download_media_playlist(\n        &self,\n        m3u8_url: &str,\n        output_path: &str,\n        referer: &str,\n        bytes_tx: Option<UnboundedSender<u64>>,\n        cancel_token: CancellationToken,\n        max_concurrent: u32,\n        max_retries: u32,\n    ) -> anyhow::Result<HlsDownloadResult> {\n        // 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) {","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L219-L255","documentation":"This is the terminal error of fetch_m3u8_with_retry: after max_retries attempts all failed, and if no specific last_err was recorded the generic message naming the attempt count is returned. It means the playlist could not be fetched despite retries with exponential-ish backoff and jitter.","triggerScenarios":"All attempts to GET the m3u8 failed (network errors or non-success statuses) across max_retries; last_err was None only in degenerate cases (max_retries=0).","commonSituations":"Extended network outage; host hard-blocking the client; URL expired before the first attempt; overly small max_retries with a flaky CDN.","solutions":["Inspect the retried sub-errors (log last_err each attempt) to see the real cause (status vs transport).","Increase max_retries and backoff for unreliable hosts.","Re-obtain a fresh playlist URL before retrying the whole operation.","Verify network/DNS/proxy connectivity to the stream host."],"exampleFix":"// before\nlet playlist = fetch_m3u8_with_retry(client, url, referer, 3).await?;\n// after\nlet playlist = fetch_m3u8_with_retry(client, url, referer, 5)\n    .await\n    .with_context(|| format!(\"fetching playlist {url}\"))?;","handlingStrategy":"retry","validationCode":"// Ensure retries are configured before calling\nlet max_retries = max_retries.max(3);","typeGuard":null,"tryCatchPattern":"let text = fetch_m3u8_with_retry(client, url, referer, 5).await\n    .with_context(|| format!(\"m3u8 fetch failed after retries: {url}\"))?;","preventionTips":["Log each attempt's error so the final 'after N attempts' message isn't opaque.","Refresh the stream URL between retry rounds for expiring tokens.","Use larger backoff with jitter for rate-limited hosts.","Check network/proxy health before concluding the host is down."],"tags":["hls","m3u8","retry","exhausted-retries"],"backgroundTag":"request-failed-after-retries","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"}