1717
1818import static androidx .media3 .test .utils .robolectric .RobolectricUtil .runMainLooperUntil ;
1919import static androidx .media3 .test .utils .robolectric .TestPlayerRunHelper .runUntilPlaybackState ;
20+ import static com .google .common .collect .Iterables .getLast ;
2021import static com .google .common .truth .Truth .assertThat ;
2122import static java .lang .Math .max ;
2223import static org .mockito .ArgumentMatchers .any ;
2728import static org .mockito .Mockito .times ;
2829import static org .mockito .Mockito .verify ;
2930
30- import android .os .ConditionVariable ;
3131import android .os .Handler ;
3232import android .os .Looper ;
3333import android .util .Pair ;
3939import androidx .media3 .common .MimeTypes ;
4040import androidx .media3 .common .Player ;
4141import androidx .media3 .common .Timeline ;
42+ import androidx .media3 .common .util .Clock ;
4243import androidx .media3 .common .util .Util ;
4344import androidx .media3 .datasource .TransferListener ;
4445import androidx .media3 .exoplayer .ExoPlayer ;
4546import androidx .media3 .exoplayer .LoadingInfo ;
4647import androidx .media3 .exoplayer .analytics .PlayerId ;
4748import androidx .media3 .exoplayer .util .EventLogger ;
49+ import androidx .media3 .test .utils .FakeClock ;
4850import androidx .media3 .test .utils .FakeMediaSource ;
4951import androidx .media3 .test .utils .FakeTimeline ;
5052import androidx .media3 .test .utils .TestExoPlayerBuilder ;
5153import androidx .media3 .test .utils .TestUtil ;
5254import androidx .media3 .test .utils .robolectric .RobolectricUtil ;
5355import androidx .test .core .app .ApplicationProvider ;
54- import com .google .common .base .Supplier ;
5556import com .google .common .collect .ImmutableList ;
56- import com .google .common .collect .Iterables ;
5757import com .google .errorprone .annotations .CanIgnoreReturnValue ;
5858import java .util .ArrayList ;
5959import java .util .HashSet ;
60- import java .util .concurrent .atomic .AtomicInteger ;
60+ import java .util .concurrent .TimeoutException ;
61+ import java .util .concurrent .atomic .AtomicBoolean ;
6162import java .util .concurrent .atomic .AtomicReference ;
63+ import java .util .function .Function ;
64+ import org .junit .Before ;
6265import org .junit .Test ;
6366import org .junit .runner .RunWith ;
6467import 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