Do not advance SystemClock manually in tests
Changes MetadataRetriever and Transformer so that their respective tests don't need to manually control the SystemClock in order to execute taks posted with delay from Loader. PiperOrigin-RevId: 345024140
This commit is contained in:
parent
1a5d79b78a
commit
1ff0965a10
@ -22,6 +22,7 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||||
@ -32,7 +33,8 @@ import com.google.android.exoplayer2.source.MediaSourceFactory;
|
|||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Clock;
|
||||||
|
import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -56,13 +58,7 @@ public final class MetadataRetriever {
|
|||||||
*/
|
*/
|
||||||
public static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
public static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
||||||
Context context, MediaItem mediaItem) {
|
Context context, MediaItem mediaItem) {
|
||||||
ExtractorsFactory extractorsFactory =
|
return retrieveMetadata(context, mediaItem, Clock.DEFAULT);
|
||||||
new DefaultExtractorsFactory()
|
|
||||||
.setMp4ExtractorFlags(
|
|
||||||
Mp4Extractor.FLAG_READ_MOTION_PHOTO_METADATA | Mp4Extractor.FLAG_READ_SEF_DATA);
|
|
||||||
MediaSourceFactory mediaSourceFactory =
|
|
||||||
new DefaultMediaSourceFactory(context, extractorsFactory);
|
|
||||||
return retrieveMetadata(mediaSourceFactory, mediaItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,9 +73,26 @@ public final class MetadataRetriever {
|
|||||||
*/
|
*/
|
||||||
public static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
public static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
||||||
MediaSourceFactory mediaSourceFactory, MediaItem mediaItem) {
|
MediaSourceFactory mediaSourceFactory, MediaItem mediaItem) {
|
||||||
|
return retrieveMetadata(mediaSourceFactory, mediaItem, Clock.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
||||||
|
Context context, MediaItem mediaItem, Clock clock) {
|
||||||
|
ExtractorsFactory extractorsFactory =
|
||||||
|
new DefaultExtractorsFactory()
|
||||||
|
.setMp4ExtractorFlags(
|
||||||
|
Mp4Extractor.FLAG_READ_MOTION_PHOTO_METADATA | Mp4Extractor.FLAG_READ_SEF_DATA);
|
||||||
|
MediaSourceFactory mediaSourceFactory =
|
||||||
|
new DefaultMediaSourceFactory(context, extractorsFactory);
|
||||||
|
return retrieveMetadata(mediaSourceFactory, mediaItem, clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListenableFuture<TrackGroupArray> retrieveMetadata(
|
||||||
|
MediaSourceFactory mediaSourceFactory, MediaItem mediaItem, Clock clock) {
|
||||||
// Recreate thread and handler every time this method is called so that it can be used
|
// Recreate thread and handler every time this method is called so that it can be used
|
||||||
// concurrently.
|
// concurrently.
|
||||||
return new MetadataRetrieverInternal(mediaSourceFactory).retrieveMetadata(mediaItem);
|
return new MetadataRetrieverInternal(mediaSourceFactory, clock).retrieveMetadata(mediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MetadataRetrieverInternal {
|
private static final class MetadataRetrieverInternal {
|
||||||
@ -91,15 +104,15 @@ public final class MetadataRetriever {
|
|||||||
|
|
||||||
private final MediaSourceFactory mediaSourceFactory;
|
private final MediaSourceFactory mediaSourceFactory;
|
||||||
private final HandlerThread mediaSourceThread;
|
private final HandlerThread mediaSourceThread;
|
||||||
private final Handler mediaSourceHandler;
|
private final HandlerWrapper mediaSourceHandler;
|
||||||
private final SettableFuture<TrackGroupArray> trackGroupsFuture;
|
private final SettableFuture<TrackGroupArray> trackGroupsFuture;
|
||||||
|
|
||||||
public MetadataRetrieverInternal(MediaSourceFactory mediaSourceFactory) {
|
public MetadataRetrieverInternal(MediaSourceFactory mediaSourceFactory, Clock clock) {
|
||||||
this.mediaSourceFactory = mediaSourceFactory;
|
this.mediaSourceFactory = mediaSourceFactory;
|
||||||
mediaSourceThread = new HandlerThread("ExoPlayer:MetadataRetriever");
|
mediaSourceThread = new HandlerThread("ExoPlayer:MetadataRetriever");
|
||||||
mediaSourceThread.start();
|
mediaSourceThread.start();
|
||||||
mediaSourceHandler =
|
mediaSourceHandler =
|
||||||
Util.createHandler(mediaSourceThread.getLooper(), new MediaSourceHandlerCallback());
|
clock.createHandler(mediaSourceThread.getLooper(), new MediaSourceHandlerCallback());
|
||||||
trackGroupsFuture = SettableFuture.create();
|
trackGroupsFuture = SettableFuture.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,9 @@ public interface HandlerWrapper {
|
|||||||
/** @see Handler#sendEmptyMessage(int) */
|
/** @see Handler#sendEmptyMessage(int) */
|
||||||
boolean sendEmptyMessage(int what);
|
boolean sendEmptyMessage(int what);
|
||||||
|
|
||||||
|
/** @see Handler#sendEmptyMessageDelayed(int, long) */
|
||||||
|
boolean sendEmptyMessageDelayed(int what, int delayMs);
|
||||||
|
|
||||||
/** @see Handler#sendEmptyMessageAtTime(int, long) */
|
/** @see Handler#sendEmptyMessageAtTime(int, long) */
|
||||||
boolean sendEmptyMessageAtTime(int what, long uptimeMs);
|
boolean sendEmptyMessageAtTime(int what, long uptimeMs);
|
||||||
|
|
||||||
|
@ -58,6 +58,11 @@ import androidx.annotation.Nullable;
|
|||||||
return handler.sendEmptyMessage(what);
|
return handler.sendEmptyMessage(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sendEmptyMessageDelayed(int what, int delayMs) {
|
||||||
|
return handler.sendEmptyMessageDelayed(what, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendEmptyMessageAtTime(int what, long uptimeMs) {
|
public boolean sendEmptyMessageAtTime(int what, long uptimeMs) {
|
||||||
return handler.sendEmptyMessageAtTime(what, uptimeMs);
|
return handler.sendEmptyMessageAtTime(what, uptimeMs);
|
||||||
|
@ -22,17 +22,18 @@ import static org.junit.Assert.assertThrows;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
|
import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
|
||||||
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
|
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -41,11 +42,15 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class MetadataRetrieverTest {
|
public class MetadataRetrieverTest {
|
||||||
|
|
||||||
|
private static final long TEST_TIMEOUT_SEC = 10;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
private AutoAdvancingFakeClock clock;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
context = ApplicationProvider.getApplicationContext();
|
context = ApplicationProvider.getApplicationContext();
|
||||||
|
clock = new AutoAdvancingFakeClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -53,8 +58,9 @@ public class MetadataRetrieverTest {
|
|||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"));
|
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"));
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture = retrieveMetadata(context, mediaItem);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture =
|
||||||
TrackGroupArray trackGroups = waitAndGetTrackGroups(trackGroupsFuture);
|
retrieveMetadata(context, mediaItem, clock);
|
||||||
|
TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertThat(trackGroups.length).isEqualTo(2);
|
assertThat(trackGroups.length).isEqualTo(2);
|
||||||
// Video group.
|
// Video group.
|
||||||
@ -72,10 +78,12 @@ public class MetadataRetrieverTest {
|
|||||||
MediaItem mediaItem2 =
|
MediaItem mediaItem2 =
|
||||||
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp3/bear-id3.mp3"));
|
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp3/bear-id3.mp3"));
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture1 = retrieveMetadata(context, mediaItem1);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture1 =
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture2 = retrieveMetadata(context, mediaItem2);
|
retrieveMetadata(context, mediaItem1, clock);
|
||||||
TrackGroupArray trackGroups1 = waitAndGetTrackGroups(trackGroupsFuture1);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture2 =
|
||||||
TrackGroupArray trackGroups2 = waitAndGetTrackGroups(trackGroupsFuture2);
|
retrieveMetadata(context, mediaItem2, clock);
|
||||||
|
TrackGroupArray trackGroups1 = trackGroupsFuture1.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
TrackGroupArray trackGroups2 = trackGroupsFuture2.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
|
||||||
// First track group.
|
// First track group.
|
||||||
assertThat(trackGroups1.length).isEqualTo(2);
|
assertThat(trackGroups1.length).isEqualTo(2);
|
||||||
@ -104,8 +112,9 @@ public class MetadataRetrieverTest {
|
|||||||
/* videoStartPosition= */ 28_869,
|
/* videoStartPosition= */ 28_869,
|
||||||
/* videoSize= */ 28_803);
|
/* videoSize= */ 28_803);
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture = retrieveMetadata(context, mediaItem);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture =
|
||||||
TrackGroupArray trackGroups = waitAndGetTrackGroups(trackGroupsFuture);
|
retrieveMetadata(context, mediaItem, clock);
|
||||||
|
TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertThat(trackGroups.length).isEqualTo(1);
|
assertThat(trackGroups.length).isEqualTo(1);
|
||||||
assertThat(trackGroups.get(0).length).isEqualTo(1);
|
assertThat(trackGroups.get(0).length).isEqualTo(1);
|
||||||
@ -119,8 +128,9 @@ public class MetadataRetrieverTest {
|
|||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp4/sample_still_photo.heic"));
|
MediaItem.fromUri(Uri.parse("asset://android_asset/media/mp4/sample_still_photo.heic"));
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture = retrieveMetadata(context, mediaItem);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture =
|
||||||
TrackGroupArray trackGroups = waitAndGetTrackGroups(trackGroupsFuture);
|
retrieveMetadata(context, mediaItem, clock);
|
||||||
|
TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertThat(trackGroups.length).isEqualTo(1);
|
assertThat(trackGroups.length).isEqualTo(1);
|
||||||
assertThat(trackGroups.get(0).length).isEqualTo(1);
|
assertThat(trackGroups.get(0).length).isEqualTo(1);
|
||||||
@ -140,15 +150,14 @@ public class MetadataRetrieverTest {
|
|||||||
/* startTimeMs= */ 1255, /* endTimeMs= */ 1970, /* speedDivisor= */ 8));
|
/* startTimeMs= */ 1255, /* endTimeMs= */ 1970, /* speedDivisor= */ 8));
|
||||||
SlowMotionData expectedSlowMotionData = new SlowMotionData(segments);
|
SlowMotionData expectedSlowMotionData = new SlowMotionData(segments);
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture = retrieveMetadata(context, mediaItem);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture =
|
||||||
TrackGroupArray trackGroups = waitAndGetTrackGroups(trackGroupsFuture);
|
retrieveMetadata(context, mediaItem, clock);
|
||||||
|
TrackGroupArray trackGroups = trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertThat(trackGroups.length).isEqualTo(2); // Video and audio
|
assertThat(trackGroups.length).isEqualTo(2); // Video and audio
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
assertThat(trackGroups.get(0).getFormat(0).metadata.length()).isEqualTo(1);
|
assertThat(trackGroups.get(0).getFormat(0).metadata.length()).isEqualTo(1);
|
||||||
assertThat(trackGroups.get(0).getFormat(0).metadata.get(0)).isEqualTo(expectedSlowMotionData);
|
assertThat(trackGroups.get(0).getFormat(0).metadata.get(0)).isEqualTo(expectedSlowMotionData);
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
assertThat(trackGroups.get(1).getFormat(0).metadata.length())
|
assertThat(trackGroups.get(1).getFormat(0).metadata.length())
|
||||||
.isEqualTo(3); // 2 Mdta entries and 1 slow motion entry.
|
.isEqualTo(3); // 2 Mdta entries and 1 slow motion entry.
|
||||||
@ -160,21 +169,10 @@ public class MetadataRetrieverTest {
|
|||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
MediaItem.fromUri(Uri.parse("asset://android_asset/media/does_not_exist"));
|
MediaItem.fromUri(Uri.parse("asset://android_asset/media/does_not_exist"));
|
||||||
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture = retrieveMetadata(context, mediaItem);
|
ListenableFuture<TrackGroupArray> trackGroupsFuture =
|
||||||
|
retrieveMetadata(context, mediaItem, clock);
|
||||||
|
|
||||||
assertThrows(ExecutionException.class, () -> waitAndGetTrackGroups(trackGroupsFuture));
|
assertThrows(
|
||||||
}
|
ExecutionException.class, () -> trackGroupsFuture.get(TEST_TIMEOUT_SEC, TimeUnit.SECONDS));
|
||||||
|
|
||||||
private static TrackGroupArray waitAndGetTrackGroups(
|
|
||||||
ListenableFuture<TrackGroupArray> trackGroupsFuture)
|
|
||||||
throws InterruptedException, ExecutionException {
|
|
||||||
while (!trackGroupsFuture.isDone()) {
|
|
||||||
// TODO: update once [Internal: b/168084145] is implemented.
|
|
||||||
// Advance SystemClock so that messages that are sent with a delay to the MetadataRetriever
|
|
||||||
// looper are received.
|
|
||||||
SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + 100);
|
|
||||||
Thread.sleep(/* millis= */ 100);
|
|
||||||
}
|
|
||||||
return trackGroupsFuture.get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,20 +15,22 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.testutil;
|
package com.google.android.exoplayer2.testutil;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.util.HandlerWrapper;
|
import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link FakeClock} extension which automatically advances time whenever an empty message is
|
* {@link FakeClock} extension which automatically advances time whenever an empty message is
|
||||||
* enqueued at a future time.
|
* enqueued at a future time.
|
||||||
*
|
*
|
||||||
* <p>The clock time is advanced to the time of enqueued empty messages. Only the first Handler
|
* <p>The clock time is advanced to the time of enqueued empty messages. The first Handler sending
|
||||||
* sending messages at a future time will be allowed to advance time to ensure there is only one
|
* messages at a future time will be allowed to advance time to ensure there is only one primary
|
||||||
* primary time source. This should usually be the Handler of the internal playback loop.
|
* time source at a time. This should usually be the Handler of the internal playback loop. You can
|
||||||
|
* {@link #resetHandler() reset the handler} so that the next Handler that sends messages at a
|
||||||
|
* future time becomes the primary time source.
|
||||||
*/
|
*/
|
||||||
public final class AutoAdvancingFakeClock extends FakeClock {
|
public final class AutoAdvancingFakeClock extends FakeClock {
|
||||||
|
|
||||||
private @MonotonicNonNull HandlerWrapper autoAdvancingHandler;
|
@Nullable private HandlerWrapper autoAdvancingHandler;
|
||||||
|
|
||||||
/** Creates the auto-advancing clock with an initial time of 0. */
|
/** Creates the auto-advancing clock with an initial time of 0. */
|
||||||
public AutoAdvancingFakeClock() {
|
public AutoAdvancingFakeClock() {
|
||||||
@ -57,4 +59,9 @@ public final class AutoAdvancingFakeClock extends FakeClock {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resets the internal handler, so that this clock can later be used with another handler. */
|
||||||
|
public void resetHandler() {
|
||||||
|
autoAdvancingHandler = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,6 +223,11 @@ public class FakeClock implements Clock {
|
|||||||
return handler.sendEmptyMessage(what);
|
return handler.sendEmptyMessage(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sendEmptyMessageDelayed(int what, int delayMs) {
|
||||||
|
return addHandlerMessageAtTime(this, what, uptimeMillis() + delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendEmptyMessageAtTime(int what, long uptimeMs) {
|
public boolean sendEmptyMessageAtTime(int what, long uptimeMs) {
|
||||||
return addHandlerMessageAtTime(this, what, uptimeMs);
|
return addHandlerMessageAtTime(this, what, uptimeMs);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user