Merge "Remove MediaPlayer2"
am: 2e51cefc7e
Change-Id: I43f2b99b916bf989025d01495c0d8eeb73762886
This commit is contained in:
@@ -61,26 +61,6 @@ filegroup {
|
||||
path: "apex/java",
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "mediaplayer2-srcs",
|
||||
srcs: [
|
||||
"apex/java/android/media/CloseGuard.java",
|
||||
"apex/java/android/media/DataSourceCallback.java",
|
||||
"apex/java/android/media/DataSourceDesc.java",
|
||||
"apex/java/android/media/UriDataSourceDesc.java",
|
||||
"apex/java/android/media/FileDataSourceDesc.java",
|
||||
"apex/java/android/media/Media2Utils.java",
|
||||
"apex/java/android/media/MediaPlayer2Utils.java",
|
||||
"apex/java/android/media/MediaPlayer2.java",
|
||||
"apex/java/android/media/Media2HTTPService.java",
|
||||
"apex/java/android/media/Media2HTTPConnection.java",
|
||||
"apex/java/android/media/RoutingDelegate.java",
|
||||
"apex/java/android/media/BufferingParams.java",
|
||||
"apex/java/android/media/ProxyDataSourceCallback.java",
|
||||
],
|
||||
path: "apex/java",
|
||||
}
|
||||
|
||||
metalava_updatable_media_args = " --error UnhiddenSystemApi " +
|
||||
"--hide RequiresPermission " +
|
||||
"--hide MissingPermission --hide BroadcastBehavior " +
|
||||
|
||||
@@ -1,384 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* 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.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Structure of data source descriptor for sources using file descriptor.
|
||||
*
|
||||
* Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
|
||||
* {@link MediaPlayer2#setNextDataSources} to set data source for playback.
|
||||
*
|
||||
* <p>Users should use {@link Builder} to create {@link FileDataSourceDesc}.
|
||||
* @hide
|
||||
*/
|
||||
public class FileDataSourceDesc extends DataSourceDesc {
|
||||
private static final String TAG = "FileDataSourceDesc";
|
||||
|
||||
/**
|
||||
* Used when the length of file descriptor is unknown.
|
||||
*
|
||||
* @see #getLength()
|
||||
*/
|
||||
public static final long FD_LENGTH_UNKNOWN = LONG_MAX;
|
||||
|
||||
private ParcelFileDescriptor mPFD;
|
||||
private long mOffset = 0;
|
||||
private long mLength = FD_LENGTH_UNKNOWN;
|
||||
private int mCount = 0;
|
||||
private boolean mClosed = false;
|
||||
|
||||
FileDataSourceDesc(String mediaId, long startPositionMs, long endPositionMs,
|
||||
ParcelFileDescriptor pfd, long offset, long length) {
|
||||
super(mediaId, startPositionMs, endPositionMs);
|
||||
mPFD = pfd;
|
||||
mOffset = offset;
|
||||
mLength = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the resources held by this {@code FileDataSourceDesc} object.
|
||||
*/
|
||||
@Override
|
||||
void close() {
|
||||
super.close();
|
||||
decCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements usage count by {@link MediaPlayer2}.
|
||||
* If this is the last usage, also releases the file descriptor held by this
|
||||
* {@code FileDataSourceDesc} object.
|
||||
*/
|
||||
void decCount() {
|
||||
synchronized (this) {
|
||||
--mCount;
|
||||
if (mCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mPFD.close();
|
||||
mClosed = true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "failed to close pfd: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments usage count by {@link MediaPlayer2} if PFD has not been closed.
|
||||
*/
|
||||
void incCount() {
|
||||
synchronized (this) {
|
||||
if (!mClosed) {
|
||||
++mCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status of underline ParcelFileDescriptor
|
||||
* @return true if underline ParcelFileDescriptor is closed, false otherwise.
|
||||
*/
|
||||
boolean isPFDClosed() {
|
||||
synchronized (this) {
|
||||
return mClosed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ParcelFileDescriptor of this data source.
|
||||
* @return the ParcelFileDescriptor of this data source
|
||||
*/
|
||||
public @NonNull ParcelFileDescriptor getParcelFileDescriptor() {
|
||||
return mPFD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the offset associated with the ParcelFileDescriptor of this data source.
|
||||
* It's meaningful only when it has been set by the {@link Builder}.
|
||||
* @return the offset associated with the ParcelFileDescriptor of this data source
|
||||
*/
|
||||
public long getOffset() {
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content length associated with the ParcelFileDescriptor of this data source.
|
||||
* {@link #FD_LENGTH_UNKNOWN} means same as the length of source content.
|
||||
* @return the content length associated with the ParcelFileDescriptor of this data source
|
||||
*/
|
||||
public long getLength() {
|
||||
return mLength;
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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 static android.media.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
|
||||
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.UnknownServiceException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** @hide */
|
||||
public class Media2HTTPConnection {
|
||||
private static final String TAG = "Media2HTTPConnection";
|
||||
private static final boolean VERBOSE = false;
|
||||
|
||||
// connection timeout - 30 sec
|
||||
private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
|
||||
|
||||
private long mCurrentOffset = -1;
|
||||
private URL mURL = null;
|
||||
private Map<String, String> mHeaders = null;
|
||||
private HttpURLConnection mConnection = null;
|
||||
private long mTotalSize = -1;
|
||||
private InputStream mInputStream = null;
|
||||
|
||||
private boolean mAllowCrossDomainRedirect = true;
|
||||
private boolean mAllowCrossProtocolRedirect = true;
|
||||
|
||||
// from com.squareup.okhttp.internal.http
|
||||
private final static int HTTP_TEMP_REDIRECT = 307;
|
||||
private final static int MAX_REDIRECTS = 20;
|
||||
|
||||
public Media2HTTPConnection() {
|
||||
CookieHandler cookieHandler = CookieHandler.getDefault();
|
||||
if (cookieHandler == null) {
|
||||
Log.w(TAG, "Media2HTTPConnection: Unexpected. No CookieHandler found.");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean connect(String uri, String headers) {
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
|
||||
}
|
||||
|
||||
try {
|
||||
disconnect();
|
||||
mAllowCrossDomainRedirect = true;
|
||||
mURL = new URL(uri);
|
||||
mHeaders = convertHeaderStringToMap(headers);
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean parseBoolean(String val) {
|
||||
try {
|
||||
return Long.parseLong(val) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
return "true".equalsIgnoreCase(val) ||
|
||||
"yes".equalsIgnoreCase(val);
|
||||
}
|
||||
}
|
||||
|
||||
/* returns true iff header is internal */
|
||||
private boolean filterOutInternalHeaders(String key, String val) {
|
||||
if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
|
||||
mAllowCrossDomainRedirect = parseBoolean(val);
|
||||
// cross-protocol redirects are also controlled by this flag
|
||||
mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<String, String> convertHeaderStringToMap(String headers) {
|
||||
HashMap<String, String> map = new HashMap<String, String>();
|
||||
|
||||
String[] pairs = headers.split("\r\n");
|
||||
for (String pair : pairs) {
|
||||
int colonPos = pair.indexOf(":");
|
||||
if (colonPos >= 0) {
|
||||
String key = pair.substring(0, colonPos);
|
||||
String val = pair.substring(colonPos + 1);
|
||||
|
||||
if (!filterOutInternalHeaders(key, val)) {
|
||||
map.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
teardownConnection();
|
||||
mHeaders = null;
|
||||
mURL = null;
|
||||
}
|
||||
|
||||
private void teardownConnection() {
|
||||
if (mConnection != null) {
|
||||
if (mInputStream != null) {
|
||||
try {
|
||||
mInputStream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
mInputStream = null;
|
||||
}
|
||||
|
||||
mConnection.disconnect();
|
||||
mConnection = null;
|
||||
|
||||
mCurrentOffset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean isLocalHost(URL url) {
|
||||
if (url == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String host = url.getHost();
|
||||
|
||||
if (host == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (host.equalsIgnoreCase("localhost")) {
|
||||
return true;
|
||||
}
|
||||
if (InetAddress.getByName(host).isLoopbackAddress()) {
|
||||
return true;
|
||||
}
|
||||
} catch (IllegalArgumentException | UnknownHostException e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void seekTo(long offset) throws IOException {
|
||||
teardownConnection();
|
||||
|
||||
try {
|
||||
int response;
|
||||
int redirectCount = 0;
|
||||
|
||||
URL url = mURL;
|
||||
|
||||
// do not use any proxy for localhost (127.0.0.1)
|
||||
boolean noProxy = isLocalHost(url);
|
||||
|
||||
while (true) {
|
||||
if (noProxy) {
|
||||
mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
|
||||
} else {
|
||||
mConnection = (HttpURLConnection)url.openConnection();
|
||||
}
|
||||
mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
|
||||
|
||||
// handle redirects ourselves if we do not allow cross-domain redirect
|
||||
mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
|
||||
|
||||
if (mHeaders != null) {
|
||||
for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
|
||||
mConnection.setRequestProperty(
|
||||
entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (offset > 0) {
|
||||
mConnection.setRequestProperty(
|
||||
"Range", "bytes=" + offset + "-");
|
||||
}
|
||||
|
||||
response = mConnection.getResponseCode();
|
||||
if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
|
||||
response != HttpURLConnection.HTTP_MOVED_PERM &&
|
||||
response != HttpURLConnection.HTTP_MOVED_TEMP &&
|
||||
response != HttpURLConnection.HTTP_SEE_OTHER &&
|
||||
response != HTTP_TEMP_REDIRECT) {
|
||||
// not a redirect, or redirect handled by HttpURLConnection
|
||||
break;
|
||||
}
|
||||
|
||||
if (++redirectCount > MAX_REDIRECTS) {
|
||||
throw new NoRouteToHostException("Too many redirects: " + redirectCount);
|
||||
}
|
||||
|
||||
String method = mConnection.getRequestMethod();
|
||||
if (response == HTTP_TEMP_REDIRECT &&
|
||||
!method.equals("GET") && !method.equals("HEAD")) {
|
||||
// "If the 307 status code is received in response to a
|
||||
// request other than GET or HEAD, the user agent MUST NOT
|
||||
// automatically redirect the request"
|
||||
throw new NoRouteToHostException("Invalid redirect");
|
||||
}
|
||||
String location = mConnection.getHeaderField("Location");
|
||||
if (location == null) {
|
||||
throw new NoRouteToHostException("Invalid redirect");
|
||||
}
|
||||
url = new URL(mURL /* TRICKY: don't use url! */, location);
|
||||
if (!url.getProtocol().equals("https") &&
|
||||
!url.getProtocol().equals("http")) {
|
||||
throw new NoRouteToHostException("Unsupported protocol redirect");
|
||||
}
|
||||
boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
|
||||
if (!mAllowCrossProtocolRedirect && !sameProtocol) {
|
||||
throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
|
||||
}
|
||||
boolean sameHost = mURL.getHost().equals(url.getHost());
|
||||
if (!mAllowCrossDomainRedirect && !sameHost) {
|
||||
throw new NoRouteToHostException("Cross-domain redirects are disallowed");
|
||||
}
|
||||
|
||||
if (response != HTTP_TEMP_REDIRECT) {
|
||||
// update effective URL, unless it is a Temporary Redirect
|
||||
mURL = url;
|
||||
}
|
||||
}
|
||||
|
||||
if (mAllowCrossDomainRedirect) {
|
||||
// remember the current, potentially redirected URL if redirects
|
||||
// were handled by HttpURLConnection
|
||||
mURL = mConnection.getURL();
|
||||
}
|
||||
|
||||
if (response == HttpURLConnection.HTTP_PARTIAL) {
|
||||
// Partial content, we cannot just use getContentLength
|
||||
// because what we want is not just the length of the range
|
||||
// returned but the size of the full content if available.
|
||||
|
||||
String contentRange =
|
||||
mConnection.getHeaderField("Content-Range");
|
||||
|
||||
mTotalSize = -1;
|
||||
if (contentRange != null) {
|
||||
// format is "bytes xxx-yyy/zzz
|
||||
// where "zzz" is the total number of bytes of the
|
||||
// content or '*' if unknown.
|
||||
|
||||
int lastSlashPos = contentRange.lastIndexOf('/');
|
||||
if (lastSlashPos >= 0) {
|
||||
String total =
|
||||
contentRange.substring(lastSlashPos + 1);
|
||||
|
||||
try {
|
||||
mTotalSize = Long.parseLong(total);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (response != HttpURLConnection.HTTP_OK) {
|
||||
throw new IOException();
|
||||
} else {
|
||||
mTotalSize = mConnection.getContentLength();
|
||||
}
|
||||
|
||||
if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
|
||||
// Some servers simply ignore "Range" requests and serve
|
||||
// data from the start of the content.
|
||||
throw new ProtocolException();
|
||||
}
|
||||
|
||||
mInputStream =
|
||||
new BufferedInputStream(mConnection.getInputStream());
|
||||
|
||||
mCurrentOffset = offset;
|
||||
} catch (IOException e) {
|
||||
mTotalSize = -1;
|
||||
teardownConnection();
|
||||
mCurrentOffset = -1;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public int readAt(long offset, byte[] data, int size) {
|
||||
StrictMode.ThreadPolicy policy =
|
||||
new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||
|
||||
StrictMode.setThreadPolicy(policy);
|
||||
|
||||
try {
|
||||
if (offset != mCurrentOffset) {
|
||||
seekTo(offset);
|
||||
}
|
||||
|
||||
int n = mInputStream.read(data, 0, size);
|
||||
|
||||
if (n == -1) {
|
||||
// InputStream signals EOS using a -1 result, our semantics
|
||||
// are to return a 0-length read.
|
||||
n = 0;
|
||||
}
|
||||
|
||||
mCurrentOffset += n;
|
||||
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
|
||||
}
|
||||
|
||||
return n;
|
||||
} catch (ProtocolException e) {
|
||||
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
|
||||
return MEDIA_ERROR_UNSUPPORTED;
|
||||
} catch (NoRouteToHostException e) {
|
||||
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
|
||||
return MEDIA_ERROR_UNSUPPORTED;
|
||||
} catch (UnknownServiceException e) {
|
||||
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
|
||||
return MEDIA_ERROR_UNSUPPORTED;
|
||||
} catch (IOException e) {
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
|
||||
}
|
||||
return -1;
|
||||
} catch (Exception e) {
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "unknown exception " + e);
|
||||
Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
if (mConnection == null) {
|
||||
try {
|
||||
seekTo(0);
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return mTotalSize;
|
||||
}
|
||||
|
||||
public String getMIMEType() {
|
||||
if (mConnection == null) {
|
||||
try {
|
||||
seekTo(0);
|
||||
} catch (IOException e) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
return mConnection.getContentType();
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return mURL.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.util.Log;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.util.List;
|
||||
|
||||
/** @hide */
|
||||
public class Media2HTTPService {
|
||||
private static final String TAG = "Media2HTTPService";
|
||||
private List<HttpCookie> mCookies;
|
||||
private Boolean mCookieStoreInitialized = new Boolean(false);
|
||||
|
||||
public Media2HTTPService(List<HttpCookie> cookies) {
|
||||
mCookies = cookies;
|
||||
Log.v(TAG, "Media2HTTPService(" + this + "): Cookies: " + cookies);
|
||||
}
|
||||
|
||||
public Media2HTTPConnection makeHTTPConnection() {
|
||||
|
||||
synchronized (mCookieStoreInitialized) {
|
||||
Media2Utils.storeCookies(mCookies);
|
||||
}
|
||||
|
||||
return new Media2HTTPConnection();
|
||||
}
|
||||
|
||||
/* package private */ static Media2HTTPService createHTTPService(String path) {
|
||||
return createHTTPService(path, null);
|
||||
}
|
||||
|
||||
// when cookies are provided
|
||||
static Media2HTTPService createHTTPService(String path, List<HttpCookie> cookies) {
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||
return (new Media2HTTPService(cookies));
|
||||
} else if (path.startsWith("widevine://")) {
|
||||
Log.d(TAG, "Widevine classic is no longer supported");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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.util.Log;
|
||||
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.List;
|
||||
|
||||
/** @hide */
|
||||
public class Media2Utils {
|
||||
private static final String TAG = "Media2Utils";
|
||||
|
||||
private Media2Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that an expression checking an argument is true.
|
||||
*
|
||||
* @param expression the expression to check
|
||||
* @param errorMessage the exception message to use if the check fails; will
|
||||
* be converted to a string using {@link String#valueOf(Object)}
|
||||
* @throws IllegalArgumentException if {@code expression} is false
|
||||
*/
|
||||
public static void checkArgument(boolean expression, String errorMessage) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void storeCookies(List<HttpCookie> cookies) {
|
||||
CookieHandler cookieHandler = CookieHandler.getDefault();
|
||||
if (cookieHandler == null) {
|
||||
cookieHandler = new CookieManager();
|
||||
CookieHandler.setDefault(cookieHandler);
|
||||
Log.v(TAG, "storeCookies: CookieManager created: " + cookieHandler);
|
||||
} else {
|
||||
Log.v(TAG, "storeCookies: CookieHandler (" + cookieHandler + ") exists.");
|
||||
}
|
||||
|
||||
if (cookies != null) {
|
||||
if (cookieHandler instanceof CookieManager) {
|
||||
CookieManager cookieManager = (CookieManager)cookieHandler;
|
||||
CookieStore store = cookieManager.getCookieStore();
|
||||
for (HttpCookie cookie : cookies) {
|
||||
try {
|
||||
store.add(null, cookie);
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "storeCookies: CookieStore.add" + cookie, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "storeCookies: The installed CookieHandler is not a CookieManager."
|
||||
+ " Can’t add the provided cookies to the cookie store.");
|
||||
}
|
||||
} // cookies
|
||||
|
||||
Log.v(TAG, "storeCookies: cookieHandler: " + cookieHandler + " Cookies: " + cookies);
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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;
|
||||
|
||||
/**
|
||||
* Helper class used by native code to reduce JNI calls from native side.
|
||||
* @hide
|
||||
*/
|
||||
public class MediaPlayer2Utils {
|
||||
/**
|
||||
* Returns whether audio offloading is supported for the given audio format.
|
||||
*
|
||||
* @param encoding the type of encoding defined in {@link AudioFormat}
|
||||
* @param sampleRate the sampling rate of the stream
|
||||
* @param channelMask the channel mask defined in {@link AudioFormat}
|
||||
*/
|
||||
// @CalledByNative
|
||||
public static boolean isOffloadedAudioPlaybackSupported(
|
||||
int encoding, int sampleRate, int channelMask) {
|
||||
final AudioFormat format = new AudioFormat.Builder()
|
||||
.setEncoding(encoding)
|
||||
.setSampleRate(sampleRate)
|
||||
.setChannelMask(channelMask)
|
||||
.build();
|
||||
//TODO MP2 needs to pass AudioAttributes for this query, instead of using default attr
|
||||
return AudioManager.isOffloadedPlaybackSupported(format,
|
||||
(new AudioAttributes.Builder()).build());
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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 java.net.HttpCookie;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Structure of data source descriptor for sources using URI.
|
||||
*
|
||||
* Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
|
||||
* {@link MediaPlayer2#setNextDataSources} to set data source for playback.
|
||||
*
|
||||
* <p>Users should use {@link Builder} to change {@link UriDataSourceDesc}.
|
||||
* @hide
|
||||
*/
|
||||
public class UriDataSourceDesc extends DataSourceDesc {
|
||||
private Uri mUri;
|
||||
private Map<String, String> mHeader;
|
||||
private List<HttpCookie> mCookies;
|
||||
|
||||
UriDataSourceDesc(String mediaId, long startPositionMs, long endPositionMs,
|
||||
Uri uri, Map<String, String> header, List<HttpCookie> cookies) {
|
||||
super(mediaId, startPositionMs, endPositionMs);
|
||||
mUri = uri;
|
||||
mHeader = header;
|
||||
mCookies = cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Uri of this data source.
|
||||
* @return the Uri of this data source
|
||||
*/
|
||||
public @NonNull Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Uri headers of this data source.
|
||||
* @return the Uri headers of this data source
|
||||
*/
|
||||
public @Nullable Map<String, String> getHeaders() {
|
||||
if (mHeader == null) {
|
||||
return null;
|
||||
}
|
||||
return new HashMap<String, String>(mHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Uri cookies of this data source.
|
||||
* @return the Uri cookies of this data source
|
||||
*/
|
||||
public @Nullable List<HttpCookie> getCookies() {
|
||||
if (mCookies == null) {
|
||||
return null;
|
||||
}
|
||||
return new ArrayList<HttpCookie>(mCookies);
|
||||
}
|
||||
}
|
||||
@@ -113,88 +113,6 @@ cc_library_shared {
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libmedia2_jni",
|
||||
|
||||
srcs: [
|
||||
"android_media_DataSourceCallback.cpp",
|
||||
"android_media_MediaMetricsJNI.cpp",
|
||||
"android_media_MediaPlayer2.cpp",
|
||||
"android_media_SyncParams.cpp",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
// NDK or LLNDK or NDK-compliant
|
||||
"libandroid",
|
||||
"libbinder_ndk",
|
||||
"libcgrouprc",
|
||||
"libmediandk",
|
||||
"libmediametrics",
|
||||
"libnativehelper_compat_libc++",
|
||||
"liblog",
|
||||
"libvndksupport",
|
||||
],
|
||||
|
||||
header_libs: [
|
||||
"libhardware_headers",
|
||||
"libnativewindow_headers",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
// MediaCas
|
||||
"android.hidl.allocator@1.0",
|
||||
"android.hidl.memory@1.0",
|
||||
"libhidlbase",
|
||||
"libhidlmemory",
|
||||
"libbinderthreadstate",
|
||||
|
||||
// MediaPlayer2 implementation
|
||||
"libbase",
|
||||
"libcrypto",
|
||||
"libcutils",
|
||||
"libjsoncpp",
|
||||
"libmedia_player2_util",
|
||||
"libmediaplayer2",
|
||||
"libmediaplayer2-protos",
|
||||
"libmediandk_utils",
|
||||
"libmediautils",
|
||||
"libprocessgroup",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libstagefright_esds",
|
||||
"libstagefright_foundation_without_imemory",
|
||||
"libstagefright_httplive",
|
||||
"libstagefright_id3",
|
||||
"libstagefright_mpeg2support",
|
||||
"libstagefright_nuplayer2",
|
||||
"libstagefright_player2",
|
||||
"libstagefright_rtsp_player2",
|
||||
"libstagefright_timedtext2",
|
||||
"libutils",
|
||||
"libmedia2_jni_core",
|
||||
],
|
||||
|
||||
group_static_libs: true,
|
||||
|
||||
include_dirs: [
|
||||
"frameworks/base/core/jni",
|
||||
"frameworks/native/include/media/openmax",
|
||||
"system/media/camera/include",
|
||||
],
|
||||
|
||||
export_include_dirs: ["."],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-error=deprecated-declarations",
|
||||
"-Wunused",
|
||||
"-Wunreachable-code",
|
||||
"-fvisibility=hidden",
|
||||
],
|
||||
|
||||
ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
|
||||
}
|
||||
|
||||
subdirs = [
|
||||
"audioeffect",
|
||||
"soundpool",
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017, 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.
|
||||
*/
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "JDataSourceCallback-JNI"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "android_media_DataSourceCallback.h"
|
||||
|
||||
#include "log/log.h"
|
||||
#include "jni.h"
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
|
||||
#include <drm/drm_framework_common.h>
|
||||
#include <mediaplayer2/JavaVMHelper.h>
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
#include <nativehelper/ScopedLocalRef.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
static const size_t kBufferSize = 64 * 1024;
|
||||
|
||||
JDataSourceCallback::JDataSourceCallback(JNIEnv* env, jobject source)
|
||||
: mJavaObjStatus(OK),
|
||||
mSizeIsCached(false),
|
||||
mCachedSize(0) {
|
||||
mDataSourceCallbackObj = env->NewGlobalRef(source);
|
||||
CHECK(mDataSourceCallbackObj != NULL);
|
||||
|
||||
ScopedLocalRef<jclass> media2DataSourceClass(env, env->GetObjectClass(mDataSourceCallbackObj));
|
||||
CHECK(media2DataSourceClass.get() != NULL);
|
||||
|
||||
mReadAtMethod = env->GetMethodID(media2DataSourceClass.get(), "readAt", "(J[BII)I");
|
||||
CHECK(mReadAtMethod != NULL);
|
||||
mGetSizeMethod = env->GetMethodID(media2DataSourceClass.get(), "getSize", "()J");
|
||||
CHECK(mGetSizeMethod != NULL);
|
||||
mCloseMethod = env->GetMethodID(media2DataSourceClass.get(), "close", "()V");
|
||||
CHECK(mCloseMethod != NULL);
|
||||
|
||||
ScopedLocalRef<jbyteArray> tmp(env, env->NewByteArray(kBufferSize));
|
||||
mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get());
|
||||
CHECK(mByteArrayObj != NULL);
|
||||
}
|
||||
|
||||
JDataSourceCallback::~JDataSourceCallback() {
|
||||
JNIEnv* env = JavaVMHelper::getJNIEnv();
|
||||
env->DeleteGlobalRef(mDataSourceCallbackObj);
|
||||
env->DeleteGlobalRef(mByteArrayObj);
|
||||
}
|
||||
|
||||
status_t JDataSourceCallback::initCheck() const {
|
||||
return OK;
|
||||
}
|
||||
|
||||
ssize_t JDataSourceCallback::readAt(off64_t offset, void *data, size_t size) {
|
||||
Mutex::Autolock lock(mLock);
|
||||
|
||||
if (mJavaObjStatus != OK) {
|
||||
return -1;
|
||||
}
|
||||
if (size > kBufferSize) {
|
||||
size = kBufferSize;
|
||||
}
|
||||
|
||||
JNIEnv* env = JavaVMHelper::getJNIEnv();
|
||||
jint numread = env->CallIntMethod(mDataSourceCallbackObj, mReadAtMethod,
|
||||
(jlong)offset, mByteArrayObj, (jint)0, (jint)size);
|
||||
if (env->ExceptionCheck()) {
|
||||
ALOGW("An exception occurred in readAt()");
|
||||
jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
|
||||
env->ExceptionClear();
|
||||
mJavaObjStatus = UNKNOWN_ERROR;
|
||||
return -1;
|
||||
}
|
||||
if (numread < 0) {
|
||||
if (numread != -1) {
|
||||
ALOGW("An error occurred in readAt()");
|
||||
mJavaObjStatus = UNKNOWN_ERROR;
|
||||
return -1;
|
||||
} else {
|
||||
// numread == -1 indicates EOF
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)numread > size) {
|
||||
ALOGE("readAt read too many bytes.");
|
||||
mJavaObjStatus = UNKNOWN_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread);
|
||||
env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)data);
|
||||
return numread;
|
||||
}
|
||||
|
||||
status_t JDataSourceCallback::getSize(off64_t* size) {
|
||||
Mutex::Autolock lock(mLock);
|
||||
|
||||
if (mJavaObjStatus != OK) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
if (mSizeIsCached) {
|
||||
*size = mCachedSize;
|
||||
return OK;
|
||||
}
|
||||
|
||||
JNIEnv* env = JavaVMHelper::getJNIEnv();
|
||||
*size = env->CallLongMethod(mDataSourceCallbackObj, mGetSizeMethod);
|
||||
if (env->ExceptionCheck()) {
|
||||
ALOGW("An exception occurred in getSize()");
|
||||
jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
|
||||
env->ExceptionClear();
|
||||
// After returning an error, size shouldn't be used by callers.
|
||||
*size = UNKNOWN_ERROR;
|
||||
mJavaObjStatus = UNKNOWN_ERROR;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// The minimum size should be -1, which indicates unknown size.
|
||||
if (*size < 0) {
|
||||
*size = -1;
|
||||
}
|
||||
|
||||
mCachedSize = *size;
|
||||
mSizeIsCached = true;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void JDataSourceCallback::close() {
|
||||
Mutex::Autolock lock(mLock);
|
||||
|
||||
JNIEnv* env = JavaVMHelper::getJNIEnv();
|
||||
env->CallVoidMethod(mDataSourceCallbackObj, mCloseMethod);
|
||||
// The closed state is effectively the same as an error state.
|
||||
mJavaObjStatus = UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
String8 JDataSourceCallback::toString() {
|
||||
return String8::format("JDataSourceCallback(pid %d, uid %d)", getpid(), getuid());
|
||||
}
|
||||
|
||||
String8 JDataSourceCallback::getMIMEType() const {
|
||||
return String8("application/octet-stream");
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -23,9 +23,8 @@
|
||||
#include <media/MediaAnalyticsItem.h>
|
||||
|
||||
|
||||
// This source file is compiled and linked into both:
|
||||
// This source file is compiled and linked into:
|
||||
// core/jni/ (libandroid_runtime.so)
|
||||
// media/jni (libmedia2_jni.so)
|
||||
|
||||
namespace android {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
||||
java_library_static {
|
||||
name: "mediaplayer2-protos",
|
||||
host_supported: true,
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["mediaplayer2.proto"],
|
||||
jarjar_rules: "jarjar-rules.txt",
|
||||
sdk_version: "28",
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libmediaplayer2-protos",
|
||||
host_supported: true,
|
||||
proto: {
|
||||
export_proto_headers: true,
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["mediaplayer2.proto"],
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
rule com.google.protobuf.** android.media.protobuf.@1
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
// C++ namespace: android::media:MediaPlayer2Proto:
|
||||
package android.media.MediaPlayer2Proto;
|
||||
|
||||
option java_package = "android.media";
|
||||
option java_outer_classname = "MediaPlayer2Proto";
|
||||
|
||||
message Value {
|
||||
// The kind of value.
|
||||
oneof kind {
|
||||
// Represents a boolean value.
|
||||
bool bool_value = 1;
|
||||
// Represents an int32 value.
|
||||
int32 int32_value = 2;
|
||||
// Represents an uint32 value.
|
||||
uint32 uint32_value = 3;
|
||||
// Represents an int64 value.
|
||||
int64 int64_value = 4;
|
||||
// Represents an uint64 value.
|
||||
uint64 uint64_value = 5;
|
||||
// Represents a float value.
|
||||
double float_value = 6;
|
||||
// Represents a double value.
|
||||
double double_value = 7;
|
||||
// Represents a string value.
|
||||
string string_value = 8;
|
||||
// Represents a bytes value.
|
||||
bytes bytes_value = 9;
|
||||
}
|
||||
}
|
||||
|
||||
message PlayerMessage {
|
||||
repeated Value values = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user