Commit b1d48179 by tonihei Committed by Oliver Woodman

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
parent b31438b6
......@@ -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
......
......@@ -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;
......
......@@ -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 {
......
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment