blob: f76f651c9670914469ac8401dbc00bd5d87e2250 [file] [log] [blame]
/*
* Copyright 2018 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.media;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Preconditions;
import java.io.FileDescriptor;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Structure for data source descriptor. Used by {@link MediaItem2}.
* <p>
* Users should use {@link Builder} to change {@link DataSourceDesc}.
*
* @see MediaItem2
*/
public final class DataSourceDesc {
/* No data source has been set yet */
public static final int TYPE_NONE = 0;
/* data source is type of MediaDataSource */
public static final int TYPE_CALLBACK = 1;
/* data source is type of FileDescriptor */
public static final int TYPE_FD = 2;
/* data source is type of Uri */
public static final int TYPE_URI = 3;
// intentionally less than long.MAX_VALUE.
// Declare this first to avoid 'illegal forward reference'.
private static final long LONG_MAX = 0x7ffffffffffffffL;
/**
* Used when a position is unknown.
*
* @see #getEndPosition()
*/
public static final long POSITION_UNKNOWN = LONG_MAX;
/**
* Used when the length of file descriptor is unknown.
*
* @see #getFileDescriptorLength()
*/
public static final long FD_LENGTH_UNKNOWN = LONG_MAX;
private int mType = TYPE_NONE;
private Media2DataSource mMedia2DataSource;
private FileDescriptor mFD;
private long mFDOffset = 0;
private long mFDLength = FD_LENGTH_UNKNOWN;
private Uri mUri;
private Map<String, String> mUriHeader;
private List<HttpCookie> mUriCookies;
private Context mUriContext;
private String mMediaId;
private long mStartPositionMs = 0;
private long mEndPositionMs = POSITION_UNKNOWN;
private DataSourceDesc() {
}
/**
* Return the media Id of data source.
* @return the media Id of data source
*/
public @Nullable String getMediaId() {
return mMediaId;
}
/**
* Return the position in milliseconds at which the playback will start.
* @return the position in milliseconds at which the playback will start
*/
public long getStartPosition() {
return mStartPositionMs;
}
/**
* Return the position in milliseconds at which the playback will end.
* {@link #POSITION_UNKNOWN} means ending at the end of source content.
* @return the position in milliseconds at which the playback will end
*/
public long getEndPosition() {
return mEndPositionMs;
}
/**
* Return the type of data source.
* @return the type of data source
*/
public int getType() {
return mType;
}
/**
* Return the Media2DataSource of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}.
* @return the Media2DataSource of this data source
*/
public @Nullable Media2DataSource getMedia2DataSource() {
return mMedia2DataSource;
}
/**
* Return the FileDescriptor of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
* @return the FileDescriptor of this data source
*/
public @Nullable FileDescriptor getFileDescriptor() {
return mFD;
}
/**
* Return the offset associated with the FileDescriptor of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_FD} and it has
* been set by the {@link Builder}.
* @return the offset associated with the FileDescriptor of this data source
*/
public long getFileDescriptorOffset() {
return mFDOffset;
}
/**
* Return the content length associated with the FileDescriptor of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
* {@link #FD_LENGTH_UNKNOWN} means same as the length of source content.
* @return the content length associated with the FileDescriptor of this data source
*/
public long getFileDescriptorLength() {
return mFDLength;
}
/**
* Return the Uri of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
* @return the Uri of this data source
*/
public @Nullable Uri getUri() {
return mUri;
}
/**
* Return the Uri headers of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
* @return the Uri headers of this data source
*/
public @Nullable Map<String, String> getUriHeaders() {
if (mUriHeader == null) {
return null;
}
return new HashMap<String, String>(mUriHeader);
}
/**
* Return the Uri cookies of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
* @return the Uri cookies of this data source
*/
public @Nullable List<HttpCookie> getUriCookies() {
if (mUriCookies == null) {
return null;
}
return new ArrayList<HttpCookie>(mUriCookies);
}
/**
* Return the Context used for resolving the Uri of this data source.
* It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
* @return the Context used for resolving the Uri of this data source
*/
public @Nullable Context getUriContext() {
return mUriContext;
}
/**
* Builder class for {@link DataSourceDesc} objects.
*/
public static class Builder {
private int mType = TYPE_NONE;
private Media2DataSource mMedia2DataSource;
private FileDescriptor mFD;
private long mFDOffset = 0;
private long mFDLength = FD_LENGTH_UNKNOWN;
private Uri mUri;
private Map<String, String> mUriHeader;
private List<HttpCookie> mUriCookies;
private Context mUriContext;
private String mMediaId;
private long mStartPositionMs = 0;
private long mEndPositionMs = POSITION_UNKNOWN;
/**
* Constructs a new Builder with the defaults.
*/
public Builder() {
}
/**
* Constructs a new Builder from a given {@link DataSourceDesc} instance
* @param dsd the {@link DataSourceDesc} object whose data will be reused
* in the new Builder.
*/
public Builder(@NonNull DataSourceDesc dsd) {
mType = dsd.mType;
mMedia2DataSource = dsd.mMedia2DataSource;
mFD = dsd.mFD;
mFDOffset = dsd.mFDOffset;
mFDLength = dsd.mFDLength;
mUri = dsd.mUri;
mUriHeader = dsd.mUriHeader;
mUriCookies = dsd.mUriCookies;
mUriContext = dsd.mUriContext;
mMediaId = dsd.mMediaId;
mStartPositionMs = dsd.mStartPositionMs;
mEndPositionMs = dsd.mEndPositionMs;
}
/**
* Combines all of the fields that have been set and return a new
* {@link DataSourceDesc} object. <code>IllegalStateException</code> will be
* thrown if there is conflict between fields.
*
* @return a new {@link DataSourceDesc} object
*/
public @NonNull DataSourceDesc build() {
if (mType != TYPE_CALLBACK
&& mType != TYPE_FD
&& mType != TYPE_URI) {
throw new IllegalStateException("Illegal type: " + mType);
}
if (mStartPositionMs > mEndPositionMs) {
throw new IllegalStateException("Illegal start/end position: "
+ mStartPositionMs + " : " + mEndPositionMs);
}
DataSourceDesc dsd = new DataSourceDesc();
dsd.mType = mType;
dsd.mMedia2DataSource = mMedia2DataSource;
dsd.mFD = mFD;
dsd.mFDOffset = mFDOffset;
dsd.mFDLength = mFDLength;
dsd.mUri = mUri;
dsd.mUriHeader = mUriHeader;
dsd.mUriCookies = mUriCookies;
dsd.mUriContext = mUriContext;
dsd.mMediaId = mMediaId;
dsd.mStartPositionMs = mStartPositionMs;
dsd.mEndPositionMs = mEndPositionMs;
return dsd;
}
/**
* Sets the media Id of this data source.
*
* @param mediaId the media Id of this data source
* @return the same Builder instance.
*/
public @NonNull Builder setMediaId(String mediaId) {
mMediaId = mediaId;
return this;
}
/**
* Sets the start position in milliseconds at which the playback will start.
* Any negative number is treated as 0.
*
* @param position the start position in milliseconds at which the playback will start
* @return the same Builder instance.
*
*/
public @NonNull Builder setStartPosition(long position) {
if (position < 0) {
position = 0;
}
mStartPositionMs = position;
return this;
}
/**
* Sets the end position in milliseconds at which the playback will end.
* Any negative number is treated as maximum length of the data source.
*
* @param position the end position in milliseconds at which the playback will end
* @return the same Builder instance.
*/
public @NonNull Builder setEndPosition(long position) {
if (position < 0) {
position = POSITION_UNKNOWN;
}
mEndPositionMs = position;
return this;
}
/**
* Sets the data source (Media2DataSource) to use.
*
* @param m2ds the Media2DataSource for the media you want to play
* @return the same Builder instance.
* @throws NullPointerException if m2ds is null.
*/
public @NonNull Builder setDataSource(@NonNull Media2DataSource m2ds) {
Preconditions.checkNotNull(m2ds);
resetDataSource();
mType = TYPE_CALLBACK;
mMedia2DataSource = m2ds;
return this;
}
/**
* Sets the data source (FileDescriptor) to use. The FileDescriptor must be
* seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
* to close the file descriptor after the source has been used.
*
* @param fd the FileDescriptor for the file you want to play
* @return the same Builder instance.
* @throws NullPointerException if fd is null.
*/
public @NonNull Builder setDataSource(@NonNull FileDescriptor fd) {
Preconditions.checkNotNull(fd);
resetDataSource();
mType = TYPE_FD;
mFD = fd;
return this;
}
/**
* Sets the data source (FileDescriptor) to use. The FileDescriptor must be
* seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
* to close the file descriptor after the source has been used.
*
* Any negative number for offset is treated as 0.
* Any negative number for length is treated as maximum length of the data source.
*
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts, in bytes
* @param length the length in bytes of the data to be played
* @return the same Builder instance.
* @throws NullPointerException if fd is null.
*/
public @NonNull Builder setDataSource(@NonNull FileDescriptor fd, long offset,
long length) {
Preconditions.checkNotNull(fd);
if (offset < 0) {
offset = 0;
}
if (length < 0) {
length = FD_LENGTH_UNKNOWN;
}
resetDataSource();
mType = TYPE_FD;
mFD = fd;
mFDOffset = offset;
mFDLength = length;
return this;
}
/**
* Sets the data source as a content Uri.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @return the same Builder instance.
* @throws NullPointerException if context or uri is null.
*/
public @NonNull Builder setDataSource(@NonNull Context context, @NonNull Uri uri) {
Preconditions.checkNotNull(context, "context cannot be null");
Preconditions.checkNotNull(uri, "uri cannot be null");
resetDataSource();
mType = TYPE_URI;
mUri = uri;
mUriContext = context;
return this;
}
/**
* Sets the data source as a content Uri.
*
* To provide cookies for the subsequent HTTP requests, you can install your own default
* cookie handler and use other variants of setDataSource APIs instead.
*
* <p><strong>Note</strong> that the cross domain redirection is allowed by default,
* but that can be changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
* disallow or allow cross domain redirection.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
* The headers must not include cookies. Instead, use the cookies param.
* @param cookies the cookies to be sent together with the request
* @return the same Builder instance.
* @throws NullPointerException if context or uri is null.
* @throws IllegalArgumentException if the cookie handler is not of CookieManager type
* when cookies are provided.
*/
public @NonNull Builder setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
Preconditions.checkNotNull(context, "context cannot be null");
Preconditions.checkNotNull(uri);
if (cookies != null) {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
throw new IllegalArgumentException(
"The cookie handler has to be of CookieManager type "
+ "when cookies are provided.");
}
}
resetDataSource();
mType = TYPE_URI;
mUri = uri;
if (headers != null) {
mUriHeader = new HashMap<String, String>(headers);
}
if (cookies != null) {
mUriCookies = new ArrayList<HttpCookie>(cookies);
}
mUriContext = context;
return this;
}
private void resetDataSource() {
mType = TYPE_NONE;
mMedia2DataSource = null;
mFD = null;
mFDOffset = 0;
mFDLength = FD_LENGTH_UNKNOWN;
mUri = null;
mUriHeader = null;
mUriCookies = null;
mUriContext = null;
}
}
}