From b1d48179e87197c697bbf6e2663c0e570c6839cc Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 11 Sep 2018 06:44:42 -0700 Subject: [PATCH] Add support to change shuffle order after playlist creation. This allows to update the shuffle order after the ConcatenatingMediaSource has been created. ShuffleOrder objects should be immutable to ensure thread safety and thus there is no way to do this currently. Issue:#4791 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=212443146 --- RELEASENOTES.md | 2 + .../source/ConcatenatingMediaSource.java | 51 ++++++++++++++++++- .../exoplayer2/source/ShuffleOrder.java | 2 + .../source/ConcatenatingMediaSourceTest.java | 48 +++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2a8f91ae20..c1bb8ca798 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -98,6 +98,8 @@ ([#3972](https://github.com/google/ExoPlayer/issues/3972)). * Support range removal with `removeMediaSourceRange` methods ([#4542](https://github.com/google/ExoPlayer/issues/4542)). + * Support setting a new shuffle order with `setShuffleOrder` + ([#4791](https://github.com/google/ExoPlayer/issues/4791)). * MPEG-TS: Support CEA-608/708 in H262 ([#2565](https://github.com/google/ExoPlayer/issues/2565)). * Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when 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 3a474facd3..f883fbc96b 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 @@ -53,8 +53,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource mediaSourcesPublic; @@ -433,6 +434,47 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(/* index= */ 0, shuffleOrder, actionOnCompletion)) + .send(); + } else { + this.shuffleOrder = + shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder; + if (actionOnCompletion != null) { + actionOnCompletion.run(); + } + } + } + @Override public final synchronized void prepareSourceInternal( ExoPlayer player, @@ -579,6 +621,11 @@ public class ConcatenatingMediaSource extends CompositeMediaSource shuffleOrderMessage = (MessageData) message; + shuffleOrder = shuffleOrderMessage.customData; + scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion); + break; case MSG_NOTIFY_LISTENER: notifyListener(); break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java index f5f98e4d8a..750c42bbd0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java @@ -21,6 +21,8 @@ import java.util.Random; /** * Shuffled order of indices. + * + *

The shuffle order must be immutable to ensure thread safety. */ public interface ShuffleOrder { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 507b718e8f..d3d3b39ea4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -925,6 +925,54 @@ public final class ConcatenatingMediaSourceTest { testRunner.createPeriod(mediaPeriodId); } + @Test + public void testSetShuffleOrderBeforePreparation() throws Exception { + mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0)); + mediaSource.addMediaSources( + Arrays.asList(createFakeMediaSource(), createFakeMediaSource(), createFakeMediaSource())); + Timeline timeline = testRunner.prepareSource(); + + assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0); + } + + @Test + public void testSetShuffleOrderAfterPreparation() throws Exception { + mediaSource.addMediaSources( + Arrays.asList(createFakeMediaSource(), createFakeMediaSource(), createFakeMediaSource())); + testRunner.prepareSource(); + mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3)); + Timeline timeline = testRunner.assertTimelineChangeBlocking(); + + assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0); + } + + @Test + public void testCustomCallbackBeforePreparationSetShuffleOrder() throws Exception { + Runnable runnable = Mockito.mock(Runnable.class); + mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0), runnable); + + verify(runnable).run(); + } + + @Test + public void testCustomCallbackAfterPreparationSetShuffleOrder() throws Exception { + DummyMainThread dummyMainThread = new DummyMainThread(); + try { + mediaSource.addMediaSources( + Arrays.asList(createFakeMediaSource(), createFakeMediaSource(), createFakeMediaSource())); + testRunner.prepareSource(); + TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); + dummyMainThread.runOnMainThread( + () -> + mediaSource.setShuffleOrder( + new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3), timelineGrabber)); + Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); + assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0); + } finally { + dummyMainThread.release(); + } + } + private void assertCompletedAllMediaPeriodLoads(Timeline timeline) { Timeline.Period period = new Timeline.Period(); Timeline.Window window = new Timeline.Window();