Commit b6113763 by tonihei Committed by Oliver Woodman

Allow lazy preparation of child source in a concatenation.

This adds an optional parameter to ConcatenatingMediaSource to prepare
child sources only lazily when are needed. This is helpful for long playlists
of media sources with manifests to prevent a lot of simultaneous manifest
loads.

Issue:#3972

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=198855676
parent 7f866b2a
......@@ -10,6 +10,9 @@
changes ([#4276](https://github.com/google/ExoPlayer/issues/4276)).
* IMA: Don't advertise support for video/mpeg ad media, as we don't have an
extractor for this ([#4297](https://github.com/google/ExoPlayer/issues/4297)).
* Add support for lazy preparation of playlist media sources in
`ConcatenatingMediaSource`
([#3972](https://github.com/google/ExoPlayer/issues/3972)).
* HLS:
* Allow injection of custom playlist trackers.
......
......@@ -35,8 +35,8 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
private final HashMap<T, MediaSourceAndListener> childSources;
private ExoPlayer player;
private Handler eventHandler;
private @Nullable ExoPlayer player;
private @Nullable Handler eventHandler;
/** Create composite media source without child sources. */
protected CompositeMediaSource() {
......@@ -78,7 +78,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
* @param manifest The manifest of the child source.
*/
protected abstract void onChildSourceInfoRefreshed(
@Nullable T id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest);
T id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest);
/**
* Prepares a child source.
......@@ -93,7 +93,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
* @param id A unique id to identify the child source preparation. Null is allowed as an id.
* @param mediaSource The child {@link MediaSource}.
*/
protected final void prepareChildSource(@Nullable final T id, MediaSource mediaSource) {
protected final void prepareChildSource(final T id, MediaSource mediaSource) {
Assertions.checkArgument(!childSources.containsKey(id));
SourceInfoRefreshListener sourceListener =
new SourceInfoRefreshListener() {
......@@ -105,8 +105,9 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
};
MediaSourceEventListener eventListener = new ForwardingEventListener(id);
childSources.put(id, new MediaSourceAndListener(mediaSource, sourceListener, eventListener));
mediaSource.addEventListener(eventHandler, eventListener);
mediaSource.prepareSource(player, /* isTopLevelSource= */ false, sourceListener);
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
mediaSource.prepareSource(
Assertions.checkNotNull(player), /* isTopLevelSource= */ false, sourceListener);
}
/**
......@@ -114,8 +115,8 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
*
* @param id The unique id used to prepare the child source.
*/
protected final void releaseChildSource(@Nullable T id) {
MediaSourceAndListener removedChild = childSources.remove(id);
protected final void releaseChildSource(T id) {
MediaSourceAndListener removedChild = Assertions.checkNotNull(childSources.remove(id));
removedChild.mediaSource.releaseSource(removedChild.listener);
removedChild.mediaSource.removeEventListener(removedChild.eventListener);
}
......@@ -128,7 +129,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
* @param windowIndex A window index of the child source.
* @return The corresponding window index in the composite source.
*/
protected int getWindowIndexForChildWindowIndex(@Nullable T id, int windowIndex) {
protected int getWindowIndexForChildWindowIndex(T id, int windowIndex) {
return windowIndex;
}
......@@ -143,7 +144,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
* corresponding media period id can be determined.
*/
protected @Nullable MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
@Nullable T id, MediaPeriodId mediaPeriodId) {
T id, MediaPeriodId mediaPeriodId) {
return mediaPeriodId;
}
......@@ -177,10 +178,10 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
private final class ForwardingEventListener implements MediaSourceEventListener {
private final @Nullable T id;
private final T id;
private EventDispatcher eventDispatcher;
public ForwardingEventListener(@Nullable T id) {
public ForwardingEventListener(T id) {
this.eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
this.id = id;
}
......
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
......@@ -913,6 +914,58 @@ public final class ConcatenatingMediaSourceTest {
new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0));
}
@Test
public void testChildSourceIsNotPreparedWithLazyPreparation() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
/* useLazyPreparation= */ true,
new DefaultShuffleOrder(0),
childSources);
testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null);
testRunner.prepareSource();
assertThat(childSources[0].isPrepared()).isFalse();
assertThat(childSources[1].isPrepared()).isFalse();
}
@Test
public void testChildSourceIsPreparedWithLazyPreparationAfterPeriodCreation() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
/* useLazyPreparation= */ true,
new DefaultShuffleOrder(0),
childSources);
testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null);
testRunner.prepareSource();
testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0));
assertThat(childSources[0].isPrepared()).isTrue();
assertThat(childSources[1].isPrepared()).isFalse();
}
@Test
public void testChildSourceWithLazyPreparationOnlyPreparesSourceOnce() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
/* useLazyPreparation= */ true,
new DefaultShuffleOrder(0),
childSources);
testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null);
testRunner.prepareSource();
// The lazy preparation must only be triggered once, even if we create multiple periods from
// the media source. FakeMediaSource.prepareSource asserts that it's not called twice, so
// creating two periods shouldn't throw.
testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0));
testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0));
}
private void assertCompletedAllMediaPeriodLoads(Timeline timeline) {
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
......
......@@ -159,9 +159,9 @@ public class FakeMediaSource extends BaseMediaSource {
}
}
/** Asserts that the source has been prepared. */
public void assertPrepared() {
assertThat(preparedSource).isTrue();
/** Returns whether the source is currently prepared. */
public boolean isPrepared() {
return preparedSource;
}
/**
......
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