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