From de0c3979ce4bdb9464645d6a898b8e1e042cf33f Mon Sep 17 00:00:00 2001
From: Wei Jia
Date: Thu, 15 Feb 2018 16:53:18 -0800
Subject: [PATCH] MediaPlayer2: initial code for playlist support
Test: MediaPlayer2 plays
Bug: 63934228
Change-Id: I6bb8be8c2bda9221e252e54c14b95107eccf4e00
---
media/java/android/media/DataSourceDesc.java | 14 +
media/java/android/media/MediaPlayer2.java | 13 -
.../java/android/media/MediaPlayer2Impl.java | 482 +++++++++++-------
media/jni/android_media_MediaPlayer2.cpp | 97 ++--
4 files changed, 371 insertions(+), 235 deletions(-)
diff --git a/media/java/android/media/DataSourceDesc.java b/media/java/android/media/DataSourceDesc.java
index 73fad7ad4bf37..6dff07f960925 100644
--- a/media/java/android/media/DataSourceDesc.java
+++ b/media/java/android/media/DataSourceDesc.java
@@ -30,6 +30,8 @@ import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.net.CookieHandler;
+import java.net.CookieManager;
import java.net.HttpCookie;
import java.util.ArrayList;
@@ -433,10 +435,22 @@ public final class DataSourceDesc {
* @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 Builder setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map headers, @Nullable List 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;
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index e331b2cbf645d..2f3d972ae6e24 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -803,19 +803,6 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener
*/
public abstract DataSourceDesc editPlaylistItem(int index, DataSourceDesc dsd);
- /**
- * Prepares the player for playback, synchronously.
- *
- * After setting the datasource and the display surface, you need to either
- * call prepare() or prepareAsync(). For files, it is OK to call prepare(),
- * which blocks until MediaPlayer2 is ready for playback.
- *
- * @throws IOException if source can not be accessed
- * @throws IllegalStateException if it is called in an invalid state
- * @hide
- */
- public void prepare() throws IOException { }
-
/**
* Prepares the player for playback, asynchronously.
*
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 49bbc2b34858c..379642766f259 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -152,7 +152,7 @@ import java.util.Vector;
* {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)},
* {@link #setLooping(boolean)},
* {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()},
- * {@link #seekTo(long, int)}, {@link #prepare()} or
+ * {@link #seekTo(long, int)} or
* {@link #prepareAsync()} in the Idle state for both cases. If any of these
* methods is called right after a MediaPlayer2 object is constructed,
* the user supplied callback method OnErrorListener.onError() won't be
@@ -199,7 +199,7 @@ import java.util.Vector;
* register a OnErrorListener to look out for error notifications from
* the internal player engine.
* IllegalStateException is
- * thrown to prevent programming errors such as calling {@link #prepare()},
+ * thrown to prevent programming errors such as calling
* {@link #prepareAsync()}, {@link #setDataSource(DataSourceDesc)}, or
* {@code setPlaylist} methods in an invalid state.
*
@@ -221,15 +221,11 @@ import java.util.Vector;
* A MediaPlayer2 object must first enter the Prepared state
* before playback can be started.
*
* | setAudioSessionId |
* {Idle} |
* {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
@@ -424,7 +420,7 @@ import java.util.Vector;
* | {Error} |
* Successful invoke of this method does not change the state. In order for the
* target audio stream type to become effective, this method must be called before
- * prepare() or prepareAsync(). |
+ * prepareAsync().
* | setAuxEffectSendLevel |
* any |
* {} |
@@ -576,9 +572,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
private boolean mBypassInterruptionPolicy;
private final CloseGuard mGuard = CloseGuard.get();
+ private final Object mPlLock = new Object();
private List mPlaylist;
- private int mPLCurrentIndex = 0;
- private int mPLNextIndex = -1;
+ private int mPlCurrentIndex = 0;
+ private int mPlNextIndex = -1;
+ private int mPlNextSourceState = NEXT_SOURCE_STATE_INIT;
+ private boolean mPlNextSourcePlayPending = false;
private int mLoopingMode = LOOPING_MODE_NONE;
// Modular DRM
@@ -620,6 +619,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
native_setup(new WeakReference(this));
}
+ private static final int NEXT_SOURCE_STATE_ERROR = -1;
+ private static final int NEXT_SOURCE_STATE_INIT = 0;
+ private static final int NEXT_SOURCE_STATE_PREPARING = 1;
+ private static final int NEXT_SOURCE_STATE_PREPARED = 2;
+
/*
* Update the MediaPlayer2Impl SurfaceTexture.
* Call after setting a new display surface.
@@ -789,10 +793,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
@Override
public void setDataSource(@NonNull DataSourceDesc dsd) throws IOException {
Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
- mPlaylist = Collections.synchronizedList(new ArrayList(1));
- mPlaylist.add(dsd);
- mPLCurrentIndex = 0;
- setDataSourcePriv(dsd);
+ synchronized (mPlLock) {
+ mPlaylist = Collections.synchronizedList(new ArrayList(1));
+ mPlaylist.add(dsd);
+ mPlCurrentIndex = 0;
+ mPlNextIndex = -1;
+ handleDataSource(true /* isCurrent */, dsd);
+ }
}
/**
@@ -802,10 +809,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public DataSourceDesc getCurrentDataSource() {
- if (mPlaylist == null) {
- return null;
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ return null;
+ }
+ return mPlaylist.get(mPlCurrentIndex);
}
- return mPlaylist.get(mPLCurrentIndex);
}
/**
@@ -841,11 +850,14 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
startIndex = pl.size() - 1;
}
- mPlaylist = Collections.synchronizedList(new ArrayList(pl));
- mPLCurrentIndex = startIndex;
- setDataSourcePriv(mPlaylist.get(startIndex));
- // TODO: handle the preparation of next source in the play list.
- // It should be processed after current source is prepared.
+ synchronized (mPlLock) {
+ mPlaylist = Collections.synchronizedList(new ArrayList(pl));
+ mPlCurrentIndex = startIndex;
+ handleDataSource(true /* isCurrent */, mPlaylist.get(startIndex));
+ // TODO: handle the preparation of next source in the play list.
+ // It should be processed after current source is prepared.
+ mPlNextIndex = getNextIndex_l();
+ }
}
/**
@@ -855,10 +867,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public List getPlaylist() {
- if (mPlaylist == null) {
- return null;
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ return null;
+ }
+ return new ArrayList(mPlaylist);
}
- return new ArrayList(mPlaylist);
}
/**
@@ -870,19 +884,21 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public void setCurrentPlaylistItem(int index) {
- if (mPlaylist == null) {
- throw new IllegalArgumentException("play list has not been set yet.");
- }
- if (index < 0 || index >= mPlaylist.size()) {
- throw new IndexOutOfBoundsException("index is out of play list range.");
- }
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ if (index < 0 || index >= mPlaylist.size()) {
+ throw new IndexOutOfBoundsException("index is out of play list range.");
+ }
- if (index == mPLCurrentIndex) {
- return;
- }
+ if (index == mPlCurrentIndex) {
+ return;
+ }
- // TODO: in playing state, stop current source and start to play source of index.
- mPLCurrentIndex = index;
+ // TODO: in playing state, stop current source and start to play source of index.
+ mPlCurrentIndex = index;
+ }
}
/**
@@ -894,19 +910,21 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public void setNextPlaylistItem(int index) {
- if (mPlaylist == null) {
- throw new IllegalArgumentException("play list has not been set yet.");
- }
- if (index < 0 || index >= mPlaylist.size()) {
- throw new IndexOutOfBoundsException("index is out of play list range.");
- }
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ if (index < 0 || index >= mPlaylist.size()) {
+ throw new IndexOutOfBoundsException("index is out of play list range.");
+ }
- if (index == mPLNextIndex) {
- return;
- }
+ if (index == mPlNextIndex) {
+ return;
+ }
- // TODO: prepare the new next-to-be-played DataSourceDesc
- mPLNextIndex = index;
+ // TODO: prepare the new next-to-be-played DataSourceDesc
+ mPlNextIndex = index;
+ }
}
/**
@@ -916,7 +934,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public int getCurrentPlaylistItemIndex() {
- return mPLCurrentIndex;
+ synchronized (mPlLock) {
+ return mPlCurrentIndex;
+ }
}
/**
@@ -935,12 +955,15 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
&& mode != LOOPING_MODE_SHUFFLE) {
throw new IllegalArgumentException("mode is not supported.");
}
- mLoopingMode = mode;
- if (mPlaylist == null) {
- return;
- }
- // TODO: handle the new mode if necessary.
+ synchronized (mPlLock) {
+ mLoopingMode = mode;
+ if (mPlaylist == null) {
+ return;
+ }
+
+ // TODO: handle the new mode if necessary.
+ }
}
/**
@@ -950,7 +973,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public int getLoopingMode() {
- return mPLCurrentIndex;
+ synchronized (mPlLock) {
+ return mPlCurrentIndex;
+ }
}
/**
@@ -961,10 +986,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public void movePlaylistItem(int indexFrom, int indexTo) {
- if (mPlaylist == null) {
- throw new IllegalArgumentException("play list has not been set yet.");
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ // TODO: move the DataSourceDesc from indexFrom to indexTo.
}
- // TODO: move the DataSourceDesc from indexFrom to indexTo.
}
/**
@@ -979,14 +1006,16 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
*/
@Override
public DataSourceDesc removePlaylistItem(int index) {
- if (mPlaylist == null) {
- throw new IllegalArgumentException("play list has not been set yet.");
- }
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
- DataSourceDesc oldDsd = mPlaylist.remove(index);
- // TODO: if index == mPLCurrentIndex, stop current source and move to next one.
- // if index == mPLNextIndex, prepare the new next-to-be-played source.
- return oldDsd;
+ DataSourceDesc oldDsd = mPlaylist.remove(index);
+ // TODO: if index == mPlCurrentIndex, stop current source and move to next one.
+ // if index == mPlNextIndex, prepare the new next-to-be-played source.
+ return oldDsd;
+ }
}
/**
@@ -1005,26 +1034,28 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
public void addPlaylistItem(int index, DataSourceDesc dsd) {
Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
- if (mPlaylist == null) {
- if (index == 0) {
- mPlaylist = Collections.synchronizedList(new ArrayList());
- mPlaylist.add(dsd);
- mPLCurrentIndex = 0;
- return;
+ synchronized (mPlLock) {
+ if (mPlaylist == null) {
+ if (index == 0) {
+ mPlaylist = Collections.synchronizedList(new ArrayList());
+ mPlaylist.add(dsd);
+ mPlCurrentIndex = 0;
+ return;
+ }
+ throw new IllegalArgumentException("index should be 0 for first DataSourceDesc.");
}
- throw new IllegalArgumentException("index should be 0 for first DataSourceDesc.");
- }
- long id = dsd.getId();
- for (DataSourceDesc pldsd : mPlaylist) {
- if (id == pldsd.getId()) {
- throw new IllegalArgumentException("Id of dsd already exists in the play list.");
+ long id = dsd.getId();
+ for (DataSourceDesc pldsd : mPlaylist) {
+ if (id == pldsd.getId()) {
+ throw new IllegalArgumentException("Id of dsd already exists in the play list.");
+ }
}
- }
- mPlaylist.add(index, dsd);
- if (index <= mPLCurrentIndex) {
- ++mPLCurrentIndex;
+ mPlaylist.add(index, dsd);
+ if (index <= mPlCurrentIndex) {
+ ++mPlCurrentIndex;
+ }
}
}
@@ -1051,42 +1082,62 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
Preconditions.checkNotNull(mPlaylist, "the play list cannot be null");
long id = dsd.getId();
- for (int i = 0; i < mPlaylist.size(); ++i) {
- if (i == index) {
- continue;
+ synchronized (mPlLock) {
+ for (int i = 0; i < mPlaylist.size(); ++i) {
+ if (i == index) {
+ continue;
+ }
+ if (id == mPlaylist.get(i).getId()) {
+ throw new IllegalArgumentException(
+ "Id of dsd already exists in the play list.");
+ }
}
- if (id == mPlaylist.get(i).getId()) {
- throw new IllegalArgumentException("Id of dsd already exists in the play list.");
- }
- }
- // TODO: if needed, stop playback of current source, and start new dsd.
- DataSourceDesc oldDsd = mPlaylist.set(index, dsd);
- return mPlaylist.set(index, dsd);
+ // TODO: if needed, stop playback of current source, and start new dsd.
+ DataSourceDesc oldDsd = mPlaylist.set(index, dsd);
+ return mPlaylist.set(index, dsd);
+ }
}
- private void setDataSourcePriv(@NonNull DataSourceDesc dsd) throws IOException {
+ // Called with mPlLock acquired.
+ // TODO: support all looping modes
+ private int getNextIndex_l() {
+ if (mPlaylist.size() <= 1) {
+ return -1;
+ }
+ int index = mPlCurrentIndex + 1;
+ if (index >= mPlaylist.size()) {
+ index = 0;
+ }
+ return index;
+ }
+
+ private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd)
+ throws IOException {
Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
switch (dsd.getType()) {
case DataSourceDesc.TYPE_CALLBACK:
- setDataSourcePriv(dsd.getId(),
- dsd.getMedia2DataSource());
+ handleDataSource(isCurrent,
+ dsd.getId(),
+ dsd.getMedia2DataSource());
break;
case DataSourceDesc.TYPE_FD:
- setDataSourcePriv(dsd.getId(),
- dsd.getFileDescriptor(),
- dsd.getFileDescriptorOffset(),
- dsd.getFileDescriptorLength());
+ handleDataSource(isCurrent,
+ dsd.getId(),
+ dsd.getFileDescriptor(),
+ dsd.getFileDescriptorOffset(),
+ dsd.getFileDescriptorLength());
break;
case DataSourceDesc.TYPE_URI:
- setDataSourcePriv(dsd.getId(),
- dsd.getUriContext(),
- dsd.getUri(),
- dsd.getUriHeaders(),
- dsd.getUriCookies());
+ handleDataSource(isCurrent,
+ dsd.getId(),
+ dsd.getUriContext(),
+ dsd.getUri(),
+ dsd.getUriHeaders(),
+ dsd.getUriCookies());
break;
default:
@@ -1113,66 +1164,59 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* @throws NullPointerException if context or uri is null
* @throws IOException if uri has a file scheme and an I/O error occurs
*/
- private void setDataSourcePriv(long srcId, @NonNull Context context, @NonNull Uri uri,
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ @NonNull Context context, @NonNull Uri uri,
@Nullable Map headers, @Nullable List cookies)
throws IOException {
- if (context == null) {
- throw new NullPointerException("context param can not be null.");
- }
-
- if (uri == null) {
- throw new NullPointerException("uri param can not 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.");
- }
- }
-
// The context and URI usually belong to the calling user. Get a resolver for that user
// and strip out the userId from the URI if present.
final ContentResolver resolver = context.getContentResolver();
final String scheme = uri.getScheme();
final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority());
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
- setDataSourcePriv(srcId, uri.getPath(), null, null);
+ handleDataSource(isCurrent, srcId, uri.getPath(), null, null);
return;
- } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ }
+
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme)
&& Settings.AUTHORITY.equals(authority)) {
// Try cached ringtone first since the actual provider may not be
// encryption aware, or it may be stored on CE media storage
final int type = RingtoneManager.getDefaultType(uri);
final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
- if (attemptDataSource(srcId, resolver, cacheUri)) {
+ if (attemptDataSource(isCurrent, srcId, resolver, cacheUri)) {
return;
- } else if (attemptDataSource(srcId, resolver, actualUri)) {
- return;
- } else {
- setDataSourcePriv(srcId, uri.toString(), headers, cookies);
}
+ if (attemptDataSource(isCurrent, srcId, resolver, actualUri)) {
+ return;
+ }
+ handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies);
} else {
// Try requested Uri locally first, or fallback to media server
- if (attemptDataSource(srcId, resolver, uri)) {
+ if (attemptDataSource(isCurrent, srcId, resolver, uri)) {
return;
- } else {
- setDataSourcePriv(srcId, uri.toString(), headers, cookies);
}
+ handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies);
}
}
- private boolean attemptDataSource(long srcId, ContentResolver resolver, Uri uri) {
+ private boolean attemptDataSource(
+ boolean isCurrent, long srcId, ContentResolver resolver, Uri uri) {
try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
if (afd.getDeclaredLength() < 0) {
- setDataSourcePriv(srcId, afd.getFileDescriptor(), 0, DataSourceDesc.LONG_MAX);
+ handleDataSource(isCurrent,
+ srcId,
+ afd.getFileDescriptor(),
+ 0,
+ DataSourceDesc.LONG_MAX);
} else {
- setDataSourcePriv(srcId,
- afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
+ handleDataSource(isCurrent,
+ srcId,
+ afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
}
return true;
} catch (NullPointerException | SecurityException | IOException ex) {
@@ -1181,10 +1225,10 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
}
- private void setDataSourcePriv(
- long srcId, String path, Map headers, List cookies)
- throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
- {
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ String path, Map headers, List cookies)
+ throws IOException {
String[] keys = null;
String[] values = null;
@@ -1199,19 +1243,20 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
++i;
}
}
- setDataSourcePriv(srcId, path, keys, values, cookies);
+ handleDataSource(isCurrent, srcId, path, keys, values, cookies);
}
- private void setDataSourcePriv(long srcId, String path, String[] keys, String[] values,
- List cookies)
- throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ private void handleDataSource(boolean isCurrent, long srcId,
+ String path, String[] keys, String[] values, List cookies)
+ throws IOException {
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
if ("file".equals(scheme)) {
path = uri.getPath();
} else if (scheme != null) {
// handle non-file sources
- nativeSetDataSource(
+ nativeHandleDataSourceUrl(
+ isCurrent,
srcId,
Media2HTTPService.createHTTPService(path, cookies),
path,
@@ -1224,16 +1269,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
FileDescriptor fd = is.getFD();
- setDataSourcePriv(srcId, fd, 0, DataSourceDesc.LONG_MAX);
+ handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX);
is.close();
} else {
- throw new IOException("setDataSourcePriv failed.");
+ throw new IOException("handleDataSource failed.");
}
}
- private native void nativeSetDataSource(
- long srcId, Media2HTTPService httpService, String path, String[] keys, String[] values)
- throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+ private native void nativeHandleDataSourceUrl(
+ boolean isCurrent, long srcId,
+ Media2HTTPService httpService, String path, String[] keys, String[] values)
+ throws IOException;
/**
* Sets the data source (FileDescriptor) to use. The FileDescriptor must be
@@ -1244,53 +1290,92 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* @throws IllegalArgumentException if fd is not a valid FileDescriptor
* @throws IOException if fd can not be read
*/
- private void setDataSourcePriv(long srcId, FileDescriptor fd, long offset, long length)
- throws IOException {
- _setDataSource(srcId, fd, offset, length);
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ FileDescriptor fd, long offset, long length) throws IOException {
+ nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length);
}
- private native void _setDataSource(long srcId, FileDescriptor fd, long offset, long length)
- throws IOException;
+ private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
+ FileDescriptor fd, long offset, long length) throws IOException;
/**
* @throws IllegalStateException if it is called in an invalid state
* @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
*/
- private void setDataSourcePriv(long srcId, Media2DataSource dataSource) {
- _setDataSource(srcId, dataSource);
+ private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource) {
+ nativeHandleDataSourceCallback(isCurrent, srcId, dataSource);
}
- private native void _setDataSource(long srcId, Media2DataSource dataSource);
+ private native void nativeHandleDataSourceCallback(
+ boolean isCurrent, long srcId, Media2DataSource dataSource);
- /**
- * Prepares the player for playback, synchronously.
- *
- * After setting the datasource and the display surface, you need to either
- * call prepare() or prepareAsync(). For files, it is OK to call prepare(),
- * which blocks until MediaPlayer2 is ready for playback.
- *
- * @throws IOException if source can not be accessed
- * @throws IllegalStateException if it is called in an invalid state
- * @hide
- */
- @Override
- public void prepare() throws IOException {
- _prepare();
- scanInternalSubtitleTracks();
+ // This function shall be called with |mPlLock| acquired.
+ private void prepareNextDataSource_l() {
+ if (mPlNextIndex < 0 || mPlNextSourceState != NEXT_SOURCE_STATE_INIT) {
+ // There is no next source or it's in preparing or prepared state.
+ return;
+ }
- // DrmInfo, if any, has been resolved by now.
- synchronized (mDrmLock) {
- mDrmInfoResolved = true;
+ try {
+ mPlNextSourceState = NEXT_SOURCE_STATE_PREPARING;
+ handleDataSource(false /* isCurrent */, mPlaylist.get(mPlNextIndex));
+ } catch (Exception e) {
+ Message msg2 = mEventHandler.obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+ final long nextSrcId = mPlaylist.get(mPlNextIndex).getId();
+ mEventHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mEventHandler.handleMessage(msg2, nextSrcId);
+ }
+ });
}
}
- private native void _prepare() throws IOException, IllegalStateException;
+ // This function shall be called with |mPlLock| acquired.
+ private void playNextDataSource_l() {
+ if (mPlNextIndex < 0) {
+ return;
+ }
+
+ if (mPlNextSourceState == NEXT_SOURCE_STATE_PREPARED) {
+ // Switch to next source only when it's in prepared state.
+ mPlCurrentIndex = mPlNextIndex;
+ mPlNextIndex = getNextIndex_l();
+ mPlNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mPlNextSourcePlayPending = false;
+
+ long srcId = mPlaylist.get(mPlCurrentIndex).getId();
+ try {
+ nativePlayNextDataSource(srcId);
+ } catch (Exception e) {
+ Message msg2 = mEventHandler.obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+ mEventHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mEventHandler.handleMessage(msg2, srcId);
+ }
+ });
+ }
+
+ // Wait for MEDIA2_INFO_STARTED_AS_NEXT to prepare next source.
+ } else {
+ if (mPlNextSourceState == NEXT_SOURCE_STATE_INIT) {
+ prepareNextDataSource_l();
+ }
+ mPlNextSourcePlayPending = true;
+ }
+ }
+
+ private native void nativePlayNextDataSource(long srcId);
/**
* Prepares the player for playback, asynchronously.
*
* After setting the datasource and the display surface, you need to either
- * call prepare() or prepareAsync(). For streams, you should call prepareAsync(),
+ * call prepareAsync(). For streams, you should call prepareAsync(),
* which returns immediately, rather than blocking until enough data has been
* buffered.
*
@@ -1940,7 +2025,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
/**
* Resets the MediaPlayer2 to its uninitialized state. After calling
* this method, you will have to initialize it again by setting the
- * data source and calling prepare().
+ * data source and calling prepareAsync().
*/
@Override
public void reset() {
@@ -2015,7 +2100,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
/**
* Sets the audio attributes for this MediaPlayer2.
* See {@link AudioAttributes} for how to build and configure an instance of this class.
- * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order
+ * You must call this method before {@link #prepareAsync()} in order
* for the audio attributes to become effective thereafter.
* @param attributes a non-null set of audio attributes
* @throws IllegalArgumentException if the attributes are null or invalid.
@@ -3097,6 +3182,19 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
sendMessage(msg2);
}
+ synchronized (mPlLock) {
+ Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
+ + ", currentIndex=" + mPlCurrentIndex + ", nextIndex=" + mPlNextIndex);
+ if (mPlCurrentIndex >= 0 && srcId == mPlaylist.get(mPlCurrentIndex).getId()) {
+ prepareNextDataSource_l();
+ } else if (mPlNextIndex >= 0 && srcId == mPlaylist.get(mPlNextIndex).getId()) {
+ mPlNextSourceState = NEXT_SOURCE_STATE_PREPARED;
+ if (mPlNextSourcePlayPending) {
+ playNextDataSource_l();
+ }
+ }
+ }
+
synchronized (mEventCbLock) {
for (Pair cb : mEventCallbackRecords) {
cb.first.execute(() -> cb.second.onInfo(
@@ -3135,6 +3233,14 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
return;
case MEDIA_PLAYBACK_COMPLETE:
+ synchronized (mPlLock) {
+ if (mPlCurrentIndex >= 0 && srcId == mPlaylist.get(mPlCurrentIndex).getId()) {
+ Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
+ + ", currentIndex=" + mPlCurrentIndex + ", nextIndex=" + mPlNextIndex);
+ playNextDataSource_l();
+ }
+ }
+
synchronized (mEventCbLock) {
for (Pair cb : mEventCallbackRecords) {
cb.first.execute(() -> cb.second.onInfo(
@@ -3217,6 +3323,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
case MEDIA_INFO:
switch (msg.arg1) {
+ case MEDIA_INFO_STARTED_AS_NEXT:
+ if (mPlCurrentIndex >= 0 && srcId == mPlaylist.get(mPlCurrentIndex).getId()) {
+ prepareNextDataSource_l();
+ }
+ break;
+
case MEDIA_INFO_VIDEO_TRACK_LAGGING:
Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
break;
@@ -3525,7 +3637,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
/**
* Retrieves the DRM Info associated with the current source
*
- * @throws IllegalStateException if called before prepare()
+ * @throws IllegalStateException if called before prepareAsync()
*/
@Override
public DrmInfo getDrmInfo() {
@@ -3576,7 +3688,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
* from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
*
- * @throws IllegalStateException if called before prepare(), or the DRM was
+ * @throws IllegalStateException if called before prepareAsync(), or the DRM was
* prepared already
* @throws UnsupportedSchemeException if the crypto scheme is not supported
* @throws ResourceBusyException if required DRM resources are in use
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index e73b2f8c496da..fd2f933137694 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -272,9 +272,9 @@ static void process_media_player_call(
}
static void
-android_media_MediaPlayer2_setDataSourceAndHeaders(
- JNIEnv *env, jobject thiz, jlong srcId, jobject httpServiceObj, jstring path,
- jobjectArray keys, jobjectArray values) {
+android_media_MediaPlayer2_handleDataSourceUrl(
+ JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId,
+ jobject httpServiceObj, jstring path, jobjectArray keys, jobjectArray values) {
sp mp = getMediaPlayer(env, thiz);
if (mp == NULL) {
@@ -291,10 +291,10 @@ android_media_MediaPlayer2_setDataSourceAndHeaders(
if (tmp == NULL) { // Out of memory
return;
}
- ALOGV("setDataSourceAndHeaders: path %s, srcId %lld", tmp, (long long)srcId);
+ ALOGV("handleDataSourceUrl: path %s, srcId %lld", tmp, (long long)srcId);
if (strncmp(tmp, "content://", 10) == 0) {
- ALOGE("setDataSourceAndHeaders: content scheme is not supported in native code");
+ ALOGE("handleDataSourceUrl: content scheme is not supported in native code");
jniThrowException(env, "java/io/IOException",
"content scheme is not supported in native code");
return;
@@ -320,14 +320,20 @@ android_media_MediaPlayer2_setDataSourceAndHeaders(
}
dsd->mHttpService = httpService;
- process_media_player_call(
- env, thiz, mp->setDataSource(dsd), "java/io/IOException",
- "setDataSourceAndHeaders failed." );
+ status_t err;
+ if (isCurrent) {
+ err = mp->setDataSource(dsd);
+ } else {
+ err = mp->prepareNextDataSource(dsd);
+ }
+ process_media_player_call(env, thiz, err,
+ "java/io/IOException", "handleDataSourceUrl failed." );
}
static void
-android_media_MediaPlayer2_setDataSourceFD(
- JNIEnv *env, jobject thiz, jlong srcId, jobject fileDescriptor, jlong offset, jlong length)
+android_media_MediaPlayer2_handleDataSourceFD(
+ JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId,
+ jobject fileDescriptor, jlong offset, jlong length)
{
sp mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
@@ -340,14 +346,14 @@ android_media_MediaPlayer2_setDataSourceFD(
return;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- ALOGV("setDataSourceFD: srcId=%lld, fd=%d (%s), offset=%lld, length=%lld",
+ ALOGV("handleDataSourceFD: srcId=%lld, fd=%d (%s), offset=%lld, length=%lld",
(long long)srcId, fd, nameForFd(fd).c_str(), (long long)offset, (long long)length);
struct stat sb;
int ret = fstat(fd, &sb);
if (ret != 0) {
- ALOGE("setDataSourceFD: fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
- jniThrowException(env, "java/io/IOException", "setDataSourceFD failed fstat");
+ ALOGE("handleDataSourceFD: fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
+ jniThrowException(env, "java/io/IOException", "handleDataSourceFD failed fstat");
return;
}
@@ -358,14 +364,14 @@ android_media_MediaPlayer2_setDataSourceFD(
ALOGV("st_size = %llu", static_cast(sb.st_size));
if (offset >= sb.st_size) {
- ALOGE("setDataSourceFD: offset is out of range");
+ ALOGE("handleDataSourceFD: offset is out of range");
jniThrowException(env, "java/lang/IllegalArgumentException",
- "setDataSourceFD failed, offset is out of range.");
+ "handleDataSourceFD failed, offset is out of range.");
return;
}
if (offset + length > sb.st_size) {
length = sb.st_size - offset;
- ALOGV("setDataSourceFD: adjusted length = %lld", (long long)length);
+ ALOGV("handleDataSourceFD: adjusted length = %lld", (long long)length);
}
sp dsd = new DataSourceDesc();
@@ -374,13 +380,20 @@ android_media_MediaPlayer2_setDataSourceFD(
dsd->mFD = fd;
dsd->mFDOffset = offset;
dsd->mFDLength = length;
- process_media_player_call(env, thiz, mp->setDataSource(dsd),
- "java/io/IOException", "setDataSourceFD failed." );
+
+ status_t err;
+ if (isCurrent) {
+ err = mp->setDataSource(dsd);
+ } else {
+ err = mp->prepareNextDataSource(dsd);
+ }
+ process_media_player_call(env, thiz, err,
+ "java/io/IOException", "handleDataSourceFD failed." );
}
static void
-android_media_MediaPlayer2_setDataSourceCallback(
- JNIEnv *env, jobject thiz, jlong srcId, jobject dataSource)
+android_media_MediaPlayer2_handleDataSourceCallback(
+ JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId, jobject dataSource)
{
sp mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
@@ -397,8 +410,15 @@ android_media_MediaPlayer2_setDataSourceCallback(
dsd->mId = srcId;
dsd->mType = DataSourceDesc::TYPE_CALLBACK;
dsd->mCallbackSource = callbackDataSource;
- process_media_player_call(env, thiz, mp->setDataSource(dsd),
- "java/lang/RuntimeException", "setDataSourceCallback failed." );
+
+ status_t err;
+ if (isCurrent) {
+ err = mp->setDataSource(dsd);
+ } else {
+ err = mp->prepareNextDataSource(dsd);
+ }
+ process_media_player_call(env, thiz, err,
+ "java/lang/RuntimeException", "handleDataSourceCallback failed." );
}
static sp
@@ -502,20 +522,16 @@ android_media_MediaPlayer2_setBufferingParams(JNIEnv *env, jobject thiz, jobject
}
static void
-android_media_MediaPlayer2_prepare(JNIEnv *env, jobject thiz)
+android_media_MediaPlayer2_playNextDataSource(JNIEnv *env, jobject thiz, jlong srcId)
{
sp mp = getMediaPlayer(env, thiz);
- if (mp == NULL ) {
+ if (mp == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
- // Handle the case where the display surface was set before the mp was
- // initialized. We try again to make it stick.
- sp st = getVideoSurfaceTexture(env, thiz);
- mp->setVideoSurfaceTexture(st);
-
- process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
+ process_media_player_call(env, thiz, mp->playNextDataSource((int64_t)srcId),
+ "java/io/IOException", "playNextDataSource failed." );
}
static void
@@ -1389,18 +1405,25 @@ static void android_media_MediaPlayer2_enableDeviceCallback(
static const JNINativeMethod gMethods[] = {
{
- "nativeSetDataSource",
- "(JLandroid/media/Media2HTTPService;Ljava/lang/String;[Ljava/lang/String;"
+ "nativeHandleDataSourceUrl",
+ "(ZJLandroid/media/Media2HTTPService;Ljava/lang/String;[Ljava/lang/String;"
"[Ljava/lang/String;)V",
- (void *)android_media_MediaPlayer2_setDataSourceAndHeaders
+ (void *)android_media_MediaPlayer2_handleDataSourceUrl
},
-
- {"_setDataSource", "(JLjava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer2_setDataSourceFD},
- {"_setDataSource", "(JLandroid/media/Media2DataSource;)V",(void *)android_media_MediaPlayer2_setDataSourceCallback },
+ {
+ "nativeHandleDataSourceFD",
+ "(ZJLjava/io/FileDescriptor;JJ)V",
+ (void *)android_media_MediaPlayer2_handleDataSourceFD
+ },
+ {
+ "nativeHandleDataSourceCallback",
+ "(ZJLandroid/media/Media2DataSource;)V",
+ (void *)android_media_MediaPlayer2_handleDataSourceCallback
+ },
+ {"nativePlayNextDataSource", "(J)V", (void *)android_media_MediaPlayer2_playNextDataSource},
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer2_setVideoSurface},
{"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams},
{"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
- {"_prepare", "()V", (void *)android_media_MediaPlayer2_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer2_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer2_start},
{"_stop", "()V", (void *)android_media_MediaPlayer2_stop},