tonhowtf/omniget · error · anyhow::Error

Segment download failed after {} attempts

Error message

Segment download failed after {} attempts

What it means

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.

Solutions

  1. Catch this at the download_media_playlist level and re-enqueue the failed segment with a longer backoff or fresh playlist fetch.
  2. Increase max_retries and/or the backoff base for flaky networks or rate-limited CDNs.
  3. Reduce parallelism (fewer concurrent segments) if 429s are the root cause.
  4. Inspect the wrapped last_err (propagated error) for the underlying cause before treating it as unrecoverable.

Example fix

// before
Err(last_err.unwrap_or_else(|| {
    anyhow::anyhow!("Segment download failed after {} attempts", max_retries)
}))
// after
Err(last_err.unwrap_or_else(|| {
    anyhow::anyhow!("Segment download failed after {} attempts ({})", max_retries, seg_url)
}))
Defensive patterns

Strategy: try-catch

Validate before calling

// at call site: verify the segment is still reachable before the final attempt
if attempt == max_retries - 1 {
    if let Ok(resp) = client.head(&seg_url).send().await {
        log::warn!("final attempt: segment {} responds {}", seg_url, resp.status());
    }
}

Try / catch

match playlist.download_segment(&seg).await {
    Ok(data) => write_segment(data),
    Err(e) if e.to_string().contains("failed after") => {
        // all retries exhausted: re-fetch playlist (URLs may have changed) and requeue once
        let fresh = fetch_playlist(url).await?;
        retry_segment_from(&fresh, &seg)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/7dac95cd2addd2ea. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:757

        match result {
            Ok(Ok(data)) => return Ok(data),
            Ok(Err(e)) => {
                if e.to_string().contains("(fatal)") {
                    return Err(e);
                }
                last_err = Some(e);
            }
            Err(_) => last_err = Some(anyhow::anyhow!("Timeout downloading segment")),
        }
        if attempt < max_retries - 1 {
            let base = 500 * (attempt as u64 + 1);
            let jitter = rand::random::<u64>() % (base / 2 + 1);
            tokio::time::sleep(std::time::Duration::from_millis(base + jitter)).await;
        }
    }
    Err(last_err.unwrap_or_else(|| {
        anyhow::anyhow!("Segment download failed after {} attempts", max_retries)
    }))
}

const PNG_SIGNATURE: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
const JPEG_SIGNATURE: [u8; 3] = [0xFF, 0xD8, 0xFF];

/// Number of leading bytes to drop from a segment that arrived disguised as an
/// image.
///
/// A handful of CDNs wrap each transport-stream segment in a real PNG or JPEG
/// file so that naive traffic inspection sees an image download. The media
/// payload is simply appended after the image ends, so the fix is to find the
/// image's end-of-file marker and start reading right after it:
///
/// * PNG ends with the `IEND` chunk — 4 bytes of type plus a 4-byte CRC32,
///   hence 8 bytes past the marker;
/// * JPEG ends with the `FF D9` EOI marker, 2 bytes long.
///

View on GitHub (pinned to 8600b91f42)