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
This commit is contained in:
tonihei 2018-09-11 06:44:42 -07:00 committed by Oliver Woodman
parent b31438b670
commit b1d48179e8
4 changed files with 101 additions and 2 deletions

View file

@ -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

View file

@ -53,8 +53,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private static final int MSG_REMOVE_RANGE = 3;
private static final int MSG_MOVE = 4;
private static final int MSG_CLEAR = 5;
private static final int MSG_NOTIFY_LISTENER = 6;
private static final int MSG_ON_COMPLETION = 7;
private static final int MSG_SET_SHUFFLE_ORDER = 6;
private static final int MSG_NOTIFY_LISTENER = 7;
private static final int MSG_ON_COMPLETION = 8;
// Accessed on the app thread.
private final List<MediaSourceHolder> mediaSourcesPublic;
@ -433,6 +434,47 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return mediaSourcesPublic.get(index).mediaSource;
}
/**
* Sets a new shuffle order to use when shuffling the child media sources.
*
* @param shuffleOrder A {@link ShuffleOrder}.
*/
public final synchronized void setShuffleOrder(ShuffleOrder shuffleOrder) {
setShuffleOrder(shuffleOrder, /* actionOnCompletion= */ null);
}
/**
* Sets a new shuffle order to use when shuffling the child media sources.
*
* @param shuffleOrder A {@link ShuffleOrder}.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the shuffle
* order has been changed.
*/
public final synchronized void setShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Runnable actionOnCompletion) {
ExoPlayer player = this.player;
if (player != null) {
int size = getSize();
if (shuffleOrder.getLength() != size) {
shuffleOrder =
shuffleOrder
.cloneAndClear()
.cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
}
player
.createMessage(this)
.setType(MSG_SET_SHUFFLE_ORDER)
.setPayload(new MessageData<>(/* 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<MediaSourceHo
clearInternal();
scheduleListenerNotification((Runnable) message);
break;
case MSG_SET_SHUFFLE_ORDER:
MessageData<ShuffleOrder> shuffleOrderMessage = (MessageData<ShuffleOrder>) message;
shuffleOrder = shuffleOrderMessage.customData;
scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion);
break;
case MSG_NOTIFY_LISTENER:
notifyListener();
break;

View file

@ -21,6 +21,8 @@ import java.util.Random;
/**
* Shuffled order of indices.
*
* <p>The shuffle order must be immutable to ensure thread safety.
*/
public interface ShuffleOrder {

View file

@ -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();