diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java b/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java deleted file mode 100644 index 351c9d5780..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java +++ /dev/null @@ -1,707 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2; - -import android.os.Handler; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.analytics.AnalyticsCollector; -import com.google.android.exoplayer2.source.MaskingMediaPeriod; -import com.google.android.exoplayer2.source.MaskingMediaSource; -import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.MediaSourceEventListener; -import com.google.android.exoplayer2.source.ShuffleOrder; -import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; -import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified - * during playback. It is valid for the same {@link MediaSource} instance to be present more than - * once in the playlist. - * - *

With the exception of the constructor, all methods are called on the playback thread. - */ -/* package */ class Playlist { - - /** Listener for source events. */ - public interface PlaylistInfoRefreshListener { - - /** - * Called when the timeline of a media item has changed and a new timeline that reflects the - * current playlist state needs to be created by calling {@link #createTimeline()}. - * - *

Called on the playback thread. - */ - void onPlaylistUpdateRequested(); - } - - private final List mediaSourceHolders; - private final Map mediaSourceByMediaPeriod; - private final Map mediaSourceByUid; - private final PlaylistInfoRefreshListener playlistInfoListener; - private final MediaSourceEventListener.EventDispatcher eventDispatcher; - private final HashMap childSources; - private final Set enabledMediaSourceHolders; - - private ShuffleOrder shuffleOrder; - private boolean isPrepared; - - @Nullable private TransferListener mediaTransferListener; - - @SuppressWarnings("initialization") - public Playlist(PlaylistInfoRefreshListener listener) { - playlistInfoListener = listener; - shuffleOrder = new DefaultShuffleOrder(0); - mediaSourceByMediaPeriod = new IdentityHashMap<>(); - mediaSourceByUid = new HashMap<>(); - mediaSourceHolders = new ArrayList<>(); - eventDispatcher = new MediaSourceEventListener.EventDispatcher(); - childSources = new HashMap<>(); - enabledMediaSourceHolders = new HashSet<>(); - } - - /** - * Sets the media sources replacing any sources previously contained in the playlist. - * - * @param holders The list of {@link MediaSourceHolder}s to set. - * @param shuffleOrder The new shuffle order. - * @return The new {@link Timeline}. - */ - public final Timeline setMediaSources( - List holders, ShuffleOrder shuffleOrder) { - removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolders.size()); - return addMediaSources(/* index= */ this.mediaSourceHolders.size(), holders, shuffleOrder); - } - - /** - * Adds multiple {@link MediaSourceHolder}s to the playlist. - * - * @param index The index at which the new {@link MediaSourceHolder}s will be inserted. This index - * must be in the range of 0 <= index <= {@link #getSize()}. - * @param holders A list of {@link MediaSourceHolder}s to be added. - * @param shuffleOrder The new shuffle order. - * @return The new {@link Timeline}. - */ - public final Timeline addMediaSources( - int index, List holders, ShuffleOrder shuffleOrder) { - if (!holders.isEmpty()) { - this.shuffleOrder = shuffleOrder; - for (int insertionIndex = index; insertionIndex < index + holders.size(); insertionIndex++) { - MediaSourceHolder holder = holders.get(insertionIndex - index); - if (insertionIndex > 0) { - MediaSourceHolder previousHolder = mediaSourceHolders.get(insertionIndex - 1); - Timeline previousTimeline = previousHolder.mediaSource.getTimeline(); - holder.reset( - /* firstWindowInChildIndex= */ previousHolder.firstWindowIndexInChild - + previousTimeline.getWindowCount()); - } else { - holder.reset(/* firstWindowIndexInChild= */ 0); - } - Timeline newTimeline = holder.mediaSource.getTimeline(); - correctOffsets( - /* startIndex= */ insertionIndex, - /* windowOffsetUpdate= */ newTimeline.getWindowCount()); - mediaSourceHolders.add(insertionIndex, holder); - mediaSourceByUid.put(holder.uid, holder); - if (isPrepared) { - prepareChildSource(holder); - if (mediaSourceByMediaPeriod.isEmpty()) { - enabledMediaSourceHolders.add(holder); - } else { - disableChildSource(holder); - } - } - } - } - return createTimeline(); - } - - /** - * Removes a range of {@link MediaSourceHolder}s from the playlist, by specifying an initial index - * (included) and a final index (excluded). - * - *

Note: when specified range is empty, no actual media source is removed and no exception is - * thrown. - * - * @param fromIndex The initial range index, pointing to the first media source that will be - * removed. This index must be in the range of 0 <= index <= {@link #getSize()}. - * @param toIndex The final range index, pointing to the first media source that will be left - * untouched. This index must be in the range of 0 <= index <= {@link #getSize()}. - * @param shuffleOrder The new shuffle order. - * @return The new {@link Timeline}. - * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0, - * {@code toIndex} > {@link #getSize()}, {@code fromIndex} > {@code toIndex} - */ - public final Timeline removeMediaSourceRange( - int fromIndex, int toIndex, ShuffleOrder shuffleOrder) { - Assertions.checkArgument(fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize()); - this.shuffleOrder = shuffleOrder; - removeMediaSourcesInternal(fromIndex, toIndex); - return createTimeline(); - } - - /** - * Moves an existing media source within the playlist. - * - * @param currentIndex The current index of the media source in the playlist. This index must be - * in the range of 0 <= index < {@link #getSize()}. - * @param newIndex The target index of the media source in the playlist. This index must be in the - * range of 0 <= index < {@link #getSize()}. - * @param shuffleOrder The new shuffle order. - * @return The new {@link Timeline}. - * @throws IllegalArgumentException When an index is invalid, i.e. {@code currentIndex} < 0, - * {@code currentIndex} >= {@link #getSize()}, {@code newIndex} < 0 - */ - public final Timeline moveMediaSource(int currentIndex, int newIndex, ShuffleOrder shuffleOrder) { - return moveMediaSourceRange(currentIndex, currentIndex + 1, newIndex, shuffleOrder); - } - - /** - * Moves a range of media sources within the playlist. - * - *

Note: when specified range is empty or the from index equals the new from index, no actual - * media source is moved and no exception is thrown. - * - * @param fromIndex The initial range index, pointing to the first media source of the range that - * will be moved. This index must be in the range of 0 <= index <= {@link #getSize()}. - * @param toIndex The final range index, pointing to the first media source that will be left - * untouched. This index must be larger or equals than {@code fromIndex}. - * @param newFromIndex The target index of the first media source of the range that will be moved. - * @param shuffleOrder The new shuffle order. - * @return The new {@link Timeline}. - * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0, - * {@code toIndex} < {@code fromIndex}, {@code fromIndex} > {@code toIndex}, {@code - * newFromIndex} < 0 - */ - public Timeline moveMediaSourceRange( - int fromIndex, int toIndex, int newFromIndex, ShuffleOrder shuffleOrder) { - Assertions.checkArgument( - fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize() && newFromIndex >= 0); - this.shuffleOrder = shuffleOrder; - if (fromIndex == toIndex || fromIndex == newFromIndex) { - return createTimeline(); - } - int startIndex = Math.min(fromIndex, newFromIndex); - int newEndIndex = newFromIndex + (toIndex - fromIndex) - 1; - int endIndex = Math.max(newEndIndex, toIndex - 1); - int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild; - moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex); - for (int i = startIndex; i <= endIndex; i++) { - MediaSourceHolder holder = mediaSourceHolders.get(i); - holder.firstWindowIndexInChild = windowOffset; - windowOffset += holder.mediaSource.getTimeline().getWindowCount(); - } - return createTimeline(); - } - - /** Clears the playlist. */ - public final Timeline clear(@Nullable ShuffleOrder shuffleOrder) { - this.shuffleOrder = shuffleOrder != null ? shuffleOrder : this.shuffleOrder.cloneAndClear(); - removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ getSize()); - return createTimeline(); - } - - /** Whether the playlist is prepared. */ - public final boolean isPrepared() { - return isPrepared; - } - - /** Returns the number of media sources in the playlist. */ - public final int getSize() { - return mediaSourceHolders.size(); - } - - /** - * Sets the {@link AnalyticsCollector}. - * - * @param handler The handler on which to call the collector. - * @param analyticsCollector The analytics collector. - */ - public final void setAnalyticsCollector(Handler handler, AnalyticsCollector analyticsCollector) { - eventDispatcher.addEventListener(handler, analyticsCollector); - } - - /** - * Sets a new shuffle order to use when shuffling the child media sources. - * - * @param shuffleOrder A {@link ShuffleOrder}. - */ - public final Timeline setShuffleOrder(ShuffleOrder shuffleOrder) { - int size = getSize(); - if (shuffleOrder.getLength() != size) { - shuffleOrder = - shuffleOrder - .cloneAndClear() - .cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size); - } - this.shuffleOrder = shuffleOrder; - return createTimeline(); - } - - /** Prepares the playlist. */ - public final void prepare(@Nullable TransferListener mediaTransferListener) { - Assertions.checkState(!isPrepared); - this.mediaTransferListener = mediaTransferListener; - for (int i = 0; i < mediaSourceHolders.size(); i++) { - MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i); - prepareChildSource(mediaSourceHolder); - enabledMediaSourceHolders.add(mediaSourceHolder); - } - isPrepared = true; - } - - /** - * Returns a new {@link MediaPeriod} identified by {@code periodId}. - * - * @param id The identifier of the period. - * @param allocator An {@link Allocator} from which to obtain media buffer allocations. - * @param startPositionUs The expected start position, in microseconds. - * @return A new {@link MediaPeriod}. - */ - public MediaPeriod createPeriod( - MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs) { - Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid); - MediaSource.MediaPeriodId childMediaPeriodId = - id.copyWithPeriodUid(getChildPeriodUid(id.periodUid)); - MediaSourceHolder holder = Assertions.checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid)); - enableMediaSource(holder); - holder.activeMediaPeriodIds.add(childMediaPeriodId); - MediaPeriod mediaPeriod = - holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); - mediaSourceByMediaPeriod.put(mediaPeriod, holder); - disableUnusedMediaSources(); - return mediaPeriod; - } - - /** - * Releases the period. - * - * @param mediaPeriod The period to release. - */ - public final void releasePeriod(MediaPeriod mediaPeriod) { - MediaSourceHolder holder = - Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); - holder.mediaSource.releasePeriod(mediaPeriod); - holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id); - if (!mediaSourceByMediaPeriod.isEmpty()) { - disableUnusedMediaSources(); - } - maybeReleaseChildSource(holder); - } - - /** Releases the playlist. */ - public final void release() { - for (MediaSourceAndListener childSource : childSources.values()) { - childSource.mediaSource.releaseSource(childSource.caller); - childSource.mediaSource.removeEventListener(childSource.eventListener); - } - childSources.clear(); - enabledMediaSourceHolders.clear(); - isPrepared = false; - } - - /** Throws any pending error encountered while loading or refreshing. */ - public final void maybeThrowSourceInfoRefreshError() throws IOException { - for (MediaSourceAndListener childSource : childSources.values()) { - childSource.mediaSource.maybeThrowSourceInfoRefreshError(); - } - } - - /** Creates a timeline reflecting the current state of the playlist. */ - public final Timeline createTimeline() { - if (mediaSourceHolders.isEmpty()) { - return Timeline.EMPTY; - } - int windowOffset = 0; - for (int i = 0; i < mediaSourceHolders.size(); i++) { - MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i); - mediaSourceHolder.firstWindowIndexInChild = windowOffset; - windowOffset += mediaSourceHolder.mediaSource.getTimeline().getWindowCount(); - } - return new PlaylistTimeline(mediaSourceHolders, shuffleOrder); - } - - // Internal methods. - - private void enableMediaSource(MediaSourceHolder mediaSourceHolder) { - enabledMediaSourceHolders.add(mediaSourceHolder); - @Nullable MediaSourceAndListener enabledChild = childSources.get(mediaSourceHolder); - if (enabledChild != null) { - enabledChild.mediaSource.enable(enabledChild.caller); - } - } - - private void disableUnusedMediaSources() { - Iterator iterator = enabledMediaSourceHolders.iterator(); - while (iterator.hasNext()) { - MediaSourceHolder holder = iterator.next(); - if (holder.activeMediaPeriodIds.isEmpty()) { - disableChildSource(holder); - iterator.remove(); - } - } - } - - private void disableChildSource(MediaSourceHolder holder) { - @Nullable MediaSourceAndListener disabledChild = childSources.get(holder); - if (disabledChild != null) { - disabledChild.mediaSource.disable(disabledChild.caller); - } - } - - private void removeMediaSourcesInternal(int fromIndex, int toIndex) { - for (int index = toIndex - 1; index >= fromIndex; index--) { - MediaSourceHolder holder = mediaSourceHolders.remove(index); - mediaSourceByUid.remove(holder.uid); - Timeline oldTimeline = holder.mediaSource.getTimeline(); - correctOffsets( - /* startIndex= */ index, /* windowOffsetUpdate= */ -oldTimeline.getWindowCount()); - holder.isRemoved = true; - if (isPrepared) { - maybeReleaseChildSource(holder); - } - } - } - - private void correctOffsets(int startIndex, int windowOffsetUpdate) { - for (int i = startIndex; i < mediaSourceHolders.size(); i++) { - MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i); - mediaSourceHolder.firstWindowIndexInChild += windowOffsetUpdate; - } - } - - // Internal methods to manage child sources. - - @Nullable - private static MediaSource.MediaPeriodId getMediaPeriodIdForChildMediaPeriodId( - MediaSourceHolder mediaSourceHolder, MediaSource.MediaPeriodId mediaPeriodId) { - for (int i = 0; i < mediaSourceHolder.activeMediaPeriodIds.size(); i++) { - // Ensure the reported media period id has the same window sequence number as the one created - // by this media source. Otherwise it does not belong to this child source. - if (mediaSourceHolder.activeMediaPeriodIds.get(i).windowSequenceNumber - == mediaPeriodId.windowSequenceNumber) { - Object periodUid = getPeriodUid(mediaSourceHolder, mediaPeriodId.periodUid); - return mediaPeriodId.copyWithPeriodUid(periodUid); - } - } - return null; - } - - private static int getWindowIndexForChildWindowIndex( - MediaSourceHolder mediaSourceHolder, int windowIndex) { - return windowIndex + mediaSourceHolder.firstWindowIndexInChild; - } - - private void prepareChildSource(MediaSourceHolder holder) { - MediaSource mediaSource = holder.mediaSource; - MediaSource.MediaSourceCaller caller = - (source, timeline) -> playlistInfoListener.onPlaylistUpdateRequested(); - MediaSourceEventListener eventListener = new ForwardingEventListener(holder); - childSources.put(holder, new MediaSourceAndListener(mediaSource, caller, eventListener)); - mediaSource.addEventListener(new Handler(), eventListener); - mediaSource.prepareSource(caller, mediaTransferListener); - } - - private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) { - // Release if the source has been removed from the playlist and no periods are still active. - if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) { - MediaSourceAndListener removedChild = - Assertions.checkNotNull(childSources.remove(mediaSourceHolder)); - removedChild.mediaSource.releaseSource(removedChild.caller); - removedChild.mediaSource.removeEventListener(removedChild.eventListener); - enabledMediaSourceHolders.remove(mediaSourceHolder); - } - } - - /** Return uid of media source holder from period uid of concatenated source. */ - private static Object getMediaSourceHolderUid(Object periodUid) { - return PlaylistTimeline.getChildTimelineUidFromConcatenatedUid(periodUid); - } - - /** Return uid of child period from period uid of concatenated source. */ - private static Object getChildPeriodUid(Object periodUid) { - return PlaylistTimeline.getChildPeriodUidFromConcatenatedUid(periodUid); - } - - private static Object getPeriodUid(MediaSourceHolder holder, Object childPeriodUid) { - return PlaylistTimeline.getConcatenatedUid(holder.uid, childPeriodUid); - } - - /* package */ static void moveMediaSourceHolders( - List mediaSourceHolders, int fromIndex, int toIndex, int newFromIndex) { - MediaSourceHolder[] removedItems = new MediaSourceHolder[toIndex - fromIndex]; - for (int i = removedItems.length - 1; i >= 0; i--) { - removedItems[i] = mediaSourceHolders.remove(fromIndex + i); - } - mediaSourceHolders.addAll( - Math.min(newFromIndex, mediaSourceHolders.size()), Arrays.asList(removedItems)); - } - - /** Data class to hold playlist media sources together with meta data needed to process them. */ - /* package */ static final class MediaSourceHolder { - - public final MaskingMediaSource mediaSource; - public final Object uid; - public final List activeMediaPeriodIds; - - public int firstWindowIndexInChild; - public boolean isRemoved; - - public MediaSourceHolder(MediaSource mediaSource, boolean useLazyPreparation) { - this.mediaSource = new MaskingMediaSource(mediaSource, useLazyPreparation); - this.activeMediaPeriodIds = new ArrayList<>(); - this.uid = new Object(); - } - - public void reset(int firstWindowIndexInChild) { - this.firstWindowIndexInChild = firstWindowIndexInChild; - this.isRemoved = false; - this.activeMediaPeriodIds.clear(); - } - } - - /** Timeline exposing concatenated timelines of playlist media sources. */ - /* package */ static final class PlaylistTimeline extends AbstractConcatenatedTimeline { - - private final int windowCount; - private final int periodCount; - private final int[] firstPeriodInChildIndices; - private final int[] firstWindowInChildIndices; - private final Timeline[] timelines; - private final Object[] uids; - private final HashMap childIndexByUid; - - public PlaylistTimeline( - Collection mediaSourceHolders, ShuffleOrder shuffleOrder) { - super(/* isAtomic= */ false, shuffleOrder); - int childCount = mediaSourceHolders.size(); - firstPeriodInChildIndices = new int[childCount]; - firstWindowInChildIndices = new int[childCount]; - timelines = new Timeline[childCount]; - uids = new Object[childCount]; - childIndexByUid = new HashMap<>(); - int index = 0; - int windowCount = 0; - int periodCount = 0; - for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) { - timelines[index] = mediaSourceHolder.mediaSource.getTimeline(); - firstWindowInChildIndices[index] = windowCount; - firstPeriodInChildIndices[index] = periodCount; - windowCount += timelines[index].getWindowCount(); - periodCount += timelines[index].getPeriodCount(); - uids[index] = mediaSourceHolder.uid; - childIndexByUid.put(uids[index], index++); - } - this.windowCount = windowCount; - this.periodCount = periodCount; - } - - @Override - protected int getChildIndexByPeriodIndex(int periodIndex) { - return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false); - } - - @Override - protected int getChildIndexByWindowIndex(int windowIndex) { - return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false); - } - - @Override - protected int getChildIndexByChildUid(Object childUid) { - Integer index = childIndexByUid.get(childUid); - return index == null ? C.INDEX_UNSET : index; - } - - @Override - protected Timeline getTimelineByChildIndex(int childIndex) { - return timelines[childIndex]; - } - - @Override - protected int getFirstPeriodIndexByChildIndex(int childIndex) { - return firstPeriodInChildIndices[childIndex]; - } - - @Override - protected int getFirstWindowIndexByChildIndex(int childIndex) { - return firstWindowInChildIndices[childIndex]; - } - - @Override - protected Object getChildUidByChildIndex(int childIndex) { - return uids[childIndex]; - } - - @Override - public int getWindowCount() { - return windowCount; - } - - @Override - public int getPeriodCount() { - return periodCount; - } - } - - private static final class MediaSourceAndListener { - - public final MediaSource mediaSource; - public final MediaSource.MediaSourceCaller caller; - public final MediaSourceEventListener eventListener; - - public MediaSourceAndListener( - MediaSource mediaSource, - MediaSource.MediaSourceCaller caller, - MediaSourceEventListener eventListener) { - this.mediaSource = mediaSource; - this.caller = caller; - this.eventListener = eventListener; - } - } - - private final class ForwardingEventListener implements MediaSourceEventListener { - - private final Playlist.MediaSourceHolder id; - private EventDispatcher eventDispatcher; - - public ForwardingEventListener(Playlist.MediaSourceHolder id) { - eventDispatcher = Playlist.this.eventDispatcher; - this.id = id; - } - - @Override - public void onMediaPeriodCreated(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.mediaPeriodCreated(); - } - } - - @Override - public void onMediaPeriodReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.mediaPeriodReleased(); - } - } - - @Override - public void onLoadStarted( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - LoadEventInfo loadEventData, - MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.loadStarted(loadEventData, mediaLoadData); - } - } - - @Override - public void onLoadCompleted( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - LoadEventInfo loadEventData, - MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.loadCompleted(loadEventData, mediaLoadData); - } - } - - @Override - public void onLoadCanceled( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - LoadEventInfo loadEventData, - MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.loadCanceled(loadEventData, mediaLoadData); - } - } - - @Override - public void onLoadError( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - LoadEventInfo loadEventData, - MediaLoadData mediaLoadData, - IOException error, - boolean wasCanceled) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.loadError(loadEventData, mediaLoadData, error, wasCanceled); - } - } - - @Override - public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.readingStarted(); - } - } - - @Override - public void onUpstreamDiscarded( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.upstreamDiscarded(mediaLoadData); - } - } - - @Override - public void onDownstreamFormatChanged( - int windowIndex, - @Nullable MediaSource.MediaPeriodId mediaPeriodId, - MediaLoadData mediaLoadData) { - if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) { - eventDispatcher.downstreamFormatChanged(mediaLoadData); - } - } - - /** Updates the event dispatcher and returns whether the event should be dispatched. */ - private boolean maybeUpdateEventDispatcher( - int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) { - @Nullable MediaSource.MediaPeriodId mediaPeriodId = null; - if (childMediaPeriodId != null) { - mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId); - if (mediaPeriodId == null) { - // Media period not found. Ignore event. - return false; - } - } - int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex); - if (eventDispatcher.windowIndex != windowIndex - || !Util.areEqual(eventDispatcher.mediaPeriodId, mediaPeriodId)) { - eventDispatcher = - Playlist.this.eventDispatcher.withParameters( - windowIndex, mediaPeriodId, /* mediaTimeOffsetMs= */ 0L); - } - return true; - } - } -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/AbstractConcatenatedTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java similarity index 98% rename from library/core/src/main/java/com/google/android/exoplayer2/AbstractConcatenatedTimeline.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java index 73bb49ed40..29ef1faa80 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/AbstractConcatenatedTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2; +package com.google.android.exoplayer2.source; import android.util.Pair; -import com.google.android.exoplayer2.source.ShuffleOrder; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; /** Abstract base class for the concatenation of one or more {@link Timeline}s. */ -public abstract class AbstractConcatenatedTimeline extends Timeline { +/* package */ abstract class AbstractConcatenatedTimeline extends Timeline { private final int childCount; private final ShuffleOrder shuffleOrder; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index c1ab78a9bc..545b8f5155 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -19,7 +19,6 @@ import android.os.Handler; import android.os.Message; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.AbstractConcatenatedTimeline; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 8769a84d95..cedc6f911d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.AbstractConcatenatedTimeline; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Player; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java b/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java deleted file mode 100644 index cc551db8ac..0000000000 --- a/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.ShuffleOrder; -import com.google.android.exoplayer2.testutil.FakeMediaSource; -import com.google.android.exoplayer2.testutil.FakeShuffleOrder; -import com.google.android.exoplayer2.testutil.FakeTimeline; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Unit test for {@link Playlist}. */ -@RunWith(AndroidJUnit4.class) -public class PlaylistTest { - - private static final int PLAYLIST_SIZE = 4; - - private Playlist playlist; - - @Before - public void setUp() { - playlist = new Playlist(mock(Playlist.PlaylistInfoRefreshListener.class)); - } - - @Test - public void testEmptyPlaylist_expectConstantTimelineInstanceEMPTY() { - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); - List fakeHolders = createFakeHolders(); - - Timeline timeline = playlist.setMediaSources(fakeHolders, shuffleOrder); - assertNotSame(timeline, Timeline.EMPTY); - - // Remove all media sources. - timeline = - playlist.removeMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ timeline.getWindowCount(), shuffleOrder); - assertSame(timeline, Timeline.EMPTY); - - timeline = playlist.setMediaSources(fakeHolders, shuffleOrder); - assertNotSame(timeline, Timeline.EMPTY); - // Clear. - timeline = playlist.clear(shuffleOrder); - assertSame(timeline, Timeline.EMPTY); - } - - @Test - public void testPrepareAndReprepareAfterRelease_expectSourcePreparationAfterPlaylistPrepare() { - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - playlist.setMediaSources( - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2), - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2)); - // Verify prepare is called once on prepare. - verify(mockMediaSource1, times(0)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(0)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - - playlist.prepare(/* mediaTransferListener= */ null); - assertThat(playlist.isPrepared()).isTrue(); - // Verify prepare is called once on prepare. - verify(mockMediaSource1, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - - playlist.release(); - playlist.prepare(/* mediaTransferListener= */ null); - // Verify prepare is called a second time on re-prepare. - verify(mockMediaSource1, times(2)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(2)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - } - - @Test - public void testSetMediaSources_playlistUnprepared_notUsingLazyPreparation() { - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2); - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - List mediaSources = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2); - Timeline timeline = playlist.setMediaSources(mediaSources, shuffleOrder); - - assertThat(timeline.getWindowCount()).isEqualTo(2); - assertThat(playlist.getSize()).isEqualTo(2); - - // Assert holder offsets have been set properly - for (int i = 0; i < mediaSources.size(); i++) { - Playlist.MediaSourceHolder mediaSourceHolder = mediaSources.get(i); - assertThat(mediaSourceHolder.isRemoved).isFalse(); - assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i); - } - - // Set media items again. The second holder is re-used. - List moreMediaSources = - createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class)); - moreMediaSources.add(mediaSources.get(1)); - timeline = playlist.setMediaSources(moreMediaSources, shuffleOrder); - - assertThat(playlist.getSize()).isEqualTo(2); - assertThat(timeline.getWindowCount()).isEqualTo(2); - for (int i = 0; i < moreMediaSources.size(); i++) { - Playlist.MediaSourceHolder mediaSourceHolder = moreMediaSources.get(i); - assertThat(mediaSourceHolder.isRemoved).isFalse(); - assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i); - } - // Expect removed holders and sources to be removed without releasing. - verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSources.get(0).isRemoved).isTrue(); - // Expect re-used holder and source not to be removed. - verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSources.get(1).isRemoved).isFalse(); - } - - @Test - public void testSetMediaSources_playlistPrepared_notUsingLazyPreparation() { - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2); - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - List mediaSources = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2); - - playlist.prepare(/* mediaTransferListener= */ null); - playlist.setMediaSources(mediaSources, shuffleOrder); - - // Verify sources are prepared. - verify(mockMediaSource1, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - - // Set media items again. The second holder is re-used. - List moreMediaSources = - createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class)); - moreMediaSources.add(mediaSources.get(1)); - playlist.setMediaSources(moreMediaSources, shuffleOrder); - - // Expect removed holders and sources to be removed and released. - verify(mockMediaSource1, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSources.get(0).isRemoved).isTrue(); - // Expect re-used holder and source not to be removed but released. - verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSources.get(1).isRemoved).isFalse(); - verify(mockMediaSource2, times(2)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - } - - @Test - public void testAddMediaSources_playlistUnprepared_notUsingLazyPreparation_expectUnprepared() { - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - List mediaSources = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2); - playlist.addMediaSources(/* index= */ 0, mediaSources, new ShuffleOrder.DefaultShuffleOrder(2)); - - assertThat(playlist.getSize()).isEqualTo(2); - // Verify lazy initialization does not call prepare on sources. - verify(mockMediaSource1, times(0)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(0)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - - for (int i = 0; i < mediaSources.size(); i++) { - assertThat(mediaSources.get(i).firstWindowIndexInChild).isEqualTo(i); - assertThat(mediaSources.get(i).isRemoved).isFalse(); - } - - // Add for more sources in between. - List moreMediaSources = createFakeHolders(); - playlist.addMediaSources( - /* index= */ 1, moreMediaSources, new ShuffleOrder.DefaultShuffleOrder(/* length= */ 3)); - - assertThat(mediaSources.get(0).firstWindowIndexInChild).isEqualTo(0); - assertThat(moreMediaSources.get(0).firstWindowIndexInChild).isEqualTo(1); - assertThat(moreMediaSources.get(3).firstWindowIndexInChild).isEqualTo(4); - assertThat(mediaSources.get(1).firstWindowIndexInChild).isEqualTo(5); - } - - @Test - public void testAddMediaSources_playlistPrepared_notUsingLazyPreparation_expectPrepared() { - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - playlist.prepare(/* mediaTransferListener= */ null); - playlist.addMediaSources( - /* index= */ 0, - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2), - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2)); - - // Verify prepare is called on sources when added. - verify(mockMediaSource1, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - verify(mockMediaSource2, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - } - - @Test - public void testMoveMediaSources() { - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4); - List holders = createFakeHolders(); - playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder); - - assertDefaultFirstWindowInChildIndexOrder(holders); - playlist.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 3, shuffleOrder); - assertFirstWindowInChildIndices(holders, 3, 0, 1, 2); - playlist.moveMediaSource(/* currentIndex= */ 3, /* newIndex= */ 0, shuffleOrder); - assertDefaultFirstWindowInChildIndexOrder(holders); - - playlist.moveMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder); - assertFirstWindowInChildIndices(holders, 2, 3, 0, 1); - playlist.moveMediaSourceRange( - /* fromIndex= */ 2, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder); - assertDefaultFirstWindowInChildIndexOrder(holders); - - playlist.moveMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder); - assertFirstWindowInChildIndices(holders, 2, 3, 0, 1); - playlist.moveMediaSourceRange( - /* fromIndex= */ 2, /* toIndex= */ 3, /* newFromIndex= */ 0, shuffleOrder); - assertFirstWindowInChildIndices(holders, 0, 3, 1, 2); - playlist.moveMediaSourceRange( - /* fromIndex= */ 3, /* toIndex= */ 4, /* newFromIndex= */ 1, shuffleOrder); - assertDefaultFirstWindowInChildIndexOrder(holders); - - // No-ops. - playlist.moveMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder); - assertDefaultFirstWindowInChildIndexOrder(holders); - playlist.moveMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ 0, /* newFromIndex= */ 3, shuffleOrder); - assertDefaultFirstWindowInChildIndexOrder(holders); - } - - @Test - public void testRemoveMediaSources_whenUnprepared_expectNoRelease() { - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - MediaSource mockMediaSource3 = mock(MediaSource.class); - MediaSource mockMediaSource4 = mock(MediaSource.class); - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4); - - List holders = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, - mockMediaSource1, - mockMediaSource2, - mockMediaSource3, - mockMediaSource4); - playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder); - playlist.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder); - - assertThat(playlist.getSize()).isEqualTo(2); - Playlist.MediaSourceHolder removedHolder1 = holders.remove(1); - Playlist.MediaSourceHolder removedHolder2 = holders.remove(1); - - assertDefaultFirstWindowInChildIndexOrder(holders); - assertThat(removedHolder1.isRemoved).isTrue(); - assertThat(removedHolder2.isRemoved).isTrue(); - verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource3, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - } - - @Test - public void testRemoveMediaSources_whenPrepared_expectRelease() { - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - MediaSource mockMediaSource3 = mock(MediaSource.class); - MediaSource mockMediaSource4 = mock(MediaSource.class); - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4); - - List holders = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, - mockMediaSource1, - mockMediaSource2, - mockMediaSource3, - mockMediaSource4); - playlist.prepare(/* mediaTransferListener */ null); - playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder); - playlist.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder); - - assertThat(playlist.getSize()).isEqualTo(2); - holders.remove(2); - holders.remove(1); - - assertDefaultFirstWindowInChildIndexOrder(holders); - verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource3, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - } - - @Test - public void testRelease_playlistUnprepared_expectSourcesNotReleased() { - MediaSource mockMediaSource = mock(MediaSource.class); - Playlist.MediaSourceHolder mediaSourceHolder = - new Playlist.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false); - - playlist.setMediaSources( - Collections.singletonList(mediaSourceHolder), - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1)); - verify(mockMediaSource, times(0)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - playlist.release(); - verify(mockMediaSource, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSourceHolder.isRemoved).isFalse(); - } - - @Test - public void testRelease_playlistPrepared_expectSourcesReleasedNotRemoved() { - MediaSource mockMediaSource = mock(MediaSource.class); - Playlist.MediaSourceHolder mediaSourceHolder = - new Playlist.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false); - - playlist.prepare(/* mediaTransferListener= */ null); - playlist.setMediaSources( - Collections.singletonList(mediaSourceHolder), - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1)); - verify(mockMediaSource, times(1)) - .prepareSource( - any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull()); - playlist.release(); - verify(mockMediaSource, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class)); - assertThat(mediaSourceHolder.isRemoved).isFalse(); - } - - @Test - public void testClearPlaylist_expectSourcesReleasedAndRemoved() { - ShuffleOrder.DefaultShuffleOrder shuffleOrder = - new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4); - MediaSource mockMediaSource1 = mock(MediaSource.class); - MediaSource mockMediaSource2 = mock(MediaSource.class); - List holders = - createFakeHoldersWithSources( - /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2); - playlist.setMediaSources(holders, shuffleOrder); - playlist.prepare(/* mediaTransferListener= */ null); - - Timeline timeline = playlist.clear(shuffleOrder); - assertThat(timeline.isEmpty()).isTrue(); - assertThat(holders.get(0).isRemoved).isTrue(); - assertThat(holders.get(1).isRemoved).isTrue(); - verify(mockMediaSource1, times(1)).releaseSource(any()); - verify(mockMediaSource2, times(1)).releaseSource(any()); - } - - @Test - public void testSetMediaSources_expectTimelineUsesCustomShuffleOrder() { - Timeline timeline = - playlist.setMediaSources(createFakeHolders(), new FakeShuffleOrder(/* length=*/ 4)); - assertTimelineUsesFakeShuffleOrder(timeline); - } - - @Test - public void testAddMediaSources_expectTimelineUsesCustomShuffleOrder() { - Timeline timeline = - playlist.addMediaSources( - /* index= */ 0, createFakeHolders(), new FakeShuffleOrder(PLAYLIST_SIZE)); - assertTimelineUsesFakeShuffleOrder(timeline); - } - - @Test - public void testMoveMediaSources_expectTimelineUsesCustomShuffleOrder() { - ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE); - playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder); - Timeline timeline = - playlist.moveMediaSource( - /* currentIndex= */ 0, /* newIndex= */ 1, new FakeShuffleOrder(PLAYLIST_SIZE)); - assertTimelineUsesFakeShuffleOrder(timeline); - } - - @Test - public void testMoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() { - ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE); - playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder); - Timeline timeline = - playlist.moveMediaSourceRange( - /* fromIndex= */ 0, - /* toIndex= */ 2, - /* newFromIndex= */ 2, - new FakeShuffleOrder(PLAYLIST_SIZE)); - assertTimelineUsesFakeShuffleOrder(timeline); - } - - @Test - public void testRemoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() { - ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE); - playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder); - Timeline timeline = - playlist.removeMediaSourceRange( - /* fromIndex= */ 0, /* toIndex= */ 2, new FakeShuffleOrder(/* length= */ 2)); - assertTimelineUsesFakeShuffleOrder(timeline); - } - - @Test - public void testSetShuffleOrder_expectTimelineUsesCustomShuffleOrder() { - playlist.setMediaSources( - createFakeHolders(), new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE)); - assertTimelineUsesFakeShuffleOrder( - playlist.setShuffleOrder(new FakeShuffleOrder(PLAYLIST_SIZE))); - } - - // Internal methods. - - private static void assertTimelineUsesFakeShuffleOrder(Timeline timeline) { - assertThat( - timeline.getNextWindowIndex( - /* windowIndex= */ 0, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true)) - .isEqualTo(-1); - assertThat( - timeline.getPreviousWindowIndex( - /* windowIndex= */ timeline.getWindowCount() - 1, - Player.REPEAT_MODE_OFF, - /* shuffleModeEnabled= */ true)) - .isEqualTo(-1); - } - - private static void assertDefaultFirstWindowInChildIndexOrder( - List holders) { - int[] indices = new int[holders.size()]; - for (int i = 0; i < indices.length; i++) { - indices[i] = i; - } - assertFirstWindowInChildIndices(holders, indices); - } - - private static void assertFirstWindowInChildIndices( - List holders, int... firstWindowInChildIndices) { - assertThat(holders).hasSize(firstWindowInChildIndices.length); - for (int i = 0; i < holders.size(); i++) { - assertThat(holders.get(i).firstWindowIndexInChild).isEqualTo(firstWindowInChildIndices[i]); - } - } - - private static List createFakeHolders() { - MediaSource fakeMediaSource = new FakeMediaSource(new FakeTimeline(1)); - List holders = new ArrayList<>(); - for (int i = 0; i < PLAYLIST_SIZE; i++) { - holders.add(new Playlist.MediaSourceHolder(fakeMediaSource, /* useLazyPreparation= */ true)); - } - return holders; - } - - private static List createFakeHoldersWithSources( - boolean useLazyPreparation, MediaSource... sources) { - List holders = new ArrayList<>(); - for (MediaSource mediaSource : sources) { - holders.add( - new Playlist.MediaSourceHolder( - mediaSource, /* useLazyPreparation= */ useLazyPreparation)); - } - return holders; - } -}