diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index e911778992..5f41e57a6a 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -570,4 +570,48 @@ public final class ExoPlayerTest extends TestCase { testRunner.assertPlayedPeriodIndices(0, 1, 0); } + public void testSetPlaybackParametersBeforePreparationCompletesSucceeds() throws Exception { + // Test that no exception is thrown when playback parameters are updated between creating a + // period and preparation of the period completing. + final CountDownLatch createPeriodCalledCountDownLatch = new CountDownLatch(1); + final FakeMediaPeriod[] fakeMediaPeriodHolder = new FakeMediaPeriod[1]; + MediaSource mediaSource = + new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), null, Builder.VIDEO_FORMAT) { + @Override + protected FakeMediaPeriod createFakeMediaPeriod( + MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) { + // Defer completing preparation of the period until playback parameters have been set. + fakeMediaPeriodHolder[0] = + new FakeMediaPeriod(trackGroupArray, /* deferOnPrepared= */ true); + createPeriodCalledCountDownLatch.countDown(); + return fakeMediaPeriodHolder[0]; + } + }; + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testSetPlaybackParametersBeforePreparationCompletesSucceeds") + .waitForPlaybackState(Player.STATE_BUFFERING) + // Block until createPeriod has been called on the fake media source. + .executeRunnable(new Runnable() { + @Override + public void run() { + try { + createPeriodCalledCountDownLatch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }) + // Set playback parameters (while the fake media period is not yet prepared). + .setPlaybackParameters(new PlaybackParameters(2f, 2f)) + // Complete preparation of the fake media period. + .executeRunnable(new Runnable() { + @Override + public void run() { + fakeMediaPeriodHolder[0].setPreparationComplete(); + } + }) + .build(); + new ExoPlayerTestRunner.Builder().setMediaSource(mediaSource).setActionSchedule(actionSchedule) + .build().start().blockUntilEnded(TIMEOUT_MS); + } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 357d69df38..003d08cd59 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -19,6 +19,7 @@ import android.os.Handler; import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; @@ -303,6 +304,30 @@ public abstract class Action { } + /** + * Calls {@link Player#setPlaybackParameters(PlaybackParameters)}. + */ + public static final class SetPlaybackParameters extends Action { + + private final PlaybackParameters playbackParameters; + + /** + * @param tag A tag to use for logging. + * @param playbackParameters The playback parameters. + */ + public SetPlaybackParameters(String tag, PlaybackParameters playbackParameters) { + super(tag, "SetPlaybackParameters:" + playbackParameters); + this.playbackParameters = playbackParameters; + } + + @Override + protected void doActionImpl(SimpleExoPlayer player, MappingTrackSelector trackSelector, + Surface surface) { + player.setPlaybackParameters(playbackParameters); + } + + } + /** * Waits for {@link Player.EventListener#onTimelineChanged(Timeline, Object, int)}. */ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index ddfa2345ee..2dbb4e18d2 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -19,6 +19,7 @@ import android.os.Handler; import android.os.Looper; import android.view.Surface; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; @@ -28,6 +29,7 @@ import com.google.android.exoplayer2.testutil.Action.ExecuteRunnable; import com.google.android.exoplayer2.testutil.Action.PrepareSource; import com.google.android.exoplayer2.testutil.Action.Seek; import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady; +import com.google.android.exoplayer2.testutil.Action.SetPlaybackParameters; import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled; import com.google.android.exoplayer2.testutil.Action.SetRepeatMode; import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; @@ -151,6 +153,17 @@ public final class ActionSchedule { .apply(new WaitForPlaybackState(tag, Player.STATE_READY)); } + /** + * Schedules a playback parameters setting action to be executed. + * + * @param playbackParameters The playback parameters to set. + * @return The builder, for convenience. + * @see Player#setPlaybackParameters(PlaybackParameters) + */ + public Builder setPlaybackParameters(PlaybackParameters playbackParameters) { + return apply(new SetPlaybackParameters(tag, playbackParameters)); + } + /** * Schedules a stop action to be executed. * diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java index 0b409f5348..c1be199b1e 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.testutil; +import android.os.Handler; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.SampleStream; @@ -32,12 +34,30 @@ public class FakeMediaPeriod implements MediaPeriod { private final TrackGroupArray trackGroupArray; - private boolean preparedPeriod; + @Nullable private Handler playerHandler; + @Nullable private Callback prepareCallback; + + private boolean deferOnPrepared; + private boolean prepared; private long seekOffsetUs; private long discontinuityPositionUs; + /** + * @param trackGroupArray The track group array. + */ public FakeMediaPeriod(TrackGroupArray trackGroupArray) { + this(trackGroupArray, false); + } + + /** + * @param trackGroupArray The track group array. + * @param deferOnPrepared Whether {@link MediaPeriod.Callback#onPrepared(MediaPeriod)} should be + * called only after {@link #setPreparationComplete()} has been called. If {@code false} + * preparation completes immediately. + */ + public FakeMediaPeriod(TrackGroupArray trackGroupArray, boolean deferOnPrepared) { this.trackGroupArray = trackGroupArray; + this.deferOnPrepared = deferOnPrepared; discontinuityPositionUs = C.TIME_UNSET; } @@ -51,6 +71,22 @@ public class FakeMediaPeriod implements MediaPeriod { this.discontinuityPositionUs = discontinuityPositionUs; } + /** + * Allows the fake media period to complete preparation. May be called on any thread. + */ + public synchronized void setPreparationComplete() { + deferOnPrepared = false; + if (playerHandler != null && prepareCallback != null) { + playerHandler.post(new Runnable() { + @Override + public void run() { + prepared = true; + prepareCallback.onPrepared(FakeMediaPeriod.this); + } + }); + } + } + /** * Sets an offset to be applied to positions returned by {@link #seekToUs(long)}. * @@ -61,31 +97,35 @@ public class FakeMediaPeriod implements MediaPeriod { } public void release() { - preparedPeriod = false; + prepared = false; } @Override - public void prepare(Callback callback, long positionUs) { - Assert.assertFalse(preparedPeriod); - preparedPeriod = true; - callback.onPrepared(this); + public synchronized void prepare(Callback callback, long positionUs) { + if (deferOnPrepared) { + playerHandler = new Handler(); + prepareCallback = callback; + } else { + prepared = true; + callback.onPrepared(this); + } } @Override public void maybeThrowPrepareError() throws IOException { - Assert.assertTrue(preparedPeriod); + // Do nothing. } @Override public TrackGroupArray getTrackGroups() { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); return trackGroupArray; } @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); int rendererCount = selections.length; for (int i = 0; i < rendererCount; i++) { if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { @@ -113,7 +153,7 @@ public class FakeMediaPeriod implements MediaPeriod { @Override public long readDiscontinuity() { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); long positionDiscontinuityUs = this.discontinuityPositionUs; this.discontinuityPositionUs = C.TIME_UNSET; return positionDiscontinuityUs; @@ -121,25 +161,25 @@ public class FakeMediaPeriod implements MediaPeriod { @Override public long getBufferedPositionUs() { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); return C.TIME_END_OF_SOURCE; } @Override public long seekToUs(long positionUs) { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); return positionUs + seekOffsetUs; } @Override public long getNextLoadPositionUs() { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); return C.TIME_END_OF_SOURCE; } @Override public boolean continueLoading(long positionUs) { - Assert.assertTrue(preparedPeriod); + Assert.assertTrue(prepared); return false; }