Skip to content

Commit a11b38f

Browse files
toniheiicbaker
authored andcommitted
Ensure ConcatenatingMediaSource2Test uses FakeClock for delays
The delay is currently simulated by a free-running Handler, which means its order is not fully deterministic. This can be fixed by injecting the test clock into this delay setup. As a result, we also get more realistic updates for individual changes coming from multiple sources. PiperOrigin-RevId: 782002184 (cherry picked from commit 578ec5b)
1 parent 7a53b05 commit a11b38f

File tree

1 file changed

+88
-61
lines changed

1 file changed

+88
-61
lines changed

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ConcatenatingMediaSource2Test.java

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
1919
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilPlaybackState;
20+
import static com.google.common.collect.Iterables.getLast;
2021
import static com.google.common.truth.Truth.assertThat;
2122
import static java.lang.Math.max;
2223
import static org.mockito.ArgumentMatchers.any;
@@ -27,7 +28,6 @@
2728
import static org.mockito.Mockito.times;
2829
import static org.mockito.Mockito.verify;
2930

30-
import android.os.ConditionVariable;
3131
import android.os.Handler;
3232
import android.os.Looper;
3333
import android.util.Pair;
@@ -39,26 +39,29 @@
3939
import androidx.media3.common.MimeTypes;
4040
import androidx.media3.common.Player;
4141
import androidx.media3.common.Timeline;
42+
import androidx.media3.common.util.Clock;
4243
import androidx.media3.common.util.Util;
4344
import androidx.media3.datasource.TransferListener;
4445
import androidx.media3.exoplayer.ExoPlayer;
4546
import androidx.media3.exoplayer.LoadingInfo;
4647
import androidx.media3.exoplayer.analytics.PlayerId;
4748
import androidx.media3.exoplayer.util.EventLogger;
49+
import androidx.media3.test.utils.FakeClock;
4850
import androidx.media3.test.utils.FakeMediaSource;
4951
import androidx.media3.test.utils.FakeTimeline;
5052
import androidx.media3.test.utils.TestExoPlayerBuilder;
5153
import androidx.media3.test.utils.TestUtil;
5254
import androidx.media3.test.utils.robolectric.RobolectricUtil;
5355
import androidx.test.core.app.ApplicationProvider;
54-
import com.google.common.base.Supplier;
5556
import com.google.common.collect.ImmutableList;
56-
import com.google.common.collect.Iterables;
5757
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5858
import java.util.ArrayList;
5959
import java.util.HashSet;
60-
import java.util.concurrent.atomic.AtomicInteger;
60+
import java.util.concurrent.TimeoutException;
61+
import java.util.concurrent.atomic.AtomicBoolean;
6162
import java.util.concurrent.atomic.AtomicReference;
63+
import java.util.function.Function;
64+
import org.junit.Before;
6265
import org.junit.Test;
6366
import org.junit.runner.RunWith;
6467
import org.robolectric.ParameterizedRobolectricTestRunner;
@@ -302,6 +305,15 @@ public static ImmutableList<TestConfig> params() {
302305
/* periodIsPlaceholder= */ new boolean[] {true, true},
303306
/* windowDurationMs= */ 840,
304307
/* manifest= */ null),
308+
new ExpectedTimelineData(
309+
/* isSeekable= */ true,
310+
/* isDynamic= */ true,
311+
/* defaultPositionMs= */ 123,
312+
/* periodDurationsMs= */ new long[] {C.TIME_UNSET, C.TIME_UNSET},
313+
/* periodOffsetsInWindowMs= */ new long[] {-50, 420},
314+
/* periodIsPlaceholder= */ new boolean[] {false, true},
315+
/* windowDurationMs= */ 840,
316+
/* manifest= */ null),
305317
new ExpectedTimelineData(
306318
/* isSeekable= */ true,
307319
/* isDynamic= */ true,
@@ -316,24 +328,24 @@ public static ImmutableList<TestConfig> params() {
316328
builder.add(
317329
new TestConfig(
318330
"duplicated_and_nested_sources",
319-
() -> {
331+
clock -> {
320332
MediaSource duplicateSource =
321333
buildMediaSource(
322334
buildWindow(
323335
/* periodCount= */ 2,
324336
/* isSeekable= */ true,
325337
/* isDynamic= */ false,
326338
/* durationMs= */ 1000))
327-
.get();
328-
Supplier<MediaSource> duplicateSourceSupplier = () -> duplicateSource;
339+
.apply(clock);
340+
Function<Clock, MediaSource> duplicateSourceSupplier = unused -> duplicateSource;
329341
return buildConcatenatingMediaSource(
330342
duplicateSourceSupplier,
331343
buildConcatenatingMediaSource(
332344
duplicateSourceSupplier, duplicateSourceSupplier),
333345
buildConcatenatingMediaSource(
334346
duplicateSourceSupplier, duplicateSourceSupplier),
335347
duplicateSourceSupplier)
336-
.get();
348+
.apply(clock);
337349
},
338350
/* expectedAdDiscontinuities= */ 0,
339351
new ExpectedTimelineData(
@@ -456,6 +468,15 @@ public static ImmutableList<TestConfig> params() {
456468
/* periodIsPlaceholder= */ new boolean[] {true, true, true},
457469
/* windowDurationMs= */ 15000,
458470
/* manifest= */ null),
471+
new ExpectedTimelineData(
472+
/* isSeekable= */ true,
473+
/* isDynamic= */ true,
474+
/* defaultPositionMs= */ 123,
475+
/* periodDurationsMs= */ new long[] {4050, C.TIME_UNSET, C.TIME_UNSET},
476+
/* periodOffsetsInWindowMs= */ new long[] {-50, 4000, 9000},
477+
/* periodIsPlaceholder= */ new boolean[] {false, true, true},
478+
/* windowDurationMs= */ 14000,
479+
/* manifest= */ null),
459480
new ExpectedTimelineData(
460481
/* isSeekable= */ false,
461482
/* isDynamic= */ true,
@@ -481,9 +502,16 @@ public static ImmutableList<TestConfig> params() {
481502

482503
private static final String TEST_MEDIA_ITEM_ID = "test_media_item_id";
483504

505+
private Clock testClock;
506+
507+
@Before
508+
public void setUp() {
509+
testClock = new FakeClock(/* isAutoAdvancing= */ true);
510+
}
511+
484512
@Test
485513
public void prepareSource_reportsExpectedTimelines() throws Exception {
486-
MediaSource mediaSource = config.mediaSourceSupplier.get();
514+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
487515
ArrayList<Timeline> timelines = new ArrayList<>();
488516
mediaSource.prepareSource(
489517
(source, timeline) -> timelines.add(timeline),
@@ -536,7 +564,7 @@ public void prepareSource_reportsExpectedTimelines() throws Exception {
536564
@Test
537565
public void prepareSource_afterRelease_reportsSameFinalTimeline() throws Exception {
538566
// Fully prepare source once.
539-
MediaSource mediaSource = config.mediaSourceSupplier.get();
567+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
540568
ArrayList<Timeline> timelines = new ArrayList<>();
541569
MediaSource.MediaSourceCaller caller = (source, timeline) -> timelines.add(timeline);
542570
mediaSource.prepareSource(caller, /* mediaTransferListener= */ null, PlayerId.UNSET);
@@ -549,13 +577,13 @@ public void prepareSource_afterRelease_reportsSameFinalTimeline() throws Excepti
549577
mediaSource.prepareSource(secondCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
550578

551579
// Assert that we receive the same final timeline.
552-
runMainLooperUntil(() -> Iterables.getLast(timelines).equals(secondTimeline.get()));
580+
runMainLooperUntil(() -> getLast(timelines).equals(secondTimeline.get()));
553581
}
554582

555583
@Test
556584
public void preparePeriod_reportsExpectedPeriodLoadEvents() throws Exception {
557585
// Prepare source and register listener.
558-
MediaSource mediaSource = config.mediaSourceSupplier.get();
586+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
559587
MediaSourceEventListener eventListener = mock(MediaSourceEventListener.class);
560588
mediaSource.addEventListener(new Handler(Looper.myLooper()), eventListener);
561589
ArrayList<Timeline> timelines = new ArrayList<>();
@@ -569,7 +597,7 @@ public void preparePeriod_reportsExpectedPeriodLoadEvents() throws Exception {
569597
// should support creating the same period more than once.
570598
ArrayList<MediaPeriod> mediaPeriods = new ArrayList<>();
571599
ArrayList<MediaSource.MediaPeriodId> mediaPeriodIds = new ArrayList<>();
572-
Timeline timeline = Iterables.getLast(timelines);
600+
Timeline timeline = getLast(timelines);
573601
for (int i = 0; i < timeline.getPeriodCount(); i++) {
574602
Timeline.Period period =
575603
timeline.getPeriod(/* periodIndex= */ i, new Timeline.Period(), /* setIds= */ true);
@@ -641,8 +669,10 @@ public void preparePeriod_reportsExpectedPeriodLoadEvents() throws Exception {
641669
public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd()
642670
throws Exception {
643671
ExoPlayer player =
644-
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
645-
player.setMediaSource(config.mediaSourceSupplier.get());
672+
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
673+
.setClock(testClock)
674+
.build();
675+
player.setMediaSource(config.mediaSourceSupplier.apply(testClock));
646676
Player.Listener eventListener = mock(Player.Listener.class);
647677
player.addListener(eventListener);
648678
player.addAnalyticsListener(new EventLogger());
@@ -658,7 +688,7 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
658688
}
659689
player.release();
660690

661-
ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData);
691+
ExpectedTimelineData expectedData = getLast(config.expectedTimelineData);
662692
assertThat(positionAfterPrepareMs).isEqualTo(expectedData.defaultPositionMs);
663693
if (!isDynamic) {
664694
verify(
@@ -673,8 +703,10 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
673703
playback_fromSpecificPeriodPositionInFirstPeriod_startsFromCorrectPositionAndPlaysToEnd()
674704
throws Exception {
675705
ExoPlayer player =
676-
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
677-
MediaSource mediaSource = config.mediaSourceSupplier.get();
706+
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
707+
.setClock(testClock)
708+
.build();
709+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
678710
player.setMediaSource(mediaSource);
679711
Player.Listener eventListener = mock(Player.Listener.class);
680712
player.addListener(eventListener);
@@ -693,7 +725,7 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
693725
}
694726
player.release();
695727

696-
ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData);
728+
ExpectedTimelineData expectedData = getLast(config.expectedTimelineData);
697729
assertThat(windowPositionAfterPrepareMs).isEqualTo(startWindowPositionMs);
698730
if (!isDynamic) {
699731
verify(
@@ -710,8 +742,10 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
710742
Timeline.Period period = new Timeline.Period();
711743
Timeline.Window window = new Timeline.Window();
712744
ExoPlayer player =
713-
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
714-
MediaSource mediaSource = config.mediaSourceSupplier.get();
745+
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
746+
.setClock(testClock)
747+
.build();
748+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
715749
player.setMediaSource(mediaSource);
716750
Player.Listener eventListener = mock(Player.Listener.class);
717751
player.addListener(eventListener);
@@ -739,7 +773,7 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
739773
}
740774
player.release();
741775

742-
ExpectedTimelineData expectedData = Iterables.getLast(config.expectedTimelineData);
776+
ExpectedTimelineData expectedData = getLast(config.expectedTimelineData);
743777
assertThat(periodPositionAfterPrepareMs).isEqualTo(startPeriodPositionMs);
744778
if (timeline.getPeriod(periodIndexAfterPrepare, period).getAdGroupCount() == 0) {
745779
assertThat(periodIndexAfterPrepare).isEqualTo(startPeriodIndex);
@@ -762,7 +796,7 @@ public void playback_fromDefaultPosition_startsFromCorrectPositionAndPlaysToEnd(
762796
public void canUpdateMediaItem_withFieldsChanged_returnsTrue() {
763797
MediaItem updatedMediaItem =
764798
TestUtil.buildFullyCustomizedMediaItem().buildUpon().setUri("http://test.test").build();
765-
MediaSource mediaSource = config.mediaSourceSupplier.get();
799+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
766800

767801
boolean canUpdateMediaItem = mediaSource.canUpdateMediaItem(updatedMediaItem);
768802

@@ -772,7 +806,7 @@ public void canUpdateMediaItem_withFieldsChanged_returnsTrue() {
772806
@Test
773807
public void updateMediaItem_createsTimelineWithUpdatedItem() throws Exception {
774808
MediaItem updatedMediaItem = new MediaItem.Builder().setUri("http://test.test").build();
775-
MediaSource mediaSource = config.mediaSourceSupplier.get();
809+
MediaSource mediaSource = config.mediaSourceSupplier.apply(testClock);
776810
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
777811

778812
mediaSource.updateMediaItem(updatedMediaItem);
@@ -790,13 +824,13 @@ public void updateMediaItem_createsTimelineWithUpdatedItem() throws Exception {
790824
.isEqualTo(updatedMediaItem);
791825
}
792826

793-
private static void blockingPrepareMediaPeriod(MediaPeriod mediaPeriod) {
794-
ConditionVariable mediaPeriodPrepared = new ConditionVariable();
827+
private static void blockingPrepareMediaPeriod(MediaPeriod mediaPeriod) throws TimeoutException {
828+
AtomicBoolean mediaPeriodPrepared = new AtomicBoolean();
795829
mediaPeriod.prepare(
796830
new MediaPeriod.Callback() {
797831
@Override
798832
public void onPrepared(MediaPeriod mediaPeriod) {
799-
mediaPeriodPrepared.open();
833+
mediaPeriodPrepared.set(true);
800834
}
801835

802836
@Override
@@ -805,73 +839,66 @@ public void onContinueLoadingRequested(MediaPeriod source) {
805839
}
806840
},
807841
/* positionUs= */ 0);
808-
mediaPeriodPrepared.block();
842+
runMainLooperUntil(mediaPeriodPrepared::get);
809843
}
810844

811-
private static Supplier<MediaSource> buildConcatenatingMediaSource(
812-
Supplier<MediaSource>... sources) {
845+
private static Function<Clock, MediaSource> buildConcatenatingMediaSource(
846+
Function<Clock, MediaSource>... sources) {
813847
return buildConcatenatingMediaSource(/* placeholderDurationMs= */ C.TIME_UNSET, sources);
814848
}
815849

816-
private static Supplier<MediaSource> buildConcatenatingMediaSource(
817-
long placeholderDurationMs, Supplier<MediaSource>... sources) {
818-
return () -> {
850+
private static Function<Clock, MediaSource> buildConcatenatingMediaSource(
851+
long placeholderDurationMs, Function<Clock, MediaSource>... sources) {
852+
return clock -> {
819853
ConcatenatingMediaSource2.Builder builder = new ConcatenatingMediaSource2.Builder();
820854
builder.setMediaItem(new MediaItem.Builder().setMediaId(TEST_MEDIA_ITEM_ID).build());
821-
for (Supplier<MediaSource> source : sources) {
822-
builder.add(source.get(), placeholderDurationMs);
855+
for (Function<Clock, MediaSource> source : sources) {
856+
builder.add(source.apply(clock), placeholderDurationMs);
823857
}
824858
return builder.build();
825859
};
826860
}
827861

828-
private static Supplier<MediaSource> buildMediaSource(
862+
private static Function<Clock, MediaSource> buildMediaSource(
829863
FakeTimeline.TimelineWindowDefinition... windows) {
830864
return buildMediaSource(/* preparationDelayCount= */ 0, windows);
831865
}
832866

833-
private static Supplier<MediaSource> buildMediaSource(
867+
private static Function<Clock, MediaSource> buildMediaSource(
834868
int preparationDelayCount, FakeTimeline.TimelineWindowDefinition... windows) {
835869
return buildMediaSource(preparationDelayCount, /* manifests= */ null, windows);
836870
}
837871

838-
private static Supplier<MediaSource> buildMediaSource(
872+
private static Function<Clock, MediaSource> buildMediaSource(
839873
Object[] manifests, FakeTimeline.TimelineWindowDefinition... windows) {
840874
return buildMediaSource(/* preparationDelayCount= */ 0, manifests, windows);
841875
}
842876

843-
private static Supplier<MediaSource> buildMediaSource(
877+
private static Function<Clock, MediaSource> buildMediaSource(
844878
int preparationDelayCount,
845879
@Nullable Object[] manifests,
846880
FakeTimeline.TimelineWindowDefinition... windows) {
847881

848-
return () -> {
849-
// Simulate delay by repeatedly sending messages to self. This ensures that all other message
850-
// handling trigger by source preparation finishes before the new timeline update arrives.
851-
AtomicInteger delayCount = new AtomicInteger(10 * preparationDelayCount);
882+
return clock -> {
883+
// Add some delay according to the preparationDelayCount value. This ensures that all other
884+
// message handling triggered by source preparation finishes before the new timeline update
885+
// arrives.
852886
return new FakeMediaSource(
853887
/* timeline= */ null,
854888
new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build()) {
855889
@Override
856890
public synchronized void prepareSourceInternal(
857891
@Nullable TransferListener mediaTransferListener) {
858892
super.prepareSourceInternal(mediaTransferListener);
859-
Handler delayHandler = new Handler(Looper.myLooper());
860-
Runnable handleDelay =
861-
new Runnable() {
862-
@Override
863-
public void run() {
864-
if (delayCount.getAndDecrement() == 0) {
865-
setNewSourceInfo(
866-
manifests != null
867-
? new FakeTimeline(manifests, windows)
868-
: new FakeTimeline(windows));
869-
} else {
870-
delayHandler.post(this);
871-
}
872-
}
873-
};
874-
delayHandler.post(handleDelay);
893+
clock
894+
.createHandler(Looper.myLooper(), /* callback= */ null)
895+
.postDelayed(
896+
() ->
897+
setNewSourceInfo(
898+
manifests != null
899+
? new FakeTimeline(manifests, windows)
900+
: new FakeTimeline(windows)),
901+
10 * preparationDelayCount);
875902
}
876903
};
877904
};
@@ -945,15 +972,15 @@ private static FakeTimeline.TimelineWindowDefinition buildWindow(
945972

946973
private static final class TestConfig {
947974

948-
public final Supplier<MediaSource> mediaSourceSupplier;
975+
public final Function<Clock, MediaSource> mediaSourceSupplier;
949976
public final ImmutableList<ExpectedTimelineData> expectedTimelineData;
950977

951978
private final int expectedAdDiscontinuities;
952979
private final String tag;
953980

954981
public TestConfig(
955982
String tag,
956-
Supplier<MediaSource> mediaSourceSupplier,
983+
Function<Clock, MediaSource> mediaSourceSupplier,
957984
int expectedAdDiscontinuities,
958985
ExpectedTimelineData... expectedTimelineData) {
959986
this.tag = tag;

0 commit comments

Comments
 (0)