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 bf4ea6e972..bc72ebc060 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 @@ -15,20 +15,18 @@ */ package com.google.android.exoplayer2; -import android.util.Pair; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.testutil.ExoPlayerWrapper; +import com.google.android.exoplayer2.testutil.ActionSchedule; +import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; +import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder; import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeRenderer; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; -import com.google.android.exoplayer2.util.MimeTypes; -import java.util.LinkedList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import junit.framework.TestCase; /** @@ -43,67 +41,59 @@ public final class ExoPlayerTest extends TestCase { */ private static final int TIMEOUT_MS = 10000; - private static final Format TEST_VIDEO_FORMAT = Format.createVideoSampleFormat(null, - MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE, - null, null); - private static final Format TEST_AUDIO_FORMAT = Format.createAudioSampleFormat(null, - MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - /** * Tests playback of a source that exposes an empty timeline. Playback is expected to end without * error. */ public void testPlayEmptyTimeline() throws Exception { - ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper(); Timeline timeline = Timeline.EMPTY; - MediaSource mediaSource = new FakeMediaSource(timeline, null); FakeRenderer renderer = new FakeRenderer(); - playerWrapper.setup(mediaSource, renderer); - playerWrapper.blockUntilEnded(TIMEOUT_MS); - assertEquals(0, playerWrapper.positionDiscontinuityCount); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setTimeline(timeline).setRenderers(renderer) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPositionDiscontinuityCount(0); + testRunner.assertTimelinesEqual(timeline); assertEquals(0, renderer.formatReadCount); assertEquals(0, renderer.bufferReadCount); assertFalse(renderer.isEnded); - playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } /** * Tests playback of a source that exposes a single period. */ public void testPlaySinglePeriodTimeline() throws Exception { - ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper(); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); Object manifest = new Object(); - MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT); - FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); - playerWrapper.setup(mediaSource, renderer); - playerWrapper.blockUntilEnded(TIMEOUT_MS); - assertEquals(0, playerWrapper.positionDiscontinuityCount); + FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setTimeline(timeline).setManifest(manifest).setRenderers(renderer) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPositionDiscontinuityCount(0); + testRunner.assertTimelinesEqual(timeline); + testRunner.assertManifestsEqual(manifest); + testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT))); assertEquals(1, renderer.formatReadCount); assertEquals(1, renderer.bufferReadCount); assertTrue(renderer.isEnded); - assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups); - playerWrapper.assertSourceInfosEquals(Pair.create(timeline, manifest)); } /** * Tests playback of a source that exposes three periods. */ public void testPlayMultiPeriodTimeline() throws Exception { - ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper(); Timeline timeline = new FakeTimeline( new TimelineWindowDefinition(false, false, 0), new TimelineWindowDefinition(false, false, 0), new TimelineWindowDefinition(false, false, 0)); - MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT); - FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); - playerWrapper.setup(mediaSource, renderer); - playerWrapper.blockUntilEnded(TIMEOUT_MS); - assertEquals(2, playerWrapper.positionDiscontinuityCount); + FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setTimeline(timeline).setRenderers(renderer) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPositionDiscontinuityCount(2); + testRunner.assertTimelinesEqual(timeline); assertEquals(3, renderer.formatReadCount); assertEquals(1, renderer.bufferReadCount); assertTrue(renderer.isEnded); - playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } /** @@ -111,16 +101,12 @@ public final class ExoPlayerTest extends TestCase { * source. */ public void testReadAheadToEndDoesNotResetRenderer() throws Exception { - final ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper(); Timeline timeline = new FakeTimeline( new TimelineWindowDefinition(false, false, 10), new TimelineWindowDefinition(false, false, 10), new TimelineWindowDefinition(false, false, 10)); - MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT, - TEST_AUDIO_FORMAT); - - FakeRenderer videoRenderer = new FakeRenderer(TEST_VIDEO_FORMAT); - FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(TEST_AUDIO_FORMAT) { + final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT); + FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) { @Override public long getPositionUs() { @@ -143,35 +129,30 @@ public final class ExoPlayerTest extends TestCase { @Override public boolean isEnded() { - // Allow playback to end once the final period is playing. - return playerWrapper.positionDiscontinuityCount == 2; + return videoRenderer.isEnded(); } }; - playerWrapper.setup(mediaSource, videoRenderer, audioRenderer); - playerWrapper.blockUntilEnded(TIMEOUT_MS); - assertEquals(2, playerWrapper.positionDiscontinuityCount); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setTimeline(timeline).setRenderers(videoRenderer, audioRenderer) + .setSupportedFormats(Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPositionDiscontinuityCount(2); + testRunner.assertTimelinesEqual(timeline); assertEquals(1, audioRenderer.positionResetCount); assertTrue(videoRenderer.isEnded); assertTrue(audioRenderer.isEnded); - playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } public void testRepreparationGivesFreshSourceInfo() throws Exception { - ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper(); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); - FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); - - // Prepare the player with a source with the first manifest and a non-empty timeline + FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); Object firstSourceManifest = new Object(); - playerWrapper.setup(new FakeMediaSource(timeline, firstSourceManifest, TEST_VIDEO_FORMAT), - renderer); - playerWrapper.blockUntilSourceInfoRefreshed(TIMEOUT_MS); - - // Prepare the player again with a source and a new manifest, which will never be exposed. + MediaSource firstSource = new FakeMediaSource(timeline, firstSourceManifest, + Builder.VIDEO_FORMAT); final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1); final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1); - playerWrapper.prepare(new FakeMediaSource(timeline, new Object(), TEST_VIDEO_FORMAT) { + MediaSource secondSource = new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) { @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { super.prepareSource(player, isTopLevelSource, listener); @@ -185,29 +166,49 @@ public final class ExoPlayerTest extends TestCase { throw new IllegalStateException(e); } } - }); - - // Prepare the player again with a third source. - queuedSourceInfoCountDownLatch.await(); + }; Object thirdSourceManifest = new Object(); - playerWrapper.prepare(new FakeMediaSource(timeline, thirdSourceManifest, TEST_VIDEO_FORMAT)); - completePreparationCountDownLatch.countDown(); - - // Wait for playback to complete. - playerWrapper.blockUntilEnded(TIMEOUT_MS); - assertEquals(0, playerWrapper.positionDiscontinuityCount); - assertEquals(1, renderer.formatReadCount); - assertEquals(1, renderer.bufferReadCount); - assertTrue(renderer.isEnded); - assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups); + MediaSource thirdSource = new FakeMediaSource(timeline, thirdSourceManifest, + Builder.VIDEO_FORMAT); + // 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 + // 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. + ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparation") + .waitForTimelineChanged(timeline) + .prepareSource(secondSource) + .executeRunnable(new Runnable() { + @Override + public void run() { + try { + queuedSourceInfoCountDownLatch.await(); + } catch (InterruptedException e) { + // Ignore. + } + } + }) + .prepareSource(thirdSource) + .executeRunnable(new Runnable() { + @Override + public void run() { + completePreparationCountDownLatch.countDown(); + } + }) + .build(); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setMediaSource(firstSource).setRenderers(renderer).setActionSchedule(actionSchedule) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPositionDiscontinuityCount(0); // The first source's preparation completed with a non-empty timeline. When the player was // re-prepared with the second source, it immediately exposed an empty timeline, but the source // info refresh from the second source was suppressed as we re-prepared with the third source. - playerWrapper.assertSourceInfosEquals( - Pair.create(timeline, firstSourceManifest), - Pair.create(Timeline.EMPTY, null), - Pair.create(timeline, thirdSourceManifest)); + testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline); + testRunner.assertManifestsEqual(firstSourceManifest, null, thirdSourceManifest); + testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT))); + assertEquals(1, renderer.formatReadCount); + assertEquals(1, renderer.bufferReadCount); + assertTrue(renderer.isEnded); } public void testRepeatModeChanges() throws Exception { @@ -215,49 +216,22 @@ public final class ExoPlayerTest extends TestCase { new TimelineWindowDefinition(true, false, 100000), new TimelineWindowDefinition(true, false, 100000), new TimelineWindowDefinition(true, false, 100000)); - final int[] actionSchedule = { // 0 -> 1 - Player.REPEAT_MODE_ONE, // 1 -> 1 - Player.REPEAT_MODE_OFF, // 1 -> 2 - Player.REPEAT_MODE_ONE, // 2 -> 2 - Player.REPEAT_MODE_ALL, // 2 -> 0 - Player.REPEAT_MODE_ONE, // 0 -> 0 - -1, // 0 -> 0 - Player.REPEAT_MODE_OFF, // 0 -> 1 - -1, // 1 -> 2 - -1 // 2 -> ended - }; - int[] expectedWindowIndices = {1, 1, 2, 2, 0, 0, 0, 1, 2}; - final LinkedList windowIndices = new LinkedList<>(); - final CountDownLatch actionCounter = new CountDownLatch(actionSchedule.length); - ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper() { - @Override - @SuppressWarnings("ResourceType") - public void onPositionDiscontinuity() { - super.onPositionDiscontinuity(); - int actionIndex = actionSchedule.length - (int) actionCounter.getCount(); - if (actionSchedule[actionIndex] != -1) { - player.setRepeatMode(actionSchedule[actionIndex]); - } - windowIndices.add(player.getCurrentWindowIndex()); - actionCounter.countDown(); - } - }; - MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT); - FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); - playerWrapper.setup(mediaSource, renderer); - boolean finished = actionCounter.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - playerWrapper.release(); - assertTrue("Test playback timed out waiting for action schedule to end.", finished); - if (playerWrapper.exception != null) { - throw playerWrapper.exception; - } - assertEquals(expectedWindowIndices.length, windowIndices.size()); - for (int i = 0; i < expectedWindowIndices.length; i++) { - assertEquals(expectedWindowIndices[i], windowIndices.get(i).intValue()); - } - assertEquals(9, playerWrapper.positionDiscontinuityCount); + FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); + ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepeatMode") // 0 -> 1 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 1 -> 1 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 1 -> 2 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 2 -> 2 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ALL) // 2 -> 0 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 0 -> 0 + .waitForPositionDiscontinuity() // 0 -> 0 + .waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 0 -> end + .build(); + ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() + .setTimeline(timeline).setRenderers(renderer).setActionSchedule(actionSchedule) + .build().start().blockUntilEnded(TIMEOUT_MS); + testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2); + testRunner.assertTimelinesEqual(timeline); assertTrue(renderer.isEnded); - playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java new file mode 100644 index 0000000000..2bfef0b4ab --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2017 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 android.os.Handler; +import android.os.HandlerThread; +import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.LoadControl; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder.PlayerFactory; +import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; +import com.google.android.exoplayer2.text.TextRenderer.Output; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.video.VideoRendererEventListener; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import junit.framework.Assert; + +/** + * Helper class to run an ExoPlayer test. + */ +public final class ExoPlayerTestRunner implements Player.EventListener { + + /** + * Builder to set-up a {@link ExoPlayerTestRunner}. Default fake implementations will be used for + * unset test properties. + */ + public static final class Builder { + + /** + * Factory to create an {@link SimpleExoPlayer} instance. The player will be created on its own + * {@link HandlerThread}. + */ + public interface PlayerFactory { + + SimpleExoPlayer createExoPlayer(RenderersFactory renderersFactory, + MappingTrackSelector trackSelector, LoadControl loadControl); + + } + + public static final Format VIDEO_FORMAT = Format.createVideoSampleFormat(null, + MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE, + null, null); + public static final Format AUDIO_FORMAT = Format.createAudioSampleFormat(null, + MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); + + private PlayerFactory playerFactory; + private Timeline timeline; + private Object manifest; + private MediaSource mediaSource; + private MappingTrackSelector trackSelector; + private LoadControl loadControl; + private Format[] supportedFormats; + private Renderer[] renderers; + private RenderersFactory renderersFactory; + private ActionSchedule actionSchedule; + private Player.EventListener eventListener; + + public Builder setTimeline(Timeline timeline) { + Assert.assertNull(mediaSource); + this.timeline = timeline; + return this; + } + + public Builder setManifest(Object manifest) { + Assert.assertNull(mediaSource); + this.manifest = manifest; + return this; + } + + /** Replaces {@link #setTimeline(Timeline)} and {@link #setManifest(Object)}. */ + public Builder setMediaSource(MediaSource mediaSource) { + Assert.assertNull(timeline); + Assert.assertNull(manifest); + this.mediaSource = mediaSource; + return this; + } + + public Builder setTrackSelector(MappingTrackSelector trackSelector) { + this.trackSelector = trackSelector; + return this; + } + + public Builder setLoadControl(LoadControl loadControl) { + this.loadControl = loadControl; + return this; + } + + public Builder setSupportedFormats(Format... supportedFormats) { + this.supportedFormats = supportedFormats; + return this; + } + + public Builder setRenderers(Renderer... renderers) { + Assert.assertNull(renderersFactory); + this.renderers = renderers; + return this; + } + + /** Replaces {@link #setRenderers(Renderer...)}. */ + public Builder setRenderersFactory(RenderersFactory renderersFactory) { + Assert.assertNull(renderers); + this.renderersFactory = renderersFactory; + return this; + } + + public Builder setExoPlayer(PlayerFactory playerFactory) { + this.playerFactory = playerFactory; + return this; + } + + public Builder setActionSchedule(ActionSchedule actionSchedule) { + this.actionSchedule = actionSchedule; + return this; + } + + public Builder setEventListener(Player.EventListener eventListener) { + this.eventListener = eventListener; + return this; + } + + public ExoPlayerTestRunner build() { + if (supportedFormats == null) { + supportedFormats = new Format[] { VIDEO_FORMAT }; + } + if (trackSelector == null) { + trackSelector = new DefaultTrackSelector(); + } + if (renderersFactory == null) { + if (renderers == null) { + renderers = new Renderer[] { new FakeRenderer(supportedFormats) }; + } + renderersFactory = new RenderersFactory() { + @Override + public Renderer[] createRenderers(Handler eventHandler, + VideoRendererEventListener videoRendererEventListener, + AudioRendererEventListener audioRendererEventListener, Output textRendererOutput, + MetadataRenderer.Output metadataRendererOutput) { + return renderers; + } + }; + } + if (loadControl == null) { + loadControl = new DefaultLoadControl(); + } + if (playerFactory == null) { + playerFactory = new PlayerFactory() { + @Override + public SimpleExoPlayer createExoPlayer(RenderersFactory renderersFactory, + MappingTrackSelector trackSelector, LoadControl loadControl) { + return ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + }; + } + if (mediaSource == null) { + if (timeline == null) { + timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); + } + mediaSource = new FakeMediaSource(timeline, manifest, supportedFormats); + } + return new ExoPlayerTestRunner(playerFactory, mediaSource, renderersFactory, trackSelector, + loadControl, actionSchedule, eventListener); + } + } + + private final PlayerFactory playerFactory; + private final MediaSource mediaSource; + private final RenderersFactory renderersFactory; + private final MappingTrackSelector trackSelector; + private final LoadControl loadControl; + private final ActionSchedule actionSchedule; + private final Player.EventListener eventListener; + + private final HandlerThread playerThread; + private final Handler handler; + private final CountDownLatch endedCountDownLatch; + private final LinkedList timelines; + private final LinkedList manifests; + private final LinkedList periodIndices; + + private SimpleExoPlayer player; + private Exception exception; + private TrackGroupArray trackGroups; + private int positionDiscontinuityCount; + + private ExoPlayerTestRunner(PlayerFactory playerFactory, MediaSource mediaSource, + RenderersFactory renderersFactory, MappingTrackSelector trackSelector, + LoadControl loadControl, ActionSchedule actionSchedule, Player.EventListener eventListener) { + this.playerFactory = playerFactory; + this.mediaSource = mediaSource; + this.renderersFactory = renderersFactory; + this.trackSelector = trackSelector; + this.loadControl = loadControl; + this.actionSchedule = actionSchedule; + this.eventListener = eventListener; + this.timelines = new LinkedList<>(); + this.manifests = new LinkedList<>(); + this.periodIndices = new LinkedList<>(); + this.endedCountDownLatch = new CountDownLatch(1); + this.playerThread = new HandlerThread("ExoPlayerTest thread"); + playerThread.start(); + this.handler = new Handler(playerThread.getLooper()); + } + + // Called on the test thread to run the test. + + public ExoPlayerTestRunner start() { + handler.post(new Runnable() { + @Override + public void run() { + try { + player = playerFactory.createExoPlayer(renderersFactory, trackSelector, loadControl); + player.addListener(ExoPlayerTestRunner.this); + if (eventListener != null) { + player.addListener(eventListener); + } + player.setPlayWhenReady(true); + if (actionSchedule != null) { + actionSchedule.start(player, trackSelector, null, handler); + } + player.prepare(mediaSource); + } catch (Exception e) { + handleException(e); + } + } + }); + return this; + } + + public ExoPlayerTestRunner blockUntilEnded(long timeoutMs) throws Exception { + if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { + exception = new TimeoutException("Test playback timed out waiting for playback to end."); + } + release(); + // Throw any pending exception (from playback, timing out or releasing). + if (exception != null) { + throw exception; + } + return this; + } + + // Assertions called on the test thread after test finished. + + public void assertTimelinesEqual(Timeline... timelines) { + Assert.assertEquals(timelines.length, this.timelines.size()); + for (Timeline timeline : timelines) { + Assert.assertEquals(timeline, this.timelines.remove()); + } + } + + public void assertManifestsEqual(Object... manifests) { + Assert.assertEquals(manifests.length, this.manifests.size()); + for (Object manifest : manifests) { + Assert.assertEquals(manifest, this.manifests.remove()); + } + } + + public void assertTrackGroupsEqual(TrackGroupArray trackGroupArray) { + Assert.assertEquals(trackGroupArray, this.trackGroups); + } + + public void assertPositionDiscontinuityCount(int expectedCount) { + Assert.assertEquals(expectedCount, positionDiscontinuityCount); + } + + public void assertPlayedPeriodIndices(int... periodIndices) { + Assert.assertEquals(periodIndices.length, this.periodIndices.size()); + for (int periodIndex : periodIndices) { + Assert.assertEquals(periodIndex, (int) this.periodIndices.remove()); + } + } + + // Private implementation details. + + private void release() throws InterruptedException { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (player != null) { + player.release(); + } + } catch (Exception e) { + handleException(e); + } finally { + playerThread.quit(); + } + } + }); + playerThread.join(); + } + + private void handleException(Exception exception) { + if (this.exception == null) { + this.exception = exception; + } + endedCountDownLatch.countDown(); + } + + // Player.EventListener + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + timelines.add(timeline); + manifests.add(manifest); + } + + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + this.trackGroups = trackGroups; + } + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + if (periodIndices.isEmpty() && playbackState == Player.STATE_READY) { + periodIndices.add(player.getCurrentPeriodIndex()); + } + if (playbackState == Player.STATE_ENDED) { + endedCountDownLatch.countDown(); + } + } + + @Override + public void onRepeatModeChanged(@Player.RepeatMode int repeatMode) { + // Do nothing. + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + handleException(exception); + } + + @Override + public void onPositionDiscontinuity() { + positionDiscontinuityCount++; + periodIndices.add(player.getCurrentPeriodIndex()); + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + +} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerWrapper.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerWrapper.java deleted file mode 100644 index ab247283e6..0000000000 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerWrapper.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2017 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 android.os.Handler; -import android.os.HandlerThread; -import android.util.Pair; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Renderer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import java.util.LinkedList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import junit.framework.Assert; - -/** - * Wraps a player with its own handler thread. - */ -public class ExoPlayerWrapper implements Player.EventListener { - - private final CountDownLatch sourceInfoCountDownLatch; - private final CountDownLatch endedCountDownLatch; - private final HandlerThread playerThread; - private final Handler handler; - private final LinkedList> sourceInfos; - - public ExoPlayer player; - public TrackGroupArray trackGroups; - public Exception exception; - - // Written only on the main thread. - public volatile int positionDiscontinuityCount; - - public ExoPlayerWrapper() { - sourceInfoCountDownLatch = new CountDownLatch(1); - endedCountDownLatch = new CountDownLatch(1); - playerThread = new HandlerThread("ExoPlayerTest thread"); - playerThread.start(); - handler = new Handler(playerThread.getLooper()); - sourceInfos = new LinkedList<>(); - } - - // Called on the test thread. - - public void blockUntilEnded(long timeoutMs) throws Exception { - if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { - exception = new TimeoutException("Test playback timed out waiting for playback to end."); - } - release(); - // Throw any pending exception (from playback, timing out or releasing). - if (exception != null) { - throw exception; - } - } - - public void blockUntilSourceInfoRefreshed(long timeoutMs) throws Exception { - if (!sourceInfoCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { - throw new TimeoutException("Test playback timed out waiting for source info."); - } - } - - public void setup(final MediaSource mediaSource, final Renderer... renderers) { - handler.post(new Runnable() { - @Override - public void run() { - try { - player = ExoPlayerFactory.newInstance(renderers, new DefaultTrackSelector()); - player.addListener(ExoPlayerWrapper.this); - player.setPlayWhenReady(true); - player.prepare(mediaSource); - } catch (Exception e) { - handleError(e); - } - } - }); - } - - public void prepare(final MediaSource mediaSource) { - handler.post(new Runnable() { - @Override - public void run() { - try { - player.prepare(mediaSource); - } catch (Exception e) { - handleError(e); - } - } - }); - } - - public void release() throws InterruptedException { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (player != null) { - player.release(); - } - } catch (Exception e) { - handleError(e); - } finally { - playerThread.quit(); - } - } - }); - playerThread.join(); - } - - private void handleError(Exception exception) { - if (this.exception == null) { - this.exception = exception; - } - endedCountDownLatch.countDown(); - } - - @SafeVarargs - public final void assertSourceInfosEquals(Pair... sourceInfos) { - Assert.assertEquals(sourceInfos.length, this.sourceInfos.size()); - for (Pair sourceInfo : sourceInfos) { - Assert.assertEquals(sourceInfo, this.sourceInfos.remove()); - } - } - - // Player.EventListener implementation. - - @Override - public void onLoadingChanged(boolean isLoading) { - // Do nothing. - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - if (playbackState == Player.STATE_ENDED) { - endedCountDownLatch.countDown(); - } - } - - @Override - public void onRepeatModeChanged(int repeatMode) { - // Do nothing. - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { - sourceInfos.add(Pair.create(timeline, manifest)); - sourceInfoCountDownLatch.countDown(); - } - - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - this.trackGroups = trackGroups; - } - - @Override - public void onPlayerError(ExoPlaybackException exception) { - handleError(exception); - } - - @SuppressWarnings("NonAtomicVolatileUpdate") - @Override - public void onPositionDiscontinuity() { - positionDiscontinuityCount++; - } - - @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - // Do nothing. - } - -}