From 65a50e142221815505ffaeba9abf2ff4be22ae6a Mon Sep 17 00:00:00 2001 From: honzup <5564623+honzup@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:00:18 +0200 Subject: [PATCH] Fix iOS stream restart loop by forwarding Range to ServeFile stream_episode served local files via tower_http ServeFile but built the inner request with no headers, so the client Range header was dropped and every response was 200 + full body. iOS AVPlayer re-fetched from byte 0 on each buffer refill, making YouTube playback restart every ~60s. Forward Range/If-Range/If-Modified-Since/If-None-Match/If-Unmodified-Since into the ServeFile request so it can answer 206 Partial Content. --- rust-api/src/handlers/podcasts.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/rust-api/src/handlers/podcasts.rs b/rust-api/src/handlers/podcasts.rs index a7b867d2a..e6ac0cbf9 100644 --- a/rust-api/src/handlers/podcasts.rs +++ b/rust-api/src/handlers/podcasts.rs @@ -3265,6 +3265,7 @@ pub async fn stream_episode( State(state): State, Path(episode_id): Path, Query(query): Query, + headers: HeaderMap, ) -> Result { let api_key = &query.api_key; info!("Stream request for episode {} with api_key {} and user_id {}", episode_id, api_key, query.user_id); @@ -3355,9 +3356,23 @@ pub async fn stream_episode( use tower::ServiceExt; let service = ServeFile::new(&path); - let request = axum::http::Request::builder() - .method("GET") - .uri("/") + // Forward the range/conditional headers ServeFile needs so it can answer with + // 206 Partial Content. Without them it always returns the full file with 200, and + // iOS AVPlayer re-fetches from byte 0 on every buffer refill (the "stream keeps + // restarting" loop). Only these headers are copied — auth stays in the query string. + let mut builder = axum::http::Request::builder().method("GET").uri("/"); + for name in [ + axum::http::header::RANGE, + axum::http::header::IF_RANGE, + axum::http::header::IF_MODIFIED_SINCE, + axum::http::header::IF_NONE_MATCH, + axum::http::header::IF_UNMODIFIED_SINCE, + ] { + if let Some(value) = headers.get(&name) { + builder = builder.header(name, value); + } + } + let request = builder .body(axum::body::Body::empty()) .map_err(|e| AppError::external_error(&format!("Failed to build request: {}", e)))?;