{"record":{"id":"7dac95cd2addd2ea","repo":"tonhowtf/omniget","slug":"segment-download-failed-after-attempts","errorCode":null,"errorMessage":"Segment download failed after {} attempts","messagePattern":"Segment download failed after (.+?) attempts","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":757,"sourceCode":"\n        match result {\n            Ok(Ok(data)) => return Ok(data),\n            Ok(Err(e)) => {\n                if e.to_string().contains(\"(fatal)\") {\n                    return Err(e);\n                }\n                last_err = Some(e);\n            }\n            Err(_) => last_err = Some(anyhow::anyhow!(\"Timeout downloading segment\")),\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(std::time::Duration::from_millis(base + jitter)).await;\n        }\n    }\n    Err(last_err.unwrap_or_else(|| {\n        anyhow::anyhow!(\"Segment download failed after {} attempts\", max_retries)\n    }))\n}\n\nconst PNG_SIGNATURE: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];\nconst JPEG_SIGNATURE: [u8; 3] = [0xFF, 0xD8, 0xFF];\n\n/// Number of leading bytes to drop from a segment that arrived disguised as an\n/// image.\n///\n/// A handful of CDNs wrap each transport-stream segment in a real PNG or JPEG\n/// file so that naive traffic inspection sees an image download. The media\n/// payload is simply appended after the image ends, so the fix is to find the\n/// image's end-of-file marker and start reading right after it:\n///\n/// * PNG ends with the `IEND` chunk — 4 bytes of type plus a 4-byte CRC32,\n///   hence 8 bytes past the marker;\n/// * JPEG ends with the `FF D9` EOI marker, 2 bytes long.\n///","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L739-L775","documentation":"Terminal error from download_segment_with_retry: after max_retries attempts, the segment still failed (or no error was recorded, in which case this fallback is used). It reports the attempt count so callers know the failure was persistent, not transient. If a non-fatal error was captured, that error is propagated instead; this message appears when last_err was None or as a last-resort wrapper.","triggerScenarios":"All retry attempts of download_segment_with_retry fail — e.g., persistent 5xx/429 responses, repeated timeouts, or connection failures across every attempt within the retry window.","commonSituations":"Extended CDN outage during a download, aggressive rate limiting that outlasts the backoff window, dead segment URLs on a live stream that pruned content, or sustained network outage (offline/VPN drop) during the retries.","solutions":["Catch this at the download_media_playlist level and re-enqueue the failed segment with a longer backoff or fresh playlist fetch.","Increase max_retries and/or the backoff base for flaky networks or rate-limited CDNs.","Reduce parallelism (fewer concurrent segments) if 429s are the root cause.","Inspect the wrapped last_err (propagated error) for the underlying cause before treating it as unrecoverable."],"exampleFix":"// before\nErr(last_err.unwrap_or_else(|| {\n    anyhow::anyhow!(\"Segment download failed after {} attempts\", max_retries)\n}))\n// after\nErr(last_err.unwrap_or_else(|| {\n    anyhow::anyhow!(\"Segment download failed after {} attempts ({})\", max_retries, seg_url)\n}))","handlingStrategy":"try-catch","validationCode":"// at call site: verify the segment is still reachable before the final attempt\nif attempt == max_retries - 1 {\n    if let Ok(resp) = client.head(&seg_url).send().await {\n        log::warn!(\"final attempt: segment {} responds {}\", seg_url, resp.status());\n    }\n}","typeGuard":null,"tryCatchPattern":"match playlist.download_segment(&seg).await {\n    Ok(data) => write_segment(data),\n    Err(e) if e.to_string().contains(\"failed after\") => {\n        // all retries exhausted: re-fetch playlist (URLs may have changed) and requeue once\n        let fresh = fetch_playlist(url).await?;\n        retry_segment_from(&fresh, &seg)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Wrap segment downloads with one playlist re-fetch + requeue cycle before declaring the download failed.","Tune max_retries and backoff base to your network/CDN profile (flaky links need more attempts).","Lower download concurrency when the wrapped error indicates 429/5xx storms.","Persist progress so a failed segment can be resumed on a later run instead of restarting the whole media."],"tags":["hls","retry-exhausted","network","segments"],"backgroundTag":"http-request-failed","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"}