IosSafeHttpDataSource: use buildUpon().setPosition/setLength (not subrange)

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.
This commit is contained in:
Kayos 2026-05-24 14:10:39 -07:00
parent 3e8109c726
commit 7d2cf5d9bc

View file

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