blob: 9a9c74aba2c7df133c4334ab605132bb8ec349fd [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 android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
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;
/**
* Data source descriptor.
*
* Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
* {@link MediaPlayer2#setNextDataSources} to set data source for playback.
*
* @hide
*/
public class DataSourceDesc {
// intentionally less than long.MAX_VALUE
static final long LONG_MAX = 0x7ffffffffffffffL;
// keep consistent with native code
public static final long LONG_MAX_TIME_MS = LONG_MAX / 1000;
/**
* @hide
*/
public static final long LONG_MAX_TIME_US = LONG_MAX_TIME_MS * 1000;
public static final long POSITION_UNKNOWN = LONG_MAX_TIME_MS;
private String mMediaId;
private long mStartPositionMs = 0;
private long mEndPositionMs = POSITION_UNKNOWN;
DataSourceDesc(String mediaId, long startPositionMs, long endPositionMs) {
mMediaId = mediaId;
mStartPositionMs = startPositionMs;
mEndPositionMs = endPositionMs;
}
/**
* Releases the resources held by this {@code DataSourceDesc} object.
*/
void close() {
}
// Have to declare protected for finalize() since it is protected
// in the base class Object.
@Override
protected void finalize() throws Throwable {
close();
}
/**
* 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;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("DataSourceDesc{");
sb.append("mMediaId=").append(mMediaId);
sb.append(", mStartPositionMs=").append(mStartPositionMs);
sb.append(", mEndPositionMs=").append(mEndPositionMs);
sb.append('}');
return sb.toString();
}
/**
* Builder for {@link DataSourceDesc}.
* <p>
* Here is an example where <code>Builder</code> is used to define the
* {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance:
*
* <pre class="prettyprint">
* DataSourceDesc newDSD = new DataSourceDesc.Builder()
* .setDataSource(context, uri, headers, cookies)
* .setStartPosition(1000)
* .setEndPosition(15000)
* .build();
* mediaplayer2.setDataSourceDesc(newDSD);
* </pre>
*/
public static final class Builder {
private static final int SOURCE_TYPE_UNKNOWN = 0;
private static final int SOURCE_TYPE_URI = 1;
private static final int SOURCE_TYPE_FILE = 2;
private int mSourceType = SOURCE_TYPE_UNKNOWN;
private String mMediaId;
private long mStartPositionMs = 0;
private long mEndPositionMs = POSITION_UNKNOWN;
// For UriDataSourceDesc
private Uri mUri;
private Map<String, String> mHeader;
private List<HttpCookie> mCookies;
// For FileDataSourceDesc
private ParcelFileDescriptor mPFD;
private long mOffset = 0;
private long mLength = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
/**
* Constructs a new BuilderBase with the defaults.
*/
public Builder() {
}
/**
* Constructs a new BuilderBase from a given {@link DataSourceDesc} instance
* @param dsd the {@link DataSourceDesc} object whose data will be reused
* in the new BuilderBase.
*/
public Builder(@Nullable DataSourceDesc dsd) {
if (dsd == null) {
return;
}
mMediaId = dsd.mMediaId;
mStartPositionMs = dsd.mStartPositionMs;
mEndPositionMs = dsd.mEndPositionMs;
if (dsd instanceof FileDataSourceDesc) {
mSourceType = SOURCE_TYPE_FILE;
mPFD = ((FileDataSourceDesc) dsd).getParcelFileDescriptor();
mOffset = ((FileDataSourceDesc) dsd).getOffset();
mLength = ((FileDataSourceDesc) dsd).getLength();
} else if (dsd instanceof UriDataSourceDesc) {
mSourceType = SOURCE_TYPE_URI;
mUri = ((UriDataSourceDesc) dsd).getUri();
mHeader = ((UriDataSourceDesc) dsd).getHeaders();
mCookies = ((UriDataSourceDesc) dsd).getCookies();
} else {
throw new IllegalStateException("Unknown source type:" + mSourceType);
}
}
/**
* Sets all fields that have been set in the {@link DataSourceDesc} object.
* <code>IllegalStateException</code> will be thrown if there is conflict between fields.
*
* @return {@link DataSourceDesc}
*/
@NonNull
public DataSourceDesc build() {
if (mSourceType == SOURCE_TYPE_UNKNOWN) {
throw new IllegalStateException("Source is not set.");
}
if (mStartPositionMs > mEndPositionMs) {
throw new IllegalStateException("Illegal start/end position: "
+ mStartPositionMs + " : " + mEndPositionMs);
}
DataSourceDesc desc;
if (mSourceType == SOURCE_TYPE_FILE) {
desc = new FileDataSourceDesc(
mMediaId, mStartPositionMs, mEndPositionMs, mPFD, mOffset, mLength);
} else if (mSourceType == SOURCE_TYPE_URI) {
desc = new UriDataSourceDesc(
mMediaId, mStartPositionMs, mEndPositionMs, mUri, mHeader, mCookies);
} else {
throw new IllegalStateException("Unknown source type:" + mSourceType);
}
return desc;
}
/**
* Sets the media Id of this data source.
*
* @param mediaId the media Id of this data source
* @return the same Builder instance.
*/
@NonNull
public Builder setMediaId(@Nullable 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.
*
*/
@NonNull
public 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 duration {@link #LONG_MAX_TIME_MS}
* of the data source
*
* @param position the end position in milliseconds at which the playback will end
* @return the same Builder instance.
*/
@NonNull
public Builder setEndPosition(long position) {
if (position < 0) {
position = LONG_MAX_TIME_MS;
}
mEndPositionMs = position;
return this;
}
/**
* Sets the data source as a content 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.
*/
@NonNull
public Builder setDataSource(@NonNull Uri uri) {
setSourceType(SOURCE_TYPE_URI);
Media2Utils.checkArgument(uri != null, "uri cannot be null");
mUri = uri;
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. Alternatively, you
* can use this API to pass the cookies as a list of HttpCookie. If the app has not
* installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager
* and populates its CookieStore with the provided cookies when this data source is passed
* to {@link MediaPlayer2}. If the app has installed its own handler already, the handler
* is required to be of CookieManager type such that {@link MediaPlayer2} can update the
* manager’s CookieStore.
*
* <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 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.
*/
@NonNull
public Builder setDataSource(@NonNull Uri uri, @Nullable Map<String, String> headers,
@Nullable List<HttpCookie> cookies) {
setSourceType(SOURCE_TYPE_URI);
Media2Utils.checkArgument(uri != null, "uri cannot be null");
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.");
}
}
mUri = uri;
if (headers != null) {
mHeader = new HashMap<String, String>(headers);
}
if (cookies != null) {
mCookies = new ArrayList<HttpCookie>(cookies);
}
return this;
}
/**
* Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
* seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
* created by this builder is passed to {@link MediaPlayer2} via
* {@link MediaPlayer2#setDataSource},
* {@link MediaPlayer2#setNextDataSource} or
* {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
* close the ParcelFileDescriptor.
*
* @param pfd the ParcelFileDescriptor for the file to play
* @return the same Builder instance.
* @throws NullPointerException if pfd is null.
*/
@NonNull
public Builder setDataSource(@NonNull ParcelFileDescriptor pfd) {
setSourceType(SOURCE_TYPE_FILE);
Media2Utils.checkArgument(pfd != null, "pfd cannot be null.");
mPFD = pfd;
return this;
}
/**
* Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
* seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
* created by this builder is passed to {@link MediaPlayer2} via
* {@link MediaPlayer2#setDataSource},
* {@link MediaPlayer2#setNextDataSource} or
* {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
* close the ParcelFileDescriptor.
*
* Any negative number for offset is treated as 0.
* Any negative number for length is treated as maximum length of the data source.
*
* @param pfd the ParcelFileDescriptor for the file 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 pfd is null.
*/
@NonNull
public Builder setDataSource(
@NonNull ParcelFileDescriptor pfd, long offset, long length) {
setSourceType(SOURCE_TYPE_FILE);
if (pfd == null) {
throw new NullPointerException("pfd cannot be null.");
}
if (offset < 0) {
offset = 0;
}
if (length < 0) {
length = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
}
mPFD = pfd;
mOffset = offset;
mLength = length;
return this;
}
private void setSourceType(int type) {
if (mSourceType != SOURCE_TYPE_UNKNOWN) {
throw new IllegalStateException("Source is already set. type=" + mSourceType);
}
mSourceType = type;
}
}
}