diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
index eef79daa91..9a8dc5b8be 100644
--- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
+++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
@@ -111,6 +111,22 @@ public final class DataSpec {
/** The {@link Uri} from which data should be read. */
public final Uri uri;
+ /**
+ * The offset of the data located at {@link #uri} within an original resource.
+ *
+ *
Equal to 0 unless {@link #uri} provides access to a subset of an original resource. As an
+ * example, consider a resource that can be requested over the network and is 1000 bytes long. If
+ * {@link #uri} points to a local file that contains just bytes [200-300], then this field will be
+ * set to {@code 200}.
+ *
+ *
This field can be ignored except for in specific circumstances where the absolute position
+ * in the original resource is required in a {@link DataSource} chain. One example is when a
+ * {@link DataSource} needs to decrypt the content as it's read. In this case the absolute
+ * position in the original resource is typically needed to correctly initialize the decryption
+ * algorithm.
+ */
+ public final long uriPositionOffset;
+
/**
* The HTTP method to use when requesting the data. This value will be ignored by non-HTTP {@link
* DataSource} implementations.
@@ -126,15 +142,16 @@ public final class DataSpec {
/** Immutable map containing the headers to use in HTTP requests. */
public final Map httpRequestHeaders;
- /** The absolute position of the data in the full stream. */
- public final long absoluteStreamPosition;
-
/**
- * The position of the data when read from {@link #uri}.
- *
- * Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location
- * of a subset of the underlying data.
+ * The absolute position of the data in the full stream.
+ *
+ * @deprecated Use {@link #position} except for specific use cases where the absolute position
+ * within the original resource is required within a {@link DataSource} chain. Where the
+ * absolute position is required, use {@code uriPositionOffset + position}.
*/
+ @Deprecated public final long absoluteStreamPosition;
+
+ /** The position of the data when read from {@link #uri}. */
public final long position;
/**
@@ -152,7 +169,7 @@ public final class DataSpec {
public final @Flags int flags;
/**
- * Construct a data spec for the given uri and with {@link #key} set to null.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
*/
@@ -161,7 +178,7 @@ public final class DataSpec {
}
/**
- * Construct a data spec for the given uri and with {@link #key} set to null.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
* @param flags {@link #flags}.
@@ -171,10 +188,10 @@ public final class DataSpec {
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition}.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
- * @param position {@link #position}, equal to {@link #absoluteStreamPosition}.
+ * @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
*/
@@ -183,10 +200,10 @@ public final class DataSpec {
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition}.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
- * @param position {@link #position}, equal to {@link #absoluteStreamPosition}.
+ * @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
@@ -196,11 +213,10 @@ public final class DataSpec {
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition} and has
- * request headers.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
- * @param position {@link #position}, equal to {@link #absoluteStreamPosition}.
+ * @param position {@link #position}, equal to {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
@@ -226,10 +242,10 @@ public final class DataSpec {
}
/**
- * Construct a data spec where {@link #position} may differ from {@link #absoluteStreamPosition}.
+ * Constructs an instance where {@link #uriPositionOffset} may be non-zero.
*
* @param uri {@link #uri}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
@@ -246,14 +262,15 @@ public final class DataSpec {
}
/**
- * Construct a data spec by inferring the {@link #httpMethod} based on the {@code postBody}
- * parameter. If postBody is non-null, then httpMethod is set to {@link #HTTP_METHOD_POST}. If
- * postBody is null, then httpMethod is set to {@link #HTTP_METHOD_GET}.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero. The {@link #httpMethod}
+ * is inferred from {@code postBody}. If {@code postBody} is non-null then {@link #httpMethod} is
+ * set to {@link #HTTP_METHOD_POST}. If {@code postBody} is null then {@link #httpMethod} is set
+ * to {@link #HTTP_METHOD_GET}.
*
* @param uri {@link #uri}.
* @param postBody {@link #httpBody} The body of the HTTP request, which is also used to infer the
* {@link #httpMethod}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
@@ -279,12 +296,12 @@ public final class DataSpec {
}
/**
- * Construct a data spec where {@link #position} may differ from {@link #absoluteStreamPosition}.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero.
*
* @param uri {@link #uri}.
* @param httpMethod {@link #httpMethod}.
* @param httpBody {@link #httpBody}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
@@ -312,12 +329,12 @@ public final class DataSpec {
}
/**
- * Construct a data spec with request parameters to be used as HTTP headers inside HTTP requests.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero.
*
* @param uri {@link #uri}.
* @param httpMethod {@link #httpMethod}.
* @param httpBody {@link #httpBody}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
@@ -334,18 +351,44 @@ public final class DataSpec {
@Nullable String key,
@Flags int flags,
Map httpRequestHeaders) {
- Assertions.checkArgument(absoluteStreamPosition >= 0);
+ this(
+ uri,
+ /* uriPositionOffset= */ absoluteStreamPosition - position,
+ httpMethod,
+ httpBody,
+ httpRequestHeaders,
+ position,
+ length,
+ key,
+ flags);
+ }
+
+ @SuppressWarnings("deprecation")
+ private DataSpec(
+ Uri uri,
+ long uriPositionOffset,
+ @HttpMethod int httpMethod,
+ @Nullable byte[] httpBody,
+ Map httpRequestHeaders,
+ long position,
+ long length,
+ @Nullable String key,
+ @Flags int flags) {
+ // TODO: Replace this assertion with a stricter one checking "uriPositionOffset >= 0", after
+ // validating there are no violations in ExoPlayer and 1P apps.
+ Assertions.checkArgument(uriPositionOffset + position >= 0);
Assertions.checkArgument(position >= 0);
Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET);
this.uri = uri;
+ this.uriPositionOffset = uriPositionOffset;
this.httpMethod = httpMethod;
- this.httpBody = (httpBody != null && httpBody.length != 0) ? httpBody : null;
- this.absoluteStreamPosition = absoluteStreamPosition;
+ this.httpBody = httpBody != null && httpBody.length != 0 ? httpBody : null;
+ this.httpRequestHeaders = Collections.unmodifiableMap(new HashMap<>(httpRequestHeaders));
this.position = position;
+ this.absoluteStreamPosition = uriPositionOffset + position;
this.length = length;
this.key = key;
this.flags = flags;
- this.httpRequestHeaders = Collections.unmodifiableMap(new HashMap<>(httpRequestHeaders));
}
/**
@@ -389,14 +432,14 @@ public final class DataSpec {
} else {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition + offset,
+ httpRequestHeaders,
position + offset,
length,
key,
- flags,
- httpRequestHeaders);
+ flags);
}
}
@@ -409,14 +452,14 @@ public final class DataSpec {
public DataSpec withUri(Uri uri) {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
- flags,
- httpRequestHeaders);
+ flags);
}
/**
@@ -429,14 +472,14 @@ public final class DataSpec {
public DataSpec withRequestHeaders(Map httpRequestHeaders) {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
- flags,
- httpRequestHeaders);
+ flags);
}
/**
@@ -452,14 +495,14 @@ public final class DataSpec {
httpRequestHeaders.putAll(additionalHttpRequestHeaders);
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
- flags,
- httpRequestHeaders);
+ flags);
}
@Override
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
index 5155685999..4ee958e77f 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
@@ -267,7 +267,7 @@ public abstract class SegmentDownloader> impleme
private static boolean canMergeSegments(DataSpec dataSpec1, DataSpec dataSpec2) {
return dataSpec1.uri.equals(dataSpec2.uri)
&& dataSpec1.length != C.LENGTH_UNSET
- && (dataSpec1.absoluteStreamPosition + dataSpec1.length == dataSpec2.absoluteStreamPosition)
+ && (dataSpec1.position + dataSpec1.length == dataSpec2.position)
&& Util.areEqual(dataSpec1.key, dataSpec2.key)
&& dataSpec1.flags == dataSpec2.flags
&& dataSpec1.httpMethod == dataSpec2.httpMethod
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
index 9dffe09194..6af85c07ac 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
@@ -126,7 +126,7 @@ public class ContainerMediaChunk extends BaseMediaChunk {
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
ExtractorInput input =
new DefaultExtractorInput(
- dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ dataSource, loadDataSpec.position, dataSource.open(loadDataSpec));
// Load and decode the sample data.
try {
Extractor extractor = extractorWrapper.extractor;
@@ -136,7 +136,7 @@ public class ContainerMediaChunk extends BaseMediaChunk {
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
- nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ nextLoadPosition = input.getPosition() - dataSpec.position;
}
} finally {
Util.closeQuietly(dataSource);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
index 178fb94c7c..ce355de6ef 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
@@ -93,7 +93,7 @@ public final class InitializationChunk extends Chunk {
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
ExtractorInput input =
new DefaultExtractorInput(
- dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ dataSource, loadDataSpec.position, dataSource.open(loadDataSpec));
// Load and decode the initialization data.
try {
Extractor extractor = extractorWrapper.extractor;
@@ -103,7 +103,7 @@ public final class InitializationChunk extends Chunk {
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
- nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ nextLoadPosition = input.getPosition() - dataSpec.position;
}
} finally {
Util.closeQuietly(dataSource);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
index fdddcf9989..92dff8b394 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
@@ -169,9 +169,7 @@ public final class CacheDataSink implements DataSink {
dataSpec.length == C.LENGTH_UNSET
? C.LENGTH_UNSET
: Math.min(dataSpec.length - dataSpecBytesWritten, dataSpecFragmentSize);
- file =
- cache.startFile(
- dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, length);
+ file = cache.startFile(dataSpec.key, dataSpec.position + dataSpecBytesWritten, length);
FileOutputStream underlyingFileOutputStream = new FileOutputStream(file);
if (bufferSize > 0) {
if (bufferedOutputStream == null) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
index fadffd9b95..8da2fb1def 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
@@ -79,7 +79,7 @@ public final class CacheUtil {
public static Pair getCached(
DataSpec dataSpec, Cache cache, @Nullable CacheKeyFactory cacheKeyFactory) {
String key = buildCacheKey(dataSpec, cacheKeyFactory);
- long position = dataSpec.absoluteStreamPosition;
+ long position = dataSpec.position;
long requestLength = getRequestLength(dataSpec, cache, key);
long bytesAlreadyCached = 0;
long bytesLeft = requestLength;
@@ -193,7 +193,7 @@ public final class CacheUtil {
bytesLeft = getRequestLength(dataSpec, cache, key);
}
- long position = dataSpec.absoluteStreamPosition;
+ long position = dataSpec.position;
boolean lengthUnset = bytesLeft == C.LENGTH_UNSET;
while (bytesLeft != 0) {
throwExceptionIfInterruptedOrCancelled(isCanceled);
@@ -238,18 +238,16 @@ public final class CacheUtil {
return dataSpec.length;
} else {
long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(key));
- return contentLength == C.LENGTH_UNSET
- ? C.LENGTH_UNSET
- : contentLength - dataSpec.absoluteStreamPosition;
+ return contentLength == C.LENGTH_UNSET ? C.LENGTH_UNSET : contentLength - dataSpec.position;
}
}
/**
* Reads and discards all data specified by the {@code dataSpec}.
*
- * @param dataSpec Defines the data to be read. {@code absoluteStreamPosition} and {@code length}
- * fields are overwritten by the following parameters.
- * @param absoluteStreamPosition The absolute position of the data to be read.
+ * @param dataSpec Defines the data to be read. The {@code position} and {@code length} fields are
+ * overwritten by the following parameters.
+ * @param position The position of the data to be read.
* @param length Length of the data to be read, or {@link C#LENGTH_UNSET} if it is unknown.
* @param dataSource The {@link DataSource} to read the data from.
* @param buffer The buffer to be used while downloading.
@@ -264,7 +262,7 @@ public final class CacheUtil {
*/
private static long readAndDiscard(
DataSpec dataSpec,
- long absoluteStreamPosition,
+ long position,
long length,
DataSource dataSource,
byte[] buffer,
@@ -274,7 +272,7 @@ public final class CacheUtil {
boolean isLastBlock,
@Nullable AtomicBoolean isCanceled)
throws IOException, InterruptedException {
- long positionOffset = absoluteStreamPosition - dataSpec.absoluteStreamPosition;
+ long positionOffset = position - dataSpec.position;
long initialPositionOffset = positionOffset;
long endOffset = length != C.LENGTH_UNSET ? positionOffset + length : C.POSITION_UNSET;
while (true) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
index d9b3ff0069..95295035e7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
@@ -68,8 +68,9 @@ public final class AesCipherDataSink implements DataSink {
public void open(DataSpec dataSpec) throws IOException {
wrappedDataSink.open(dataSpec);
long nonce = CryptoUtil.getFNV64Hash(dataSpec.key);
- cipher = new AesFlushingCipher(Cipher.ENCRYPT_MODE, secretKey, nonce,
- dataSpec.absoluteStreamPosition);
+ cipher =
+ new AesFlushingCipher(
+ Cipher.ENCRYPT_MODE, secretKey, nonce, dataSpec.uriPositionOffset + dataSpec.position);
}
@Override
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
index 0910c63c19..665a47191e 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
@@ -52,8 +52,9 @@ public final class AesCipherDataSource implements DataSource {
public long open(DataSpec dataSpec) throws IOException {
long dataLength = upstream.open(dataSpec);
long nonce = CryptoUtil.getFNV64Hash(dataSpec.key);
- cipher = new AesFlushingCipher(Cipher.DECRYPT_MODE, secretKey, nonce,
- dataSpec.absoluteStreamPosition);
+ cipher =
+ new AesFlushingCipher(
+ Cipher.DECRYPT_MODE, secretKey, nonce, dataSpec.uriPositionOffset + dataSpec.position);
return dataLength;
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
index 27438fcac3..c94e063749 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
@@ -546,7 +546,7 @@ public final class CacheDataSourceTest {
private void assertReadData(
CacheDataSource cacheDataSource, DataSpec dataSpec, boolean unknownLength)
throws IOException {
- int position = (int) dataSpec.absoluteStreamPosition;
+ int position = (int) dataSpec.position;
int requestLength = (int) dataSpec.length;
int readLength = TEST_DATA.length - position;
if (requestLength != C.LENGTH_UNSET) {
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
index bb32f47ba8..05ef69c572 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
@@ -112,7 +112,7 @@ public final class CacheDataSourceTest2 {
byte[] scratch = new byte[4096];
Random random = new Random(0);
source.open(dataSpec);
- int position = (int) dataSpec.absoluteStreamPosition;
+ int position = (int) dataSpec.position;
int bytesRead = 0;
while (bytesRead != C.RESULT_END_OF_INPUT) {
int maxBytesToRead = random.nextInt(scratch.length) + 1;
@@ -134,7 +134,6 @@ public final class CacheDataSourceTest2 {
DataSpec[] openedDataSpecs = upstreamSource.getAndClearOpenedDataSpecs();
assertThat(openedDataSpecs).hasLength(1);
assertThat(openedDataSpecs[0].position).isEqualTo(start);
- assertThat(openedDataSpecs[0].absoluteStreamPosition).isEqualTo(start);
assertThat(openedDataSpecs[0].length).isEqualTo(end - start);
}
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
index f1d6648193..3849410fe0 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
@@ -376,7 +376,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
result = extractor.read(input, DUMMY_POSITION_HOLDER);
}
} finally {
- nextLoadPosition = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
+ nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
}
} finally {
Util.closeQuietly(dataSource);
@@ -389,7 +389,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
throws IOException, InterruptedException {
long bytesToRead = dataSource.open(dataSpec);
DefaultExtractorInput extractorInput =
- new DefaultExtractorInput(dataSource, dataSpec.absoluteStreamPosition, bytesToRead);
+ new DefaultExtractorInput(dataSource, dataSpec.position, bytesToRead);
if (extractor == null) {
long id3Timestamp = peekId3PrivTimestamp(extractorInput);