Commit 4671c23c by tonihei Committed by Oliver Woodman

Migrate ExoPlayerTest to Robolectric.

So far this wasn't possible because Robolectric's Looper and MessageQueue
implementations have multiple shortcomings:
 1. The message loop of new HandlerThreads is an not an actual loop and
    scheduled messages are executed on the thread the message is enqueued
    (not the handler thread).
 2. The scheduler used to replace the message queue is synchronizing all its
    methods. Thus, when a test attempts to add messages to a Handler from
    two different threads, it may easily run into a deadlock.
 3. The scheduler doesn't correctly emulate the order of messages as they
    would be in an actual MessageQueue:
   a. If the message is enqueued on the handler thread, it gets executed
      immediately (and not after all other messages at the same time).
   b. The list of messages is always re-sorted by time, meaning that the
      order of execution for messages at the same time is indeterminate.
 4. Robolectric's SystemClock implementation returns the current scheduler
    time of the main UI thread. So, unless this scheduler is used to add
    messages in the future, the SystemClock time never advances.

This CL adds two helper classes which extend and replace Robolectric's
ShadowLooper and ShadowMessageQueue.
 1. We intercept messages being enqueued or deleted in the message queue.
    Thus Robolectric's faulty scheduler gets never used. Instead, we keep
    a blocking priority queue of messages, sorted first by execution time
    and then by FIFO order to correctly emulate the real MessageQueue.
 2. We also keep a list of deleted messages to know which messages to ignore
    when they come up in the looper.
 3. When a new Looper is started, we override the dummy loop to an actual
    eternal while loop which waits for new messages, checks if they haven't
    been deleted, and runs the messages (similar to what Robolectric's
    MessageQueue would have done at this point).

Because we don't actually use the main UI thread in our tests, we can't rely
on the SystemClock to progress in any sensible manner. To overcome this issue,
we can use the auto-advancing FakeClock also used for the simulation tests.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=182912510
parent e991a801
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.view.Surface; import android.view.Surface;
import com.google.android.exoplayer2.Player.DefaultEventListener; import com.google.android.exoplayer2.Player.DefaultEventListener;
...@@ -40,18 +41,24 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit ...@@ -40,18 +41,24 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit
import com.google.android.exoplayer2.testutil.FakeTrackSelection; import com.google.android.exoplayer2.testutil.FakeTrackSelection;
import com.google.android.exoplayer2.testutil.FakeTrackSelector; import com.google.android.exoplayer2.testutil.FakeTrackSelector;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.video.DummySurface;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import junit.framework.TestCase; import org.junit.Test;
import org.junit.runner.RunWith;
/** import org.robolectric.RobolectricTestRunner;
* Unit test for {@link ExoPlayer}. import org.robolectric.annotation.Config;
*/
public final class ExoPlayerTest extends TestCase { /** Unit test for {@link ExoPlayer}. */
@RunWith(RobolectricTestRunner.class)
@Config(
sdk = Config.TARGET_SDK,
manifest = Config.NONE,
shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}
)
public final class ExoPlayerTest {
/** /**
* For tests that rely on the player transitioning to the ended state, the duration in * For tests that rely on the player transitioning to the ended state, the duration in
...@@ -64,6 +71,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -64,6 +71,7 @@ public final class ExoPlayerTest extends TestCase {
* Tests playback of a source that exposes an empty timeline. Playback is expected to end without * Tests playback of a source that exposes an empty timeline. Playback is expected to end without
* error. * error.
*/ */
@Test
public void testPlayEmptyTimeline() throws Exception { public void testPlayEmptyTimeline() throws Exception {
Timeline timeline = Timeline.EMPTY; Timeline timeline = Timeline.EMPTY;
FakeRenderer renderer = new FakeRenderer(); FakeRenderer renderer = new FakeRenderer();
...@@ -81,9 +89,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -81,9 +89,8 @@ public final class ExoPlayerTest extends TestCase {
assertThat(renderer.isEnded).isFalse(); assertThat(renderer.isEnded).isFalse();
} }
/** /** Tests playback of a source that exposes a single period. */
* Tests playback of a source that exposes a single period. @Test
*/
public void testPlaySinglePeriodTimeline() throws Exception { public void testPlaySinglePeriodTimeline() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
Object manifest = new Object(); Object manifest = new Object();
...@@ -106,9 +113,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -106,9 +113,8 @@ public final class ExoPlayerTest extends TestCase {
assertThat(renderer.isEnded).isTrue(); assertThat(renderer.isEnded).isTrue();
} }
/** /** Tests playback of a source that exposes three periods. */
* Tests playback of a source that exposes three periods. @Test
*/
public void testPlayMultiPeriodTimeline() throws Exception { public void testPlayMultiPeriodTimeline() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3); Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
...@@ -130,6 +136,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -130,6 +136,7 @@ public final class ExoPlayerTest extends TestCase {
} }
/** Tests playback of periods with very short duration. */ /** Tests playback of periods with very short duration. */
@Test
public void testPlayShortDurationPeriods() throws Exception { public void testPlayShortDurationPeriods() throws Exception {
// TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US / 100 = 1000 us per period. // TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US / 100 = 1000 us per period.
Timeline timeline = Timeline timeline =
...@@ -156,16 +163,19 @@ public final class ExoPlayerTest extends TestCase { ...@@ -156,16 +163,19 @@ public final class ExoPlayerTest extends TestCase {
* Tests that the player does not unnecessarily reset renderers when playing a multi-period * Tests that the player does not unnecessarily reset renderers when playing a multi-period
* source. * source.
*/ */
@Test
public void testReadAheadToEndDoesNotResetRenderer() throws Exception { public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3); Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT); final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) { FakeMediaClockRenderer audioRenderer =
new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) {
@Override @Override
public long getPositionUs() { public long getPositionUs() {
// Simulate the playback position lagging behind the reading position: the renderer media // Simulate the playback position lagging behind the reading position: the renderer
// clock position will be the start of the timeline until the stream is set to be final, at // media clock position will be the start of the timeline until the stream is set to be
// which point it jumps to the end of the timeline allowing the playing period to advance. // final, at which point it jumps to the end of the timeline allowing the playing period
// to advance.
// TODO: Avoid hard-coding ExoPlayerImplInternal.RENDERER_TIMESTAMP_OFFSET_US. // TODO: Avoid hard-coding ExoPlayerImplInternal.RENDERER_TIMESTAMP_OFFSET_US.
return isCurrentStreamFinal() ? 60000030 : 60000000; return isCurrentStreamFinal() ? 60000030 : 60000000;
} }
...@@ -184,7 +194,6 @@ public final class ExoPlayerTest extends TestCase { ...@@ -184,7 +194,6 @@ public final class ExoPlayerTest extends TestCase {
public boolean isEnded() { public boolean isEnded() {
return videoRenderer.isEnded(); return videoRenderer.isEnded();
} }
}; };
ExoPlayerTestRunner testRunner = ExoPlayerTestRunner testRunner =
new Builder() new Builder()
...@@ -203,12 +212,13 @@ public final class ExoPlayerTest extends TestCase { ...@@ -203,12 +212,13 @@ public final class ExoPlayerTest extends TestCase {
assertThat(audioRenderer.isEnded).isTrue(); assertThat(audioRenderer.isEnded).isTrue();
} }
@Test
public void testRepreparationGivesFreshSourceInfo() throws Exception { public void testRepreparationGivesFreshSourceInfo() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
Object firstSourceManifest = new Object(); Object firstSourceManifest = new Object();
MediaSource firstSource = new FakeMediaSource(timeline, firstSourceManifest, MediaSource firstSource =
Builder.VIDEO_FORMAT); new FakeMediaSource(timeline, firstSourceManifest, Builder.VIDEO_FORMAT);
final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1); final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1);
final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1); final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1);
MediaSource secondSource = MediaSource secondSource =
...@@ -218,10 +228,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -218,10 +228,8 @@ public final class ExoPlayerTest extends TestCase {
ExoPlayer player, boolean isTopLevelSource, Listener listener) { ExoPlayer player, boolean isTopLevelSource, Listener listener) {
super.prepareSource(player, isTopLevelSource, listener); super.prepareSource(player, isTopLevelSource, listener);
// We've queued a source info refresh on the playback thread's event queue. Allow the // We've queued a source info refresh on the playback thread's event queue. Allow the
// test // test thread to prepare the player with the third source, and block this thread (the
// thread to prepare the player with the third source, and block this thread (the // playback thread) until the test thread's call to prepare() has returned.
// playback
// thread) until the test thread's call to prepare() has returned.
queuedSourceInfoCountDownLatch.countDown(); queuedSourceInfoCountDownLatch.countDown();
try { try {
completePreparationCountDownLatch.await(); completePreparationCountDownLatch.await();
...@@ -231,17 +239,19 @@ public final class ExoPlayerTest extends TestCase { ...@@ -231,17 +239,19 @@ public final class ExoPlayerTest extends TestCase {
} }
}; };
Object thirdSourceManifest = new Object(); Object thirdSourceManifest = new Object();
MediaSource thirdSource = new FakeMediaSource(timeline, thirdSourceManifest, MediaSource thirdSource =
Builder.VIDEO_FORMAT); new FakeMediaSource(timeline, thirdSourceManifest, Builder.VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline. Prepare // Prepare the player with a source with the first manifest and a non-empty timeline. Prepare
// the player again with a source and a new manifest, which will never be exposed. Allow the // the player again with a source and a new manifest, which will never be exposed. Allow the
// test thread to prepare the player with a third source, and block the playback thread until // test thread to prepare the player with a third source, and block the playback thread until
// the test thread's call to prepare() has returned. // the test thread's call to prepare() has returned.
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparation") ActionSchedule actionSchedule =
new ActionSchedule.Builder("testRepreparation")
.waitForTimelineChanged(timeline) .waitForTimelineChanged(timeline)
.prepareSource(secondSource) .prepareSource(secondSource)
.executeRunnable(new Runnable() { .executeRunnable(
new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
...@@ -252,7 +262,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -252,7 +262,8 @@ public final class ExoPlayerTest extends TestCase {
} }
}) })
.prepareSource(thirdSource) .prepareSource(thirdSource)
.executeRunnable(new Runnable() { .executeRunnable(
new Runnable() {
@Override @Override
public void run() { public void run() {
completePreparationCountDownLatch.countDown(); completePreparationCountDownLatch.countDown();
...@@ -273,12 +284,15 @@ public final class ExoPlayerTest extends TestCase { ...@@ -273,12 +284,15 @@ public final class ExoPlayerTest extends TestCase {
// info refresh from the second source was suppressed as we re-prepared with the third source. // info refresh from the second source was suppressed as we re-prepared with the third source.
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline); testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline);
testRunner.assertManifestsEqual(firstSourceManifest, null, thirdSourceManifest); testRunner.assertManifestsEqual(firstSourceManifest, null, thirdSourceManifest);
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED, testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_RESET, Player.TIMELINE_CHANGE_REASON_PREPARED); Player.TIMELINE_CHANGE_REASON_PREPARED,
Player.TIMELINE_CHANGE_REASON_RESET,
Player.TIMELINE_CHANGE_REASON_PREPARED);
testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT))); testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
assertThat(renderer.isEnded).isTrue(); assertThat(renderer.isEnded).isTrue();
} }
@Test
public void testRepeatModeChanges() throws Exception { public void testRepeatModeChanges() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3); Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
...@@ -301,9 +315,14 @@ public final class ExoPlayerTest extends TestCase { ...@@ -301,9 +315,14 @@ public final class ExoPlayerTest extends TestCase {
.setRepeatMode(Player.REPEAT_MODE_OFF) .setRepeatMode(Player.REPEAT_MODE_OFF)
.play() .play()
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
.setTimeline(timeline).setRenderers(renderer).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setTimeline(timeline)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2); testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2);
testRunner.assertPositionDiscontinuityReasonsEqual( testRunner.assertPositionDiscontinuityReasonsEqual(
Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION,
...@@ -320,6 +339,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -320,6 +339,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(renderer.isEnded).isTrue(); assertThat(renderer.isEnded).isTrue();
} }
@Test
public void testShuffleModeEnabledChanges() throws Exception { public void testShuffleModeEnabledChanges() throws Exception {
Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource[] fakeMediaSources = { MediaSource[] fakeMediaSources = {
...@@ -327,8 +347,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -327,8 +347,8 @@ public final class ExoPlayerTest extends TestCase {
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT) new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT)
}; };
ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(false, ConcatenatingMediaSource mediaSource =
new FakeShuffleOrder(3), fakeMediaSources); new ConcatenatingMediaSource(false, new FakeShuffleOrder(3), fakeMediaSources);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder("testShuffleModeEnabled") new ActionSchedule.Builder("testShuffleModeEnabled")
...@@ -342,9 +362,14 @@ public final class ExoPlayerTest extends TestCase { ...@@ -342,9 +362,14 @@ public final class ExoPlayerTest extends TestCase {
.setRepeatMode(Player.REPEAT_MODE_OFF) .setRepeatMode(Player.REPEAT_MODE_OFF)
.play() .play()
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
.setMediaSource(mediaSource).setRenderers(renderer).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setMediaSource(mediaSource)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 0, 2, 1, 2); testRunner.assertPlayedPeriodIndices(0, 1, 0, 2, 1, 2);
testRunner.assertPositionDiscontinuityReasonsEqual( testRunner.assertPositionDiscontinuityReasonsEqual(
Player.DISCONTINUITY_REASON_PERIOD_TRANSITION, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION,
...@@ -355,6 +380,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -355,6 +380,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(renderer.isEnded).isTrue(); assertThat(renderer.isEnded).isTrue();
} }
@Test
public void testPeriodHoldersReleasedAfterSeekWithRepeatModeAll() throws Exception { public void testPeriodHoldersReleasedAfterSeekWithRepeatModeAll() throws Exception {
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
...@@ -374,6 +400,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -374,6 +400,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(renderer.isEnded).isTrue(); assertThat(renderer.isEnded).isTrue();
} }
@Test
public void testSeekProcessedCallback() throws Exception { public void testSeekProcessedCallback() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2); Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
...@@ -387,7 +414,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -387,7 +414,7 @@ public final class ExoPlayerTest extends TestCase {
.seek(2) .seek(2)
.seek(10) .seek(10)
// Wait until media source prepared and re-seek to same position. Expect a seek // Wait until media source prepared and re-seek to same position. Expect a seek
// processed while still being in Player.STATE_READY. // processed while still being in STATE_READY.
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.seek(10) .seek(10)
// Start playback and wait until playback reaches second window. // Start playback and wait until playback reaches second window.
...@@ -438,6 +465,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -438,6 +465,7 @@ public final class ExoPlayerTest extends TestCase {
.inOrder(); .inOrder();
} }
@Test
public void testSeekProcessedCalledWithIllegalSeekPosition() throws Exception { public void testSeekProcessedCalledWithIllegalSeekPosition() throws Exception {
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder("testSeekProcessedCalledWithIllegalSeekPosition") new ActionSchedule.Builder("testSeekProcessedCalledWithIllegalSeekPosition")
...@@ -468,21 +496,29 @@ public final class ExoPlayerTest extends TestCase { ...@@ -468,21 +496,29 @@ public final class ExoPlayerTest extends TestCase {
assertThat(onSeekProcessedCalled[0]).isTrue(); assertThat(onSeekProcessedCalled[0]).isTrue();
} }
@Test
public void testSeekDiscontinuity() throws Exception { public void testSeekDiscontinuity() throws Exception {
FakeTimeline timeline = new FakeTimeline(1); FakeTimeline timeline = new FakeTimeline(1);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekDiscontinuity") ActionSchedule actionSchedule =
.seek(10).build(); new ActionSchedule.Builder("testSeekDiscontinuity").seek(10).build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder().setTimeline(timeline) ExoPlayerTestRunner testRunner =
.setActionSchedule(actionSchedule).build().start().blockUntilEnded(TIMEOUT_MS); new ExoPlayerTestRunner.Builder()
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
} }
@Test
public void testSeekDiscontinuityWithAdjustment() throws Exception { public void testSeekDiscontinuityWithAdjustment() throws Exception {
FakeTimeline timeline = new FakeTimeline(1); FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { FakeMediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, protected FakeMediaPeriod createFakeMediaPeriod(
TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
mediaPeriod.setSeekToUsOffset(10); mediaPeriod.setSeekToUsOffset(10);
return mediaPeriod; return mediaPeriod;
...@@ -495,45 +531,63 @@ public final class ExoPlayerTest extends TestCase { ...@@ -495,45 +531,63 @@ public final class ExoPlayerTest extends TestCase {
.seek(10) .seek(10)
.play() .play()
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource) ExoPlayerTestRunner testRunner =
.setActionSchedule(actionSchedule).build().start().blockUntilEnded(TIMEOUT_MS); new ExoPlayerTestRunner.Builder()
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK, .setMediaSource(mediaSource)
Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); .setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(
Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
} }
@Test
public void testInternalDiscontinuityAtNewPosition() throws Exception { public void testInternalDiscontinuityAtNewPosition() throws Exception {
FakeTimeline timeline = new FakeTimeline(1); FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { FakeMediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, protected FakeMediaPeriod createFakeMediaPeriod(
TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
mediaPeriod.setDiscontinuityPositionUs(10); mediaPeriod.setDiscontinuityPositionUs(10);
return mediaPeriod; return mediaPeriod;
} }
}; };
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource) ExoPlayerTestRunner testRunner =
.build().start().blockUntilEnded(TIMEOUT_MS); new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_INTERNAL); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_INTERNAL);
} }
@Test
public void testInternalDiscontinuityAtInitialPosition() throws Exception { public void testInternalDiscontinuityAtInitialPosition() throws Exception {
FakeTimeline timeline = new FakeTimeline(1); FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { FakeMediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, protected FakeMediaPeriod createFakeMediaPeriod(
TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
mediaPeriod.setDiscontinuityPositionUs(0); mediaPeriod.setDiscontinuityPositionUs(0);
return mediaPeriod; return mediaPeriod;
} }
}; };
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource) ExoPlayerTestRunner testRunner =
.build().start().blockUntilEnded(TIMEOUT_MS); new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
// If the position is unchanged we do not expect the discontinuity to be reported externally. // If the position is unchanged we do not expect the discontinuity to be reported externally.
testRunner.assertNoPositionDiscontinuities(); testRunner.assertNoPositionDiscontinuities();
} }
@Test
public void testAllActivatedTrackSelectionAreReleasedForSinglePeriod() throws Exception { public void testAllActivatedTrackSelectionAreReleasedForSinglePeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource = MediaSource mediaSource =
...@@ -564,6 +618,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -564,6 +618,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(numSelectionsEnabled).isEqualTo(2); assertThat(numSelectionsEnabled).isEqualTo(2);
} }
@Test
public void testAllActivatedTrackSelectionAreReleasedForMultiPeriods() throws Exception { public void testAllActivatedTrackSelectionAreReleasedForMultiPeriods() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2); Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource mediaSource = MediaSource mediaSource =
...@@ -594,6 +649,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -594,6 +649,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(numSelectionsEnabled).isEqualTo(4); assertThat(numSelectionsEnabled).isEqualTo(4);
} }
@Test
public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreRemade() public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreRemade()
throws Exception { throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
...@@ -640,6 +696,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -640,6 +696,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(numSelectionsEnabled).isEqualTo(3); assertThat(numSelectionsEnabled).isEqualTo(3);
} }
@Test
public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed() public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed()
throws Exception { throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
...@@ -686,6 +743,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -686,6 +743,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(numSelectionsEnabled).isEqualTo(3); assertThat(numSelectionsEnabled).isEqualTo(3);
} }
@Test
public void testDynamicTimelineChangeReason() throws Exception { public void testDynamicTimelineChangeReason() throws Exception {
Timeline timeline1 = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000)); Timeline timeline1 = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000));
final Timeline timeline2 = new FakeTimeline(new TimelineWindowDefinition(false, false, 20000)); final Timeline timeline2 = new FakeTimeline(new TimelineWindowDefinition(false, false, 20000));
...@@ -704,27 +762,36 @@ public final class ExoPlayerTest extends TestCase { ...@@ -704,27 +762,36 @@ public final class ExoPlayerTest extends TestCase {
.waitForTimelineChanged(timeline2) .waitForTimelineChanged(timeline2)
.play() .play()
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
.setMediaSource(mediaSource).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setMediaSource(mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesEqual(timeline1, timeline2); testRunner.assertTimelinesEqual(timeline1, timeline2);
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED, testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_DYNAMIC); Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_DYNAMIC);
} }
@Test
public void testRepreparationWithPositionResetAndShufflingUsesFirstPeriod() throws Exception { public void testRepreparationWithPositionResetAndShufflingUsesFirstPeriod() throws Exception {
Timeline fakeTimeline = new FakeTimeline(new TimelineWindowDefinition(/* isSeekable= */ true, Timeline fakeTimeline =
/* isDynamic= */ false, /* durationUs= */ 100000)); new FakeTimeline(
ConcatenatingMediaSource firstMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false, new TimelineWindowDefinition(
/* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 100000));
ConcatenatingMediaSource firstMediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2), new FakeShuffleOrder(/* length= */ 2),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT) new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT));
); ConcatenatingMediaSource secondMediaSource =
ConcatenatingMediaSource secondMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false, new ConcatenatingMediaSource(
/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2), new FakeShuffleOrder(/* length= */ 2),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT), new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT) new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT));
);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder("testRepreparationWithShuffle") new ActionSchedule.Builder("testRepreparationWithShuffle")
// Wait for first preparation and enable shuffling. Plays period 0. // Wait for first preparation and enable shuffling. Plays period 0.
...@@ -736,12 +803,17 @@ public final class ExoPlayerTest extends TestCase { ...@@ -736,12 +803,17 @@ public final class ExoPlayerTest extends TestCase {
.prepareSource(secondMediaSource, /* resetPosition= */ true, /* resetState= */ false) .prepareSource(secondMediaSource, /* resetPosition= */ true, /* resetState= */ false)
.play() .play()
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
.setMediaSource(firstMediaSource).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setMediaSource(firstMediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 0); testRunner.assertPlayedPeriodIndices(0, 1, 0);
} }
@Test
public void testSetPlaybackParametersBeforePreparationCompletesSucceeds() throws Exception { public void testSetPlaybackParametersBeforePreparationCompletesSucceeds() throws Exception {
// Test that no exception is thrown when playback parameters are updated between creating a // Test that no exception is thrown when playback parameters are updated between creating a
// period and preparation of the period completing. // period and preparation of the period completing.
...@@ -763,7 +835,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -763,7 +835,8 @@ public final class ExoPlayerTest extends TestCase {
new ActionSchedule.Builder("testSetPlaybackParametersBeforePreparationCompletesSucceeds") new ActionSchedule.Builder("testSetPlaybackParametersBeforePreparationCompletesSucceeds")
.waitForPlaybackState(Player.STATE_BUFFERING) .waitForPlaybackState(Player.STATE_BUFFERING)
// Block until createPeriod has been called on the fake media source. // Block until createPeriod has been called on the fake media source.
.executeRunnable(new Runnable() { .executeRunnable(
new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
...@@ -776,17 +849,23 @@ public final class ExoPlayerTest extends TestCase { ...@@ -776,17 +849,23 @@ public final class ExoPlayerTest extends TestCase {
// Set playback parameters (while the fake media period is not yet prepared). // Set playback parameters (while the fake media period is not yet prepared).
.setPlaybackParameters(new PlaybackParameters(2f, 2f)) .setPlaybackParameters(new PlaybackParameters(2f, 2f))
// Complete preparation of the fake media period. // Complete preparation of the fake media period.
.executeRunnable(new Runnable() { .executeRunnable(
new Runnable() {
@Override @Override
public void run() { public void run() {
fakeMediaPeriodHolder[0].setPreparationComplete(); fakeMediaPeriodHolder[0].setPreparationComplete();
} }
}) })
.build(); .build();
new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setMediaSource(mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
} }
@Test
public void testStopDoesNotResetPosition() throws Exception { public void testStopDoesNotResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; final long[] positionHolder = new long[1];
...@@ -818,6 +897,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -818,6 +897,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(positionHolder[0]).isAtLeast(50L); assertThat(positionHolder[0]).isAtLeast(50L);
} }
@Test
public void testStopWithoutResetDoesNotResetPosition() throws Exception { public void testStopWithoutResetDoesNotResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; final long[] positionHolder = new long[1];
...@@ -849,6 +929,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -849,6 +929,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(positionHolder[0]).isAtLeast(50L); assertThat(positionHolder[0]).isAtLeast(50L);
} }
@Test
public void testStopWithResetDoesResetPosition() throws Exception { public void testStopWithResetDoesResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; final long[] positionHolder = new long[1];
...@@ -875,21 +956,24 @@ public final class ExoPlayerTest extends TestCase { ...@@ -875,21 +956,24 @@ public final class ExoPlayerTest extends TestCase {
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY); testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY);
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED, testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_RESET); Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_RESET);
testRunner.assertNoPositionDiscontinuities(); testRunner.assertNoPositionDiscontinuities();
assertThat(positionHolder[0]).isEqualTo(0); assertThat(positionHolder[0]).isEqualTo(0);
} }
@Test
public void testStopWithoutResetReleasesMediaSource() throws Exception { public void testStopWithoutResetReleasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT); new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopReleasesMediaSource") ActionSchedule actionSchedule =
new ActionSchedule.Builder("testStopReleasesMediaSource")
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ false) .stop(/* reset= */ false)
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
...@@ -899,15 +983,18 @@ public final class ExoPlayerTest extends TestCase { ...@@ -899,15 +983,18 @@ public final class ExoPlayerTest extends TestCase {
testRunner.blockUntilEnded(TIMEOUT_MS); testRunner.blockUntilEnded(TIMEOUT_MS);
} }
@Test
public void testStopWithResetReleasesMediaSource() throws Exception { public void testStopWithResetReleasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT); new FakeMediaSource(timeline, /* manifest= */ null, Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testStopReleasesMediaSource") ActionSchedule actionSchedule =
new ActionSchedule.Builder("testStopReleasesMediaSource")
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true) .stop(/* reset= */ true)
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
...@@ -917,16 +1004,19 @@ public final class ExoPlayerTest extends TestCase { ...@@ -917,16 +1004,19 @@ public final class ExoPlayerTest extends TestCase {
testRunner.blockUntilEnded(TIMEOUT_MS); testRunner.blockUntilEnded(TIMEOUT_MS);
} }
@Test
public void testRepreparationDoesNotResetAfterStopWithReset() throws Exception { public void testRepreparationDoesNotResetAfterStopWithReset() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT); MediaSource secondSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparationAfterStop") ActionSchedule actionSchedule =
new ActionSchedule.Builder("testRepreparationAfterStop")
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true) .stop(/* reset= */ true)
.waitForPlaybackState(Player.STATE_IDLE) .waitForPlaybackState(Player.STATE_IDLE)
.prepareSource(secondSource) .prepareSource(secondSource)
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.setExpectedPlayerEndedCount(2) .setExpectedPlayerEndedCount(2)
...@@ -934,16 +1024,20 @@ public final class ExoPlayerTest extends TestCase { ...@@ -934,16 +1024,20 @@ public final class ExoPlayerTest extends TestCase {
.start() .start()
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline); testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline);
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED, testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_RESET, Player.TIMELINE_CHANGE_REASON_PREPARED); Player.TIMELINE_CHANGE_REASON_PREPARED,
Player.TIMELINE_CHANGE_REASON_RESET,
Player.TIMELINE_CHANGE_REASON_PREPARED);
testRunner.assertNoPositionDiscontinuities(); testRunner.assertNoPositionDiscontinuities();
} }
@Test
public void testSeekBeforeRepreparationPossibleAfterStopWithReset() throws Exception { public void testSeekBeforeRepreparationPossibleAfterStopWithReset() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource secondSource = new FakeMediaSource(secondTimeline, null, Builder.VIDEO_FORMAT); MediaSource secondSource = new FakeMediaSource(secondTimeline, null, Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekAfterStopWithReset") ActionSchedule actionSchedule =
new ActionSchedule.Builder("testSeekAfterStopWithReset")
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true) .stop(/* reset= */ true)
.waitForPlaybackState(Player.STATE_IDLE) .waitForPlaybackState(Player.STATE_IDLE)
...@@ -951,7 +1045,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -951,7 +1045,8 @@ public final class ExoPlayerTest extends TestCase {
.seek(/* windowIndex= */ 1, /* positionMs= */ 0) .seek(/* windowIndex= */ 1, /* positionMs= */ 0)
.prepareSource(secondSource, /* resetPosition= */ false, /* resetState= */ true) .prepareSource(secondSource, /* resetPosition= */ false, /* resetState= */ true)
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.setExpectedPlayerEndedCount(2) .setExpectedPlayerEndedCount(2)
...@@ -959,12 +1054,15 @@ public final class ExoPlayerTest extends TestCase { ...@@ -959,12 +1054,15 @@ public final class ExoPlayerTest extends TestCase {
.start() .start()
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, secondTimeline); testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, secondTimeline);
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED, testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_RESET, Player.TIMELINE_CHANGE_REASON_PREPARED); Player.TIMELINE_CHANGE_REASON_PREPARED,
Player.TIMELINE_CHANGE_REASON_RESET,
Player.TIMELINE_CHANGE_REASON_PREPARED);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
testRunner.assertPlayedPeriodIndices(0, 1); testRunner.assertPlayedPeriodIndices(0, 1);
} }
@Test
public void testStopDuringPreparationOverwritesPreparation() throws Exception { public void testStopDuringPreparationOverwritesPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
...@@ -987,6 +1085,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -987,6 +1085,7 @@ public final class ExoPlayerTest extends TestCase {
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
} }
@Test
public void testStopAndSeekAfterStopDoesNotResetTimeline() throws Exception { public void testStopAndSeekAfterStopDoesNotResetTimeline() throws Exception {
// Combining additional stop and seek after initial stop in one test to get the seek processed // Combining additional stop and seek after initial stop in one test to get the seek processed
// callback which ensures that all operations have been processed by the player. // callback which ensures that all operations have been processed by the player.
...@@ -1012,6 +1111,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1012,6 +1111,7 @@ public final class ExoPlayerTest extends TestCase {
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
} }
@Test
public void testReprepareAfterPlaybackError() throws Exception { public void testReprepareAfterPlaybackError() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
...@@ -1043,6 +1143,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1043,6 +1143,7 @@ public final class ExoPlayerTest extends TestCase {
Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED); Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED);
} }
@Test
public void testSeekAndReprepareAfterPlaybackError() throws Exception { public void testSeekAndReprepareAfterPlaybackError() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[2]; final long[] positionHolder = new long[2];
...@@ -1098,6 +1199,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1098,6 +1199,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(positionHolder[1]).isEqualTo(50); assertThat(positionHolder[1]).isEqualTo(50);
} }
@Test
public void testPlaybackErrorDuringSourceInfoRefreshStillUpdatesTimeline() throws Exception { public void testPlaybackErrorDuringSourceInfoRefreshStillUpdatesTimeline() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 1); final Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
...@@ -1133,6 +1235,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1133,6 +1235,7 @@ public final class ExoPlayerTest extends TestCase {
testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED); testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED);
} }
@Test
public void testSendMessagesDuringPreparation() throws Exception { public void testSendMessagesDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1152,6 +1255,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1152,6 +1255,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesAfterPreparation() throws Exception { public void testSendMessagesAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1171,6 +1275,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1171,6 +1275,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testMultipleSendMessages() throws Exception { public void testMultipleSendMessages() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target50 = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target50 = new PositionGrabbingMessageTarget();
...@@ -1194,6 +1299,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1194,6 +1299,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target80.positionMs).isAtLeast(target50.positionMs); assertThat(target80.positionMs).isAtLeast(target50.positionMs);
} }
@Test
public void testMultipleSendMessagesAtSameTime() throws Exception { public void testMultipleSendMessagesAtSameTime() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target1 = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target1 = new PositionGrabbingMessageTarget();
...@@ -1216,6 +1322,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1216,6 +1322,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target2.positionMs >= 50).isTrue(); assertThat(target2.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesMultiPeriodResolution() throws Exception { public void testSendMessagesMultiPeriodResolution() throws Exception {
Timeline timeline = Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 10, /* id= */ 0)); new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 10, /* id= */ 0));
...@@ -1236,6 +1343,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1236,6 +1343,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesAtStartAndEndOfPeriod() throws Exception { public void testSendMessagesAtStartAndEndOfPeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2); Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
PositionGrabbingMessageTarget targetStartFirstPeriod = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget targetStartFirstPeriod = new PositionGrabbingMessageTarget();
...@@ -1279,6 +1387,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1279,6 +1387,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(targetEndLastPeriod.positionMs).isAtLeast(duration2Ms); assertThat(targetEndLastPeriod.positionMs).isAtLeast(duration2Ms);
} }
@Test
public void testSendMessagesSeekOnDeliveryTimeDuringPreparation() throws Exception { public void testSendMessagesSeekOnDeliveryTimeDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1297,6 +1406,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1297,6 +1406,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesSeekOnDeliveryTimeAfterPreparation() throws Exception { public void testSendMessagesSeekOnDeliveryTimeAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1316,6 +1426,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1316,6 +1426,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesSeekAfterDeliveryTimeDuringPreparation() throws Exception { public void testSendMessagesSeekAfterDeliveryTimeDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1336,6 +1447,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1336,6 +1447,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET); assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
} }
@Test
public void testSendMessagesSeekAfterDeliveryTimeAfterPreparation() throws Exception { public void testSendMessagesSeekAfterDeliveryTimeAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1356,6 +1468,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1356,6 +1468,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET); assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
} }
@Test
public void testSendMessagesRepeatDoesNotRepost() throws Exception { public void testSendMessagesRepeatDoesNotRepost() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1379,6 +1492,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1379,6 +1492,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesRepeatWithoutDeletingDoesRepost() throws Exception { public void testSendMessagesRepeatWithoutDeletingDoesRepost() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1407,6 +1521,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1407,6 +1521,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesMoveCurrentWindowIndex() throws Exception { public void testSendMessagesMoveCurrentWindowIndex() throws Exception {
Timeline timeline = Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0));
...@@ -1441,6 +1556,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1441,6 +1556,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.windowIndex).isEqualTo(1); assertThat(target.windowIndex).isEqualTo(1);
} }
@Test
public void testSendMessagesMultiWindowDuringPreparation() throws Exception { public void testSendMessagesMultiWindowDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3); Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1461,6 +1577,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1461,6 +1577,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesMultiWindowAfterPreparation() throws Exception { public void testSendMessagesMultiWindowAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3); Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
...@@ -1481,6 +1598,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1481,6 +1598,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.positionMs >= 50).isTrue(); assertThat(target.positionMs >= 50).isTrue();
} }
@Test
public void testSendMessagesMoveWindowIndex() throws Exception { public void testSendMessagesMoveWindowIndex() throws Exception {
Timeline timeline = Timeline timeline =
new FakeTimeline( new FakeTimeline(
...@@ -1518,6 +1636,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1518,6 +1636,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target.windowIndex).isEqualTo(0); assertThat(target.windowIndex).isEqualTo(0);
} }
@Test
public void testSendMessagesNonLinearPeriodOrder() throws Exception { public void testSendMessagesNonLinearPeriodOrder() throws Exception {
Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource[] fakeMediaSources = { MediaSource[] fakeMediaSources = {
...@@ -1552,6 +1671,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1552,6 +1671,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(target3.windowIndex).isEqualTo(2); assertThat(target3.windowIndex).isEqualTo(2);
} }
@Test
public void testSetAndSwitchSurface() throws Exception { public void testSetAndSwitchSurface() throws Exception {
final List<Integer> rendererMessages = new ArrayList<>(); final List<Integer> rendererMessages = new ArrayList<>();
Renderer videoRenderer = Renderer videoRenderer =
...@@ -1574,6 +1694,7 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1574,6 +1694,7 @@ public final class ExoPlayerTest extends TestCase {
assertThat(Collections.frequency(rendererMessages, C.MSG_SET_SURFACE)).isEqualTo(2); assertThat(Collections.frequency(rendererMessages, C.MSG_SET_SURFACE)).isEqualTo(2);
} }
@Test
public void testSwitchSurfaceOnEndedState() throws Exception { public void testSwitchSurfaceOnEndedState() throws Exception {
ActionSchedule.Builder scheduleBuilder = ActionSchedule.Builder scheduleBuilder =
new ActionSchedule.Builder("testSwitchSurfaceOnEndedState") new ActionSchedule.Builder("testSwitchSurfaceOnEndedState")
...@@ -1591,8 +1712,8 @@ public final class ExoPlayerTest extends TestCase { ...@@ -1591,8 +1712,8 @@ public final class ExoPlayerTest extends TestCase {
// Internal methods. // Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
final Surface surface1 = DummySurface.newInstanceV17(/* context= */ null, /* secure= */ false); final Surface surface1 = new Surface(null);
final Surface surface2 = DummySurface.newInstanceV17(/* context= */ null, /* secure= */ false); final Surface surface2 = new Surface(null);
return builder return builder
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.ReflectionHelpers.callInstanceMethod;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.util.Util;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowMessageQueue;
/** Collection of shadow classes used to run tests with Robolectric which require Loopers. */
public final class RobolectricUtil {
private static final AtomicLong sequenceNumberGenerator = new AtomicLong(0);
private RobolectricUtil() {}
/**
* A custom implementation of Robolectric's ShadowLooper which runs all scheduled messages in the
* loop method of the looper. Also ensures to correctly emulate the message order of the real
* message loop and to avoid blocking caused by Robolectric's default implementation.
*
* <p>Only works in conjunction with {@link CustomMessageQueue}. Note that the test's {@code
* SystemClock} is not advanced automatically.
*/
@Implements(Looper.class)
public static final class CustomLooper extends ShadowLooper {
private final PriorityBlockingQueue<PendingMessage> pendingMessages;
private final CopyOnWriteArraySet<RemovedMessage> removedMessages;
public CustomLooper() {
pendingMessages = new PriorityBlockingQueue<>();
removedMessages = new CopyOnWriteArraySet<>();
}
@Implementation
public static void loop() {
ShadowLooper looper = shadowOf(Looper.myLooper());
if (looper instanceof CustomLooper) {
((CustomLooper) looper).doLoop();
}
}
@Implementation
@Override
public void quitUnchecked() {
super.quitUnchecked();
// Insert message at the front of the queue to quit loop as soon as possible.
addPendingMessage(/* message= */ null, /* when= */ Long.MIN_VALUE);
}
private void addPendingMessage(@Nullable Message message, long when) {
pendingMessages.put(new PendingMessage(message, when));
}
private void removeMessages(Handler handler, int what, Object object) {
RemovedMessage newRemovedMessage = new RemovedMessage(handler, what, object);
removedMessages.add(newRemovedMessage);
for (RemovedMessage removedMessage : removedMessages) {
if (removedMessage != newRemovedMessage
&& removedMessage.handler == handler
&& removedMessage.what == what
&& removedMessage.object == object) {
removedMessages.remove(removedMessage);
}
}
}
private void doLoop() {
try {
while (true) {
PendingMessage pendingMessage = pendingMessages.take();
if (pendingMessage.message == null) {
// Null message is signal to end message loop.
return;
}
// Call through to real {@code Message.markInUse()} and {@code Message.recycle()} to
// ensure message recycling works. This is also done in Robolectric's own implementation
// of the message queue.
callInstanceMethod(pendingMessage.message, "markInUse");
Handler target = pendingMessage.message.getTarget();
if (target != null) {
boolean isRemoved = false;
for (RemovedMessage removedMessage : removedMessages) {
if (removedMessage.handler == target
&& removedMessage.what == pendingMessage.message.what
&& (removedMessage.object == null
|| removedMessage.object == pendingMessage.message.obj)
&& pendingMessage.sequenceNumber < removedMessage.sequenceNumber) {
isRemoved = true;
}
}
if (!isRemoved) {
target.dispatchMessage(pendingMessage.message);
}
}
if (Util.SDK_INT >= 21) {
callInstanceMethod(pendingMessage.message, "recycleUnchecked");
} else {
callInstanceMethod(pendingMessage.message, "recycle");
}
}
} catch (InterruptedException e) {
// Ignore.
}
}
}
/**
* Custom implementation of Robolectric's ShadowMessageQueue which is needed to let {@link
* CustomLooper} work as intended.
*/
@Implements(MessageQueue.class)
public static final class CustomMessageQueue extends ShadowMessageQueue {
private final Thread looperThread;
public CustomMessageQueue() {
looperThread = Thread.currentThread();
}
@Implementation
@Override
public boolean enqueueMessage(Message msg, long when) {
ShadowLooper looper = shadowOf(ShadowLooper.getLooperForThread(looperThread));
if (looper instanceof CustomLooper) {
((CustomLooper) looper).addPendingMessage(msg, when);
}
return true;
}
@Implementation
public void removeMessages(Handler handler, int what, Object object) {
ShadowLooper looper = shadowOf(ShadowLooper.getLooperForThread(looperThread));
if (looper instanceof CustomLooper) {
((CustomLooper) looper).removeMessages(handler, what, object);
}
}
}
private static final class PendingMessage implements Comparable<PendingMessage> {
public final @Nullable Message message;
public final long when;
public final long sequenceNumber;
public PendingMessage(@Nullable Message message, long when) {
this.message = message;
this.when = when;
sequenceNumber = sequenceNumberGenerator.getAndIncrement();
}
@Override
public int compareTo(@NonNull PendingMessage other) {
int res = Long.compare(this.when, other.when);
if (res == 0 && this != other) {
res = Long.compare(this.sequenceNumber, other.sequenceNumber);
}
return res;
}
}
private static final class RemovedMessage {
public final Handler handler;
public final int what;
public final Object object;
public final long sequenceNumber;
public RemovedMessage(Handler handler, int what, Object object) {
this.handler = handler;
this.what = what;
this.object = object;
this.sequenceNumber = sequenceNumberGenerator.get();
}
}
}
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.util.HandlerWrapper;
/**
* {@link FakeClock} extension which automatically advances time whenever an empty message is
* enqueued at a future time. The clock time is advanced to the time of the message. Only the first
* Handler sending messages at a future time will be allowed to advance time to ensure there is only
* one "time master". This should usually be the Handler of the internal playback loop.
*/
public final class AutoAdvancingFakeClock extends FakeClock {
private HandlerWrapper autoAdvancingHandler;
public AutoAdvancingFakeClock() {
super(/* initialTimeMs= */ 0);
}
@Override
protected synchronized boolean addHandlerMessageAtTime(
HandlerWrapper handler, int message, long timeMs) {
boolean result = super.addHandlerMessageAtTime(handler, message, timeMs);
if (autoAdvancingHandler == null || autoAdvancingHandler == handler) {
autoAdvancingHandler = handler;
long currentTimeMs = elapsedRealtime();
if (currentTimeMs < timeMs) {
advanceTime(timeMs - currentTimeMs);
}
}
return result;
}
}
...@@ -203,8 +203,8 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener ...@@ -203,8 +203,8 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
} }
/** /**
* Sets the {@link Clock} to be used by the test runner. The default value is {@link * Sets the {@link Clock} to be used by the test runner. The default value is a {@link
* Clock#DEFAULT}. * AutoAdvancingFakeClock}.
* *
* @param clock A {@link Clock} to be used by the test runner. * @param clock A {@link Clock} to be used by the test runner.
* @return This builder. * @return This builder.
...@@ -307,7 +307,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener ...@@ -307,7 +307,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
loadControl = new DefaultLoadControl(); loadControl = new DefaultLoadControl();
} }
if (clock == null) { if (clock == null) {
clock = Clock.DEFAULT; clock = new AutoAdvancingFakeClock();
} }
if (mediaSource == null) { if (mediaSource == null) {
if (timeline == null) { if (timeline == null) {
......
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