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) {