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 b805eb4482cdf..1b21b5bc2afdf 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -137,7 +137,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 @@ -184,7 +184,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.
  • * @@ -206,15 +206,11 @@ import java.util.Vector; *
  • A MediaPlayer2 object must first enter the Prepared state * before playback can be started. *
      - *
    • There are two ways (synchronous vs. - * asynchronous) that the Prepared state can be reached: - * either a call to {@link #prepare()} (synchronous) which - * transfers the object to the Prepared state once the method call - * returns, or a call to {@link #prepareAsync()} (asynchronous) which - * first transfers the object to the Preparing state after the + *
    • {@link #prepareAsync()} first transfers the object to the + * Preparing 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 @@ -224,7 +220,7 @@ import java.util.Vector; * of calling any method with side effect while a MediaPlayer2 object is * in the Preparing state is undefined.
    • *
    • An IllegalStateException is - * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in + * thrown if {@link #prepareAsync()} is called in * any other state.
    • *
    • While in the Prepared state, properties * such as audio/sound volume, screenOnWhilePlaying, looping can be @@ -395,7 +391,7 @@ import java.util.Vector; * {Error}

      * 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().

      + * prepareAsync().

      * setAudioSessionId

      * {Idle}

      * {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, @@ -409,7 +405,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

      * {}

      @@ -561,9 +557,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 @@ -605,6 +604,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. @@ -774,10 +778,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); + } } /** @@ -787,10 +794,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); } /** @@ -826,11 +835,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(); + } } /** @@ -840,10 +852,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); } /** @@ -855,19 +869,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; + } } /** @@ -879,19 +895,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; + } } /** @@ -901,7 +919,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public int getCurrentPlaylistItemIndex() { - return mPLCurrentIndex; + synchronized (mPlLock) { + return mPlCurrentIndex; + } } /** @@ -920,12 +940,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. + } } /** @@ -935,7 +958,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public int getLoopingMode() { - return mPLCurrentIndex; + synchronized (mPlLock) { + return mPlCurrentIndex; + } } /** @@ -946,10 +971,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. } /** @@ -964,14 +991,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; + } } /** @@ -990,26 +1019,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; + } } } @@ -1036,42 +1067,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: @@ -1098,66 +1149,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) { @@ -1166,10 +1210,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; @@ -1184,19 +1228,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, @@ -1209,16 +1254,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 @@ -1229,53 +1275,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. * @@ -1925,7 +2010,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() { @@ -2000,7 +2085,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. @@ -3089,6 +3174,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( @@ -3127,6 +3225,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( @@ -3209,6 +3315,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; @@ -3517,7 +3629,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() { @@ -3568,7 +3680,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 51bc330698024..5a0081a146f20 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -273,9 +273,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) { @@ -292,10 +292,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; @@ -321,14 +321,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 ) { @@ -341,14 +347,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; } @@ -359,14 +365,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(); @@ -375,13 +381,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 ) { @@ -398,8 +411,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 @@ -503,20 +523,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 @@ -1449,18 +1465,25 @@ static void android_media_MediaPlayer2_native_on_stream_data_request(JNIEnv *env 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},