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 520d99892a..93ffc0dfc6 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 @@ -85,6 +85,24 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + // Move sources. + mediaSource.moveMediaSource(2, 3); + waitForTimelineUpdate(); + TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 5, 1, 6, 7, 3); + TimelineAsserts.assertWindowIds(timeline, 222, 444, 555, 111, 666, 777, 333); + mediaSource.moveMediaSource(3, 2); + waitForTimelineUpdate(); + TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); + TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + mediaSource.moveMediaSource(0, 6); + waitForTimelineUpdate(); + TimelineAsserts.assertPeriodCounts(timeline, 4, 1, 5, 6, 7, 3, 2); + TimelineAsserts.assertWindowIds(timeline, 444, 111, 555, 666, 777, 333, 222); + mediaSource.moveMediaSource(6, 0); + waitForTimelineUpdate(); + TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); + TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + // Remove in the middle. mediaSource.removeMediaSource(3); waitForTimelineUpdate(); @@ -138,7 +156,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[1]); mediaSource.addMediaSource(0, childSources[2]); - mediaSource.removeMediaSource(1); + mediaSource.moveMediaSource(0, 2); + mediaSource.removeMediaSource(0); + mediaSource.moveMediaSource(1, 0); mediaSource.addMediaSource(1, childSources[3]); assertNull(timeline); 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 a9e478a67f..8ba6b46a47 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 @@ -44,6 +44,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl private static final int MSG_ADD = 0; private static final int MSG_ADD_MULTIPLE = 1; private static final int MSG_REMOVE = 2; + private static final int MSG_MOVE = 3; // Accessed on the app thread. private final List mediaSourcesPublic; @@ -126,7 +127,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl /** * Removes a {@link MediaSource} from the playlist. * - * @param index The index at which the media source will be removed. + * @param index The index at which the media source will be removed. This index must be in the + * range of 0 <= index < {@link #getSize()}. */ public synchronized void removeMediaSource(int index) { mediaSourcesPublic.remove(index); @@ -135,6 +137,25 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl } } + /** + * Moves an existing {@link MediaSource} 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()}. + */ + public synchronized void moveMediaSource(int currentIndex, int newIndex) { + if (currentIndex == newIndex) { + return; + } + mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex)); + if (player != null) { + player.sendMessages(new ExoPlayerMessage(this, MSG_MOVE, + Pair.create(currentIndex, newIndex))); + } + } + /** * Returns the number of media sources in the playlist. */ @@ -225,6 +246,11 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl removeMediaSourceInternal((Integer) message); break; } + case MSG_MOVE: { + Pair messageData = (Pair) message; + moveMediaSourceInternal(messageData.first, messageData.second); + break; + } default: { throw new IllegalStateException(); } @@ -304,6 +330,21 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl holder.mediaSource.releaseSource(); } + private void moveMediaSourceInternal(int currentIndex, int newIndex) { + int startIndex = Math.min(currentIndex, newIndex); + int endIndex = Math.max(currentIndex, newIndex); + int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild; + int periodOffset = mediaSourceHolders.get(startIndex).firstPeriodIndexInChild; + mediaSourceHolders.add(newIndex, mediaSourceHolders.remove(currentIndex)); + for (int i = startIndex; i <= endIndex; i++) { + MediaSourceHolder holder = mediaSourceHolders.get(i); + holder.firstWindowIndexInChild = windowOffset; + holder.firstPeriodIndexInChild = periodOffset; + windowOffset += holder.timeline.getWindowCount(); + periodOffset += holder.timeline.getPeriodCount(); + } + } + private void correctOffsets(int startIndex, int windowOffsetUpdate, int periodOffsetUpdate) { windowCount += windowOffsetUpdate; periodCount += periodOffsetUpdate;