From 7d2cf5d9bc3609e794898547bbb2ea8860ef50ac Mon Sep 17 00:00:00 2001 From: Kayos Date: Sun, 24 May 2026 14:10:39 -0700 Subject: [PATCH] IosSafeHttpDataSource: use buildUpon().setPosition/setLength (not subrange) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous code called dataSpec.subrange(dataSpec.position, length) which *adds* the offset to the existing position rather than setting an absolute bounded slice — that turned every first open() into Range request `bytes=(2*N)-(2*N+chunk-1)`, doubling the offset and still 403'ing for the wrong reason. DataSpec.subrange(offset, length) docs: "position of the new DataSpec will be position + offset". So subrange(0, L) gives a bounded slice at the current position. We want absolute control, so use buildUpon().setPosition(N).setLength(L).build() — explicit, unambiguous. Caught on vc=17 emulator smoke: ExoPlayer logged InvalidResponseCodeException 403 at IosSafeHttpDataSource.kt:58 (the inner.open(bounded) call), meaning the bounded shape was wrong. Player.Listener.onPlayerError DID fire and surface the error in the UI — so that part of the patch works. --- .../sulkta/straw/net/IosSafeHttpDataSource.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/net/IosSafeHttpDataSource.kt b/strawApp/src/main/kotlin/com/sulkta/straw/net/IosSafeHttpDataSource.kt index 8815cfa9c..51d603e00 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/net/IosSafeHttpDataSource.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/net/IosSafeHttpDataSource.kt @@ -50,7 +50,13 @@ class IosSafeHttpDataSource( } else { minOf(dataSpec.length, chunkBytes) } - val bounded = dataSpec.subrange(dataSpec.position, requestLen) + // NOTE: DataSpec.subrange(offset, length) ADDS offset to the existing + // position — so subrange(position, length) doubles the position. Use + // buildUpon().setLength(...) which preserves position and only bounds + // the byte length. This is what makes ExoPlayer's first Range header + // come out as `bytes=N-M` (closed, accepted by googlevideo iOS URLs) + // instead of `bytes=N-` (open, rejected with 403). + val bounded = dataSpec.buildUpon().setLength(requestLen).build() originalSpec = dataSpec totalRead = 0 // inner.open() returns the BOUNDED chunk's length. Track it so we @@ -79,7 +85,13 @@ class IosSafeHttpDataSource( } if (remainingOverall <= 0L) return C.RESULT_END_OF_INPUT val nextLen = remainingOverall.coerceAtMost(chunkBytes) - chunkRemaining = inner.open(spec.subrange(nextPos, nextLen)) + // Same as in open() — use buildUpon().setPosition/setLength rather + // than subrange() so the absolute position stays meaningful. + val nextSpec = spec.buildUpon() + .setPosition(nextPos) + .setLength(nextLen) + .build() + chunkRemaining = inner.open(nextSpec) } // Cap the read against what's left in this chunk. val toRead = if (chunkRemaining < 0L) {