MediaPlayer2: initial code for playlist support

Test: MediaPlayer2 plays
Bug: 63934228
Change-Id: I6bb8be8c2bda9221e252e54c14b95107eccf4e00
This commit is contained in:
Wei Jia
2018-02-15 16:53:18 -08:00
parent 72fa42bee3
commit de0c3979ce
4 changed files with 371 additions and 235 deletions

View File

@@ -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<String, String> headers, @Nullable List<HttpCookie> cookies) {
Preconditions.checkNotNull(context, "context cannot be null");
Preconditions.checkNotNull(uri);
if (cookies != null) {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
throw new IllegalArgumentException(
"The cookie handler has to be of CookieManager type "
+ "when cookies are provided.");
}
}
resetDataSource();
mType = TYPE_URI;
mUri = uri;

View File

@@ -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.
*

View File

@@ -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 <em>Idle</em> 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.</li>
* <li>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. </li>
* </ul>
@@ -221,15 +221,11 @@ import java.util.Vector;
* <li>A MediaPlayer2 object must first enter the <em>Prepared</em> state
* before playback can be started.
* <ul>
* <li>There are two ways (synchronous vs.
* asynchronous) that the <em>Prepared</em> state can be reached:
* either a call to {@link #prepare()} (synchronous) which
* transfers the object to the <em>Prepared</em> state once the method call
* returns, or a call to {@link #prepareAsync()} (asynchronous) which
* first transfers the object to the <em>Preparing</em> state after the
* <li>{@link #prepareAsync()} first transfers the object to the
* <em>Preparing</em> state after the
* call returns (which occurs almost right way) while the internal
* player engine continues working on the rest of preparation work
* until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns,
* until the preparation work completes. When the preparation completes,
* the internal player engine then calls a user supplied callback method,
* onPrepared() of the EventCallback interface, if an
* EventCallback is registered beforehand via {@link
@@ -239,7 +235,7 @@ import java.util.Vector;
* of calling any method with side effect while a MediaPlayer2 object is
* in the <em>Preparing</em> state is undefined.</li>
* <li>An IllegalStateException is
* thrown if {@link #prepare()} or {@link #prepareAsync()} is called in
* thrown if {@link #prepareAsync()} is called in
* any other state.</li>
* <li>While in the <em>Prepared</em> state, properties
* such as audio/sound volume, screenOnWhilePlaying, looping can be
@@ -410,7 +406,7 @@ import java.util.Vector;
* <td>{Error}</p></td>
* <td>Successful invoke of this method does not change the state. In order for the
* target audio attributes type to become effective, this method must be called before
* prepare() or prepareAsync().</p></td></tr>
* prepareAsync().</p></td></tr>
* <tr><td>setAudioSessionId </p></td>
* <td>{Idle} </p></td>
* <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
@@ -424,7 +420,7 @@ import java.util.Vector;
* <td>{Error}</p></td>
* <td>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().</p></td></tr>
* prepareAsync().</p></td></tr>
* <tr><td>setAuxEffectSendLevel </p></td>
* <td>any</p></td>
* <td>{} </p></td>
@@ -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<DataSourceDesc> 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<MediaPlayer2Impl>(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<DataSourceDesc>(1));
mPlaylist.add(dsd);
mPLCurrentIndex = 0;
setDataSourcePriv(dsd);
synchronized (mPlLock) {
mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>(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<DataSourceDesc> 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<DataSourceDesc>());
mPlaylist.add(dsd);
mPLCurrentIndex = 0;
return;
synchronized (mPlLock) {
if (mPlaylist == null) {
if (index == 0) {
mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>());
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<String, String> headers, @Nullable List<HttpCookie> 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<String, String> headers, List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
private void handleDataSource(
boolean isCurrent, long srcId,
String path, Map<String, String> headers, List<HttpCookie> 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<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
private void handleDataSource(boolean isCurrent, long srcId,
String path, String[] keys, String[] values, List<HttpCookie> 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<Executor, EventCallback> 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<Executor, EventCallback> 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

View File

@@ -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<MediaPlayer2> 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<MediaPlayer2> 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<unsigned long long>(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<DataSourceDesc> 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<MediaPlayer2> 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<ANativeWindowWrapper>
@@ -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<MediaPlayer2> 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<ANativeWindowWrapper> 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},