From f7eba77ee0378a0886ad9cb9d2b9ddeda8ed2285 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 23 Aug 2017 07:30:29 -0700 Subject: [PATCH] Add shuffle support to dynamic concatenating media source. The media source is initialized with a DefaultShuffleOrder which can be changed at any time. Whenever the list of media source is changed, the shuffle order is adapted accordingly (either on the app thread if the player is not prepared yet, or on the player thread). The shuffle order is then used to construct the timeline. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=166198488 --- .../DynamicConcatenatingMediaSourceTest.java | 27 ++++++++++++++-- .../DynamicConcatenatingMediaSource.java | 32 ++++++++++++++++--- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index 9cdb461d7b..0e07e99978 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.Listener; import com.google.android.exoplayer2.testutil.FakeMediaSource; +import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.TimelineAsserts; @@ -49,7 +50,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { public void testPlaylistChangesAfterPreparation() throws InterruptedException { timeline = null; FakeMediaSource[] childSources = createMediaSources(7); - DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(); + DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource( + new FakeShuffleOrder(0)); prepareAndListenToTimelineUpdates(mediaSource); waitForTimelineUpdate(); TimelineAsserts.assertEmpty(timeline); @@ -128,6 +130,18 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { C.INDEX_UNSET, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); + assertEquals(0, timeline.getFirstWindowIndex(false)); + assertEquals(timeline.getWindowCount() - 1, timeline.getLastWindowIndex(false)); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, + C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, + 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0); + assertEquals(timeline.getWindowCount() - 1, timeline.getFirstWindowIndex(true)); + assertEquals(0, timeline.getLastWindowIndex(true)); // Remove at front of queue. mediaSource.removeMediaSource(0); @@ -153,7 +167,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { public void testPlaylistChangesBeforePreparation() throws InterruptedException { timeline = null; FakeMediaSource[] childSources = createMediaSources(4); - DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(); + DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource( + new FakeShuffleOrder(0)); mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[1]); mediaSource.addMediaSource(0, childSources[2]); @@ -168,6 +183,14 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { assertNotNull(timeline); TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2); TimelineAsserts.assertWindowIds(timeline, 333, 444, 222); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, + 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, + C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, + C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, + 1, 2, C.INDEX_UNSET); mediaSource.releaseSource(); for (int i = 1; i < 4; i++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java index 02d4bad2bf..3d0df7dcb3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -23,7 +23,7 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer.ExoPlayerComponent; import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder; +import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; @@ -58,11 +58,26 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl private ExoPlayer player; private Listener listener; + private ShuffleOrder shuffleOrder; private boolean preventListenerNotification; private int windowCount; private int periodCount; + /** + * Creates a new dynamic concatenating media source. + */ public DynamicConcatenatingMediaSource() { + this(new DefaultShuffleOrder(0)); + } + + /** + * Creates a new dynamic concatenating media source with a custom shuffle order. + * + * @param shuffleOrder The {@link ShuffleOrder} to use when shuffling the child media sources. + * This shuffle order must be empty. + */ + public DynamicConcatenatingMediaSource(ShuffleOrder shuffleOrder) { + this.shuffleOrder = shuffleOrder; this.mediaSourceByMediaPeriod = new IdentityHashMap<>(); this.mediaSourcesPublic = new ArrayList<>(); this.mediaSourceHolders = new ArrayList<>(); @@ -180,6 +195,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl this.player = player; this.listener = listener; preventListenerNotification = true; + shuffleOrder = shuffleOrder.cloneAndInsert(0, mediaSourcesPublic.size()); addMediaSourcesInternal(0, mediaSourcesPublic); preventListenerNotification = false; maybeNotifyListener(); @@ -234,21 +250,26 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl switch (messageType) { case MSG_ADD: { Pair messageData = (Pair) message; + shuffleOrder = shuffleOrder.cloneAndInsert(messageData.first, 1); addMediaSourceInternal(messageData.first, messageData.second); break; } case MSG_ADD_MULTIPLE: { Pair> messageData = (Pair>) message; + shuffleOrder = shuffleOrder.cloneAndInsert(messageData.first, messageData.second.size()); addMediaSourcesInternal(messageData.first, messageData.second); break; } case MSG_REMOVE: { + shuffleOrder = shuffleOrder.cloneAndRemove((Integer) message); removeMediaSourceInternal((Integer) message); break; } case MSG_MOVE: { Pair messageData = (Pair) message; + shuffleOrder = shuffleOrder.cloneAndRemove(messageData.first); + shuffleOrder = shuffleOrder.cloneAndInsert(messageData.second, 1); moveMediaSourceInternal(messageData.first, messageData.second); break; } @@ -262,8 +283,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl private void maybeNotifyListener() { if (!preventListenerNotification) { - listener.onSourceInfoRefreshed( - new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount), null); + listener.onSourceInfoRefreshed(new ConcatenatedTimeline(mediaSourceHolders, windowCount, + periodCount, shuffleOrder), null); } } @@ -397,8 +418,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl private final SparseIntArray childIndexByUid; public ConcatenatedTimeline(Collection mediaSourceHolders, int windowCount, - int periodCount) { - super(new UnshuffledShuffleOrder(mediaSourceHolders.size())); + int periodCount, ShuffleOrder shuffleOrder) { + super(shuffleOrder); this.windowCount = windowCount; this.periodCount = periodCount; int childCount = mediaSourceHolders.size(); @@ -638,3 +659,4 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl } } +