public final class

DataSpec

extends java.lang.Object

 java.lang.Object

↳androidx.media3.datasource.DataSpec

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-datasource', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-datasource
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-datasource:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Defines a region of data in a resource.

Summary

Fields
public final longabsoluteStreamPosition

The absolute position of the data in the resource.

public final java.lang.ObjectcustomData

Application specific data.

public static final intFLAG_ALLOW_CACHE_FRAGMENTATION

Allows fragmentation of this request into multiple cache files, meaning a cache eviction policy will be able to evict individual fragments of the data.

public static final intFLAG_ALLOW_GZIP

Allows an underlying network stack to request that the server use gzip compression.

public static final intFLAG_DONT_CACHE_IF_LENGTH_UNKNOWN

Prevents caching if the length cannot be resolved when the DataSource is opened.

public static final intFLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED

Indicates there are known external factors that might prevent the data from being loaded at full network speed (e.g.

public final intflags

Request flags.

public static final intHTTP_METHOD_GET

HTTP GET method.

public static final intHTTP_METHOD_HEAD

HTTP HEAD method.

public static final intHTTP_METHOD_POST

HTTP POST method.

public final byte[]httpBody

The HTTP request body, null otherwise.

public final inthttpMethod

The HTTP method to use when requesting the data.

public final java.util.Map<java.lang.String, java.lang.String>httpRequestHeaders

Additional HTTP headers to use when requesting the data.

public final java.lang.Stringkey

A key that uniquely identifies the resource.

public final longlength

The length of the data, or C.LENGTH_UNSET.

public final longposition

The position of the data when read from DataSpec.uri.

public final Uriuri

A from which data belonging to the resource can be read.

public final longuriPositionOffset

The offset of the data located at DataSpec.uri within the resource.

Constructors
publicDataSpec(Uri uri)

Constructs an instance.

publicDataSpec(Uri uri, long position, long length)

Constructs an instance.

publicDataSpec(Uri uri, long position, long length, java.lang.String key)

Constructs an instance.

Methods
public DataSpec.BuilderbuildUpon()

Returns a DataSpec.Builder initialized with the values of this instance.

public final java.lang.StringgetHttpMethodString()

Returns the uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the DataSpec.httpMethod.

public static java.lang.StringgetStringForHttpMethod(int httpMethod)

Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the given DataSpec.HttpMethod.

public booleanisFlagSet(int flag)

Returns whether the given flag is set.

public DataSpecsubrange(long offset)

Returns a data spec that represents a subrange of the data defined by this DataSpec.

public DataSpecsubrange(long offset, long length)

Returns a data spec that represents a subrange of the data defined by this DataSpec.

public java.lang.StringtoString()

public DataSpecwithAdditionalHeaders(java.util.Map<java.lang.String, java.lang.String> additionalHttpRequestHeaders)

Returns a copy this data spec with additional HTTP request headers.

public DataSpecwithRequestHeaders(java.util.Map<java.lang.String, java.lang.String> httpRequestHeaders)

Returns a copy of this data spec with the specified HTTP request headers.

public DataSpecwithUri(Uri uri)

Returns a copy of this data spec with the specified Uri.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final int FLAG_ALLOW_GZIP

Allows an underlying network stack to request that the server use gzip compression.

Should not typically be set if the data being requested is already compressed (e.g. most audio and video requests). May be set when requesting other data.

When a DataSource is used to request data with this flag set, and if the DataSource does make a network request, then the value returned from DataSource.open(DataSpec) will typically be C.LENGTH_UNSET. The data read from DataReader.read(byte[], int, int) will be the decompressed data.

public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN

Prevents caching if the length cannot be resolved when the DataSource is opened.

public static final int FLAG_ALLOW_CACHE_FRAGMENTATION

Allows fragmentation of this request into multiple cache files, meaning a cache eviction policy will be able to evict individual fragments of the data. Depending on the cache implementation, setting this flag may also enable more concurrent access to the data (e.g. reading one fragment whilst writing another).

public static final int FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED

Indicates there are known external factors that might prevent the data from being loaded at full network speed (e.g. server throttling or unfinished live media chunks).

public static final int HTTP_METHOD_GET

HTTP GET method.

public static final int HTTP_METHOD_POST

HTTP POST method.

public static final int HTTP_METHOD_HEAD

HTTP HEAD method.

public final Uri uri

A from which data belonging to the resource can be read.

public final long uriPositionOffset

The offset of the data located at DataSpec.uri within the resource.

Equal to 0 unless DataSpec.uri provides access to a subset of the resource. As an example, consider a resource that can be requested over the network and is 1000 bytes long. If DataSpec.uri points to a local file that contains just bytes [200-300], then this field will be set to 200.

This field can be ignored except for in specific circumstances where the absolute position in the resource is required in a DataSource chain. One example is when a DataSource needs to decrypt the content as it's read. In this case the absolute position in the resource is typically needed to correctly initialize the decryption algorithm.

public final int httpMethod

The HTTP method to use when requesting the data. This value will be ignored by non-HTTP DataSource implementations.

public final byte[] httpBody

The HTTP request body, null otherwise. If the body is non-null, then httpBody.length will be non-zero.

public final java.util.Map<java.lang.String, java.lang.String> httpRequestHeaders

Additional HTTP headers to use when requesting the data.

Note: This map is for additional headers specific to the data being requested. It does not include headers that are set directly by HttpDataSource implementations. In particular, this means the following headers are not included:

public final long absoluteStreamPosition

Deprecated: Use DataSpec.position except for specific use cases where the absolute position within the resource is required within a DataSource chain. Where the absolute position is required, use uriPositionOffset + position.

The absolute position of the data in the resource.

public final long position

The position of the data when read from DataSpec.uri.

public final long length

The length of the data, or C.LENGTH_UNSET.

public final java.lang.String key

A key that uniquely identifies the resource. Used for cache indexing. May be null if the data spec is not intended to be used in conjunction with a cache.

public final int flags

Request flags.

public final java.lang.Object customData

Application specific data.

This field is intended for advanced use cases in which applications require the ability to attach custom data to DataSpec instances. The custom data should be immutable.

Constructors

public DataSpec(Uri uri)

Constructs an instance.

Parameters:

uri: DataSpec.uri.

public DataSpec(Uri uri, long position, long length)

Constructs an instance.

Parameters:

uri: DataSpec.uri.
position: DataSpec.position.
length: DataSpec.length.

public DataSpec(Uri uri, long position, long length, java.lang.String key)

Deprecated: Use DataSpec.Builder.

Constructs an instance.

Parameters:

uri: DataSpec.uri.
position: DataSpec.position.
length: DataSpec.length.
key: DataSpec.key.

Methods

public static java.lang.String getStringForHttpMethod(int httpMethod)

Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the given DataSpec.HttpMethod.

public boolean isFlagSet(int flag)

Returns whether the given flag is set.

Parameters:

flag: Flag to be checked if it is set.

public final java.lang.String getHttpMethodString()

Returns the uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the DataSpec.httpMethod.

public DataSpec.Builder buildUpon()

Returns a DataSpec.Builder initialized with the values of this instance.

public DataSpec subrange(long offset)

Returns a data spec that represents a subrange of the data defined by this DataSpec. The subrange includes data from the offset up to the end of this DataSpec.

Parameters:

offset: The offset of the subrange.

Returns:

A data spec that represents a subrange of the data defined by this DataSpec.

public DataSpec subrange(long offset, long length)

Returns a data spec that represents a subrange of the data defined by this DataSpec.

Parameters:

offset: The offset of the subrange.
length: The length of the subrange.

Returns:

A data spec that represents a subrange of the data defined by this DataSpec.

public DataSpec withUri(Uri uri)

Returns a copy of this data spec with the specified Uri.

Parameters:

uri: The new source .

Returns:

The copied data spec with the specified Uri.

public DataSpec withRequestHeaders(java.util.Map<java.lang.String, java.lang.String> httpRequestHeaders)

Returns a copy of this data spec with the specified HTTP request headers. Headers already in the data spec are not copied to the new instance.

Parameters:

httpRequestHeaders: The HTTP request headers.

Returns:

The copied data spec with the specified HTTP request headers.

public DataSpec withAdditionalHeaders(java.util.Map<java.lang.String, java.lang.String> additionalHttpRequestHeaders)

Returns a copy this data spec with additional HTTP request headers. Headers in additionalHttpRequestHeaders will overwrite any headers already in the data spec that have the same keys.

Parameters:

additionalHttpRequestHeaders: The additional HTTP request headers.

Returns:

The copied data spec with the additional HTTP request headers.

public java.lang.String toString()

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.datasource;

import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.net.Uri;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/** Defines a region of data in a resource. */
@UnstableApi
public final class DataSpec {

  static {
    MediaLibraryInfo.registerModule("media3.datasource");
  }

  /**
   * Builds {@link DataSpec} instances.
   *
   * <p>Use DataSpec#buildUpon() to obtain a builder representing an existing {@link DataSpec}.
   */
  public static final class Builder {

    @Nullable private Uri uri;
    private long uriPositionOffset;
    private @HttpMethod int httpMethod;
    @Nullable private byte[] httpBody;
    private Map<String, String> httpRequestHeaders;
    private long position;
    private long length;
    @Nullable private String key;
    private @Flags int flags;
    @Nullable private Object customData;

    /** Creates a new instance with default values. */
    public Builder() {
      httpMethod = HTTP_METHOD_GET;
      httpRequestHeaders = Collections.emptyMap();
      length = C.LENGTH_UNSET;
    }

    /**
     * Creates a new instance to build upon the provided {@link DataSpec}.
     *
     * @param dataSpec The {@link DataSpec} to build upon.
     */
    private Builder(DataSpec dataSpec) {
      uri = dataSpec.uri;
      uriPositionOffset = dataSpec.uriPositionOffset;
      httpMethod = dataSpec.httpMethod;
      httpBody = dataSpec.httpBody;
      httpRequestHeaders = dataSpec.httpRequestHeaders;
      position = dataSpec.position;
      length = dataSpec.length;
      key = dataSpec.key;
      flags = dataSpec.flags;
      customData = dataSpec.customData;
    }

    /**
     * Sets {@link DataSpec#uri}.
     *
     * @param uriString The {@link DataSpec#uri}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setUri(String uriString) {
      this.uri = Uri.parse(uriString);
      return this;
    }

    /**
     * Sets {@link DataSpec#uri}.
     *
     * @param uri The {@link DataSpec#uri}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setUri(Uri uri) {
      this.uri = uri;
      return this;
    }

    /**
     * Sets the {@link DataSpec#uriPositionOffset}. The default value is 0.
     *
     * @param uriPositionOffset The {@link DataSpec#uriPositionOffset}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setUriPositionOffset(long uriPositionOffset) {
      this.uriPositionOffset = uriPositionOffset;
      return this;
    }

    /**
     * Sets {@link DataSpec#httpMethod}. The default value is {@link #HTTP_METHOD_GET}.
     *
     * @param httpMethod The {@link DataSpec#httpMethod}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setHttpMethod(@HttpMethod int httpMethod) {
      this.httpMethod = httpMethod;
      return this;
    }

    /**
     * Sets {@link DataSpec#httpBody}. The default value is {@code null}.
     *
     * @param httpBody The {@link DataSpec#httpBody}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setHttpBody(@Nullable byte[] httpBody) {
      this.httpBody = httpBody;
      return this;
    }

    /**
     * Sets the {@link DataSpec#httpRequestHeaders}. The default value is an empty map.
     *
     * <p>Note: {@code Range}, {@code Accept-Encoding} and {@code User-Agent} should not be set with
     * this method, since they are set directly by {@link HttpDataSource} implementations. See
     * {@link DataSpec#httpRequestHeaders} for more details.
     *
     * @param httpRequestHeaders The {@link DataSpec#httpRequestHeaders}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setHttpRequestHeaders(Map<String, String> httpRequestHeaders) {
      this.httpRequestHeaders = httpRequestHeaders;
      return this;
    }

    /**
     * Sets the {@link DataSpec#position}. The default value is 0.
     *
     * @param position The {@link DataSpec#position}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setPosition(long position) {
      this.position = position;
      return this;
    }

    /**
     * Sets the {@link DataSpec#length}. The default value is {@link C#LENGTH_UNSET}.
     *
     * @param length The {@link DataSpec#length}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setLength(long length) {
      this.length = length;
      return this;
    }

    /**
     * Sets the {@link DataSpec#key}. The default value is {@code null}.
     *
     * @param key The {@link DataSpec#key}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setKey(@Nullable String key) {
      this.key = key;
      return this;
    }

    /**
     * Sets the {@link DataSpec#flags}. The default value is 0.
     *
     * @param flags The {@link DataSpec#flags}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setFlags(@Flags int flags) {
      this.flags = flags;
      return this;
    }

    /**
     * Sets the {@link DataSpec#customData}. The default value is {@code null}.
     *
     * @param customData The {@link DataSpec#customData}.
     * @return The builder.
     */
    @CanIgnoreReturnValue
    public Builder setCustomData(@Nullable Object customData) {
      this.customData = customData;
      return this;
    }

    /**
     * Builds a {@link DataSpec} with the builder's current values.
     *
     * @return The build {@link DataSpec}.
     * @throws IllegalStateException If {@link #setUri} has not been called.
     */
    public DataSpec build() {
      Assertions.checkStateNotNull(uri, "The uri must be set.");
      return new DataSpec(
          uri,
          uriPositionOffset,
          httpMethod,
          httpBody,
          httpRequestHeaders,
          position,
          length,
          key,
          flags,
          customData);
    }
  }

  /**
   * The flags that apply to any request for data. Possible flag values are {@link
   * #FLAG_ALLOW_GZIP}, {@link #FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}, {@link
   * #FLAG_ALLOW_CACHE_FRAGMENTATION}, and {@link #FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED}.
   */
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target(TYPE_USE)
  @IntDef(
      flag = true,
      value = {
        FLAG_ALLOW_GZIP,
        FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN,
        FLAG_ALLOW_CACHE_FRAGMENTATION,
        FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED
      })
  public @interface Flags {}

  /**
   * Allows an underlying network stack to request that the server use gzip compression.
   *
   * <p>Should not typically be set if the data being requested is already compressed (e.g. most
   * audio and video requests). May be set when requesting other data.
   *
   * <p>When a {@link DataSource} is used to request data with this flag set, and if the {@link
   * DataSource} does make a network request, then the value returned from {@link
   * DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from {@link
   * DataSource#read(byte[], int, int)} will be the decompressed data.
   */
  public static final int FLAG_ALLOW_GZIP = 1;

  /** Prevents caching if the length cannot be resolved when the {@link DataSource} is opened. */
  public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN = 1 << 1;

  /**
   * Allows fragmentation of this request into multiple cache files, meaning a cache eviction policy
   * will be able to evict individual fragments of the data. Depending on the cache implementation,
   * setting this flag may also enable more concurrent access to the data (e.g. reading one fragment
   * whilst writing another).
   */
  public static final int FLAG_ALLOW_CACHE_FRAGMENTATION = 1 << 2;

  /**
   * Indicates there are known external factors that might prevent the data from being loaded at
   * full network speed (e.g. server throttling or unfinished live media chunks).
   */
  public static final int FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED = 1 << 3;

  /**
   * HTTP methods supported by ExoPlayer {@link HttpDataSource}s. One of {@link #HTTP_METHOD_GET},
   * {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}.
   */
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target(TYPE_USE)
  @IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD})
  public @interface HttpMethod {}

  /** HTTP GET method. */
  public static final int HTTP_METHOD_GET = 1;

  /** HTTP POST method. */
  public static final int HTTP_METHOD_POST = 2;

  /** HTTP HEAD method. */
  public static final int HTTP_METHOD_HEAD = 3;

  /**
   * Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the given
   * {@link HttpMethod}.
   */
  public static String getStringForHttpMethod(@HttpMethod int httpMethod) {
    switch (httpMethod) {
      case HTTP_METHOD_GET:
        return "GET";
      case HTTP_METHOD_POST:
        return "POST";
      case HTTP_METHOD_HEAD:
        return "HEAD";
      default:
        // Never happens.
        throw new IllegalStateException();
    }
  }

  /** A {@link Uri} from which data belonging to the resource can be read. */
  public final Uri uri;

  /**
   * The offset of the data located at {@link #uri} within the resource.
   *
   * <p>Equal to 0 unless {@link #uri} provides access to a subset of the 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}.
   *
   * <p>This field can be ignored except for in specific circumstances where the absolute position
   * in the 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 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.
   */
  public final @HttpMethod int httpMethod;

  /**
   * The HTTP request body, null otherwise. If the body is non-null, then {@code httpBody.length}
   * will be non-zero.
   */
  @Nullable public final byte[] httpBody;

  /**
   * Additional HTTP headers to use when requesting the data.
   *
   * <p>Note: This map is for additional headers specific to the data being requested. It does not
   * include headers that are set directly by {@link HttpDataSource} implementations. In particular,
   * this means the following headers are not included:
   *
   * <ul>
   *   <li>{@code Range}: {@link HttpDataSource} implementations derive the {@code Range} header
   *       from {@link #position} and {@link #length}.
   *   <li>{@code Accept-Encoding}: {@link HttpDataSource} implementations derive the {@code
   *       Accept-Encoding} header based on whether {@link #flags} includes {@link
   *       #FLAG_ALLOW_GZIP}.
   *   <li>{@code User-Agent}: {@link HttpDataSource} implementations set the {@code User-Agent}
   *       header directly.
   *   <li>Other headers set at the {@link HttpDataSource} layer. I.e., headers set using {@link
   *       HttpDataSource#setRequestProperty(String, String)}, and using {@link
   *       HttpDataSource.Factory#setDefaultRequestProperties(Map)}.
   * </ul>
   */
  public final Map<String, String> httpRequestHeaders;

  /**
   * The absolute position of the data in the resource.
   *
   * @deprecated Use {@link #position} except for specific use cases where the absolute position
   *     within the 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;

  /** The length of the data, or {@link C#LENGTH_UNSET}. */
  public final long length;

  /**
   * A key that uniquely identifies the resource. Used for cache indexing. May be null if the data
   * spec is not intended to be used in conjunction with a cache.
   */
  @Nullable public final String key;

  /** Request {@link Flags flags}. */
  public final @Flags int flags;

  /**
   * Application specific data.
   *
   * <p>This field is intended for advanced use cases in which applications require the ability to
   * attach custom data to {@link DataSpec} instances. The custom data should be immutable.
   */
  @Nullable public final Object customData;

  /**
   * Constructs an instance.
   *
   * @param uri {@link #uri}.
   */
  public DataSpec(Uri uri) {
    this(uri, /* position= */ 0, /* length= */ C.LENGTH_UNSET);
  }

  /**
   * Constructs an instance.
   *
   * @param uri {@link #uri}.
   * @param position {@link #position}.
   * @param length {@link #length}.
   */
  @SuppressWarnings("deprecation")
  public DataSpec(Uri uri, long position, long length) {
    this(uri, position, length, /* key= */ null);
  }

  /**
   * Constructs an instance.
   *
   * @deprecated Use {@link Builder}.
   * @param uri {@link #uri}.
   * @param position {@link #position}.
   * @param length {@link #length}.
   * @param key {@link #key}.
   */
  @Deprecated
  public DataSpec(Uri uri, long position, long length, @Nullable String key) {
    this(
        uri,
        /* uriPositionOffset= */ 0,
        HTTP_METHOD_GET,
        null,
        Collections.emptyMap(),
        position,
        length,
        key,
        0,
        /* customData= */ null);
  }

  @SuppressWarnings("deprecation") // Setting deprecated absoluteStreamPosition field.
  private DataSpec(
      Uri uri,
      long uriPositionOffset,
      @HttpMethod int httpMethod,
      @Nullable byte[] httpBody,
      Map<String, String> httpRequestHeaders,
      long position,
      long length,
      @Nullable String key,
      @Flags int flags,
      @Nullable Object customData) {
    // 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 = checkNotNull(uri);
    this.uriPositionOffset = uriPositionOffset;
    this.httpMethod = httpMethod;
    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.customData = customData;
  }

  /**
   * Returns whether the given flag is set.
   *
   * @param flag Flag to be checked if it is set.
   */
  public boolean isFlagSet(@Flags int flag) {
    return (this.flags & flag) == flag;
  }

  /**
   * Returns the uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the
   * {@link #httpMethod}.
   */
  public final String getHttpMethodString() {
    return getStringForHttpMethod(httpMethod);
  }

  /** Returns a {@link DataSpec.Builder} initialized with the values of this instance. */
  public DataSpec.Builder buildUpon() {
    return new Builder(this);
  }

  /**
   * Returns a data spec that represents a subrange of the data defined by this DataSpec. The
   * subrange includes data from the offset up to the end of this DataSpec.
   *
   * @param offset The offset of the subrange.
   * @return A data spec that represents a subrange of the data defined by this DataSpec.
   */
  public DataSpec subrange(long offset) {
    return subrange(offset, length == C.LENGTH_UNSET ? C.LENGTH_UNSET : length - offset);
  }

  /**
   * Returns a data spec that represents a subrange of the data defined by this DataSpec.
   *
   * @param offset The offset of the subrange.
   * @param length The length of the subrange.
   * @return A data spec that represents a subrange of the data defined by this DataSpec.
   */
  public DataSpec subrange(long offset, long length) {
    if (offset == 0 && this.length == length) {
      return this;
    } else {
      return new DataSpec(
          uri,
          uriPositionOffset,
          httpMethod,
          httpBody,
          httpRequestHeaders,
          position + offset,
          length,
          key,
          flags,
          customData);
    }
  }

  /**
   * Returns a copy of this data spec with the specified Uri.
   *
   * @param uri The new source {@link Uri}.
   * @return The copied data spec with the specified Uri.
   */
  public DataSpec withUri(Uri uri) {
    return new DataSpec(
        uri,
        uriPositionOffset,
        httpMethod,
        httpBody,
        httpRequestHeaders,
        position,
        length,
        key,
        flags,
        customData);
  }

  /**
   * Returns a copy of this data spec with the specified HTTP request headers. Headers already in
   * the data spec are not copied to the new instance.
   *
   * @param httpRequestHeaders The HTTP request headers.
   * @return The copied data spec with the specified HTTP request headers.
   */
  public DataSpec withRequestHeaders(Map<String, String> httpRequestHeaders) {
    return new DataSpec(
        uri,
        uriPositionOffset,
        httpMethod,
        httpBody,
        httpRequestHeaders,
        position,
        length,
        key,
        flags,
        customData);
  }

  /**
   * Returns a copy this data spec with additional HTTP request headers. Headers in {@code
   * additionalHttpRequestHeaders} will overwrite any headers already in the data spec that have the
   * same keys.
   *
   * @param additionalHttpRequestHeaders The additional HTTP request headers.
   * @return The copied data spec with the additional HTTP request headers.
   */
  public DataSpec withAdditionalHeaders(Map<String, String> additionalHttpRequestHeaders) {
    Map<String, String> httpRequestHeaders = new HashMap<>(this.httpRequestHeaders);
    httpRequestHeaders.putAll(additionalHttpRequestHeaders);
    return new DataSpec(
        uri,
        uriPositionOffset,
        httpMethod,
        httpBody,
        httpRequestHeaders,
        position,
        length,
        key,
        flags,
        customData);
  }

  @Override
  public String toString() {
    return "DataSpec["
        + getHttpMethodString()
        + " "
        + uri
        + ", "
        + position
        + ", "
        + length
        + ", "
        + key
        + ", "
        + flags
        + "]";
  }
}