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 index f100b4fac1..cefe94b6c7 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -19,7 +19,6 @@ import android.os.HandlerThread; import android.support.annotation.Nullable; 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.Player; @@ -32,11 +31,11 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.metadata.MetadataOutput; 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.text.TextOutput; 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.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.MimeTypes; @@ -59,26 +58,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener */ 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 { - - /** - * Creates a new {@link SimpleExoPlayer} using the provided renderers factory, track selector, - * and load control. - * - * @param renderersFactory A {@link RenderersFactory} to be used for the new player. - * @param trackSelector A {@link MappingTrackSelector} to be used for the new player. - * @param loadControl A {@link LoadControl} to be used for the new player. - * @return A new {@link SimpleExoPlayer}. - */ - SimpleExoPlayer createExoPlayer(RenderersFactory renderersFactory, - MappingTrackSelector trackSelector, LoadControl loadControl); - - } - /** * A generic video {@link Format} which can be used to set up media sources and renderers. */ @@ -93,7 +72,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); private Clock clock; - private PlayerFactory playerFactory; private Timeline timeline; private Object manifest; private MediaSource mediaSource; @@ -223,21 +201,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener return this; } - /** - * Sets the {@link PlayerFactory} which creates the {@link SimpleExoPlayer} to be used by the - * test runner. The default value is a {@link SimpleExoPlayer} with the renderers provided by - * {@link #setRenderers(Renderer...)} or {@link #setRenderersFactory(RenderersFactory)}, the - * track selector provided by {@link #setTrackSelector(MappingTrackSelector)} and the load - * control provided by {@link #setLoadControl(LoadControl)}. - * - * @param playerFactory A {@link PlayerFactory} to create the player. - * @return This builder. - */ - public Builder setExoPlayer(PlayerFactory playerFactory) { - this.playerFactory = playerFactory; - return this; - } - /** * Sets the {@link Clock} to be used by the test runner. The default value is {@link * Clock#DEFAULT}. @@ -345,15 +308,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener if (clock == null) { clock = Clock.DEFAULT; } - 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(1); @@ -365,7 +319,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener } return new ExoPlayerTestRunner( clock, - playerFactory, mediaSource, renderersFactory, trackSelector, @@ -378,7 +331,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener } } - private final PlayerFactory playerFactory; + private final Clock clock; private final MediaSource mediaSource; private final RenderersFactory renderersFactory; private final MappingTrackSelector trackSelector; @@ -405,7 +358,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener private ExoPlayerTestRunner( Clock clock, - PlayerFactory playerFactory, MediaSource mediaSource, RenderersFactory renderersFactory, MappingTrackSelector trackSelector, @@ -415,7 +367,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener @Nullable VideoRendererEventListener videoRendererEventListener, @Nullable AudioRendererEventListener audioRendererEventListener, int expectedPlayerEndedCount) { - this.playerFactory = playerFactory; + this.clock = clock; this.mediaSource = mediaSource; this.renderersFactory = renderersFactory; this.trackSelector = trackSelector; @@ -451,7 +403,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener @Override public void run() { try { - player = playerFactory.createExoPlayer(renderersFactory, trackSelector, loadControl); + player = new TestSimpleExoPlayer(renderersFactory, trackSelector, loadControl, clock); player.addListener(ExoPlayerTestRunner.this); if (eventListener != null) { player.addListener(eventListener); @@ -685,4 +637,15 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener actionScheduleFinishedCountDownLatch.countDown(); } + /** SimpleExoPlayer implementation using a custom Clock. */ + private static final class TestSimpleExoPlayer extends SimpleExoPlayer { + + public TestSimpleExoPlayer( + RenderersFactory renderersFactory, + TrackSelector trackSelector, + LoadControl loadControl, + Clock clock) { + super(renderersFactory, trackSelector, loadControl, clock); + } + } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java index 83ecbacdde..49656eef99 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.List; /** Fake {@link Clock} implementation independent of {@link android.os.SystemClock}. */ -public final class FakeClock implements Clock { +public class FakeClock implements Clock { private final List wakeUpTimes; private final List handlerMessages; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java deleted file mode 100644 index 591b94a9cd..0000000000 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSimpleExoPlayer.java +++ /dev/null @@ -1,541 +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.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.support.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; -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.RendererCapabilities; -import com.google.android.exoplayer2.RenderersFactory; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.Timeline.Period; -import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; -import com.google.android.exoplayer2.source.SampleStream; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; -import com.google.android.exoplayer2.trackselection.TrackSelectorResult; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Clock; -import java.util.Arrays; -import java.util.concurrent.CopyOnWriteArraySet; - -/** - * Fake {@link SimpleExoPlayer} which runs a simplified copy of the playback loop as fast as - * possible without waiting. It does only support single period timelines and does not support - * updates during playback (like seek, timeline changes, repeat mode changes). - */ -public class FakeSimpleExoPlayer extends SimpleExoPlayer { - - private FakeExoPlayer player; - - public FakeSimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, - LoadControl loadControl, FakeClock clock) { - super(renderersFactory, trackSelector, loadControl, clock); - player.setFakeClock(clock); - } - - @Override - protected ExoPlayer createExoPlayerImpl( - Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) { - this.player = new FakeExoPlayer(renderers, trackSelector, loadControl); - return player; - } - - private static class FakeExoPlayer extends StubExoPlayer implements MediaSource.Listener, - MediaPeriod.Callback, Runnable { - - private final Renderer[] renderers; - private final TrackSelector trackSelector; - private final LoadControl loadControl; - private final CopyOnWriteArraySet eventListeners; - private final HandlerThread playbackThread; - private final Handler playbackHandler; - private final Handler eventListenerHandler; - - private FakeClock clock; - private MediaSource mediaSource; - private Timeline timeline; - private Object manifest; - private MediaPeriod mediaPeriod; - private TrackSelectorResult selectorResult; - - private boolean isStartingUp; - private boolean isLoading; - private int playbackState; - private long rendererPositionUs; - private long durationUs; - private volatile long currentPositionMs; - private volatile long bufferedPositionMs; - - public FakeExoPlayer(Renderer[] renderers, TrackSelector trackSelector, - LoadControl loadControl) { - this.renderers = renderers; - this.trackSelector = trackSelector; - this.loadControl = loadControl; - this.eventListeners = new CopyOnWriteArraySet<>(); - Looper eventListenerLooper = Looper.myLooper(); - this.eventListenerHandler = new Handler(eventListenerLooper != null ? eventListenerLooper - : Looper.getMainLooper()); - this.playbackThread = new HandlerThread("FakeExoPlayer Thread"); - playbackThread.start(); - this.playbackHandler = new Handler(playbackThread.getLooper()); - this.isStartingUp = true; - this.isLoading = false; - this.playbackState = Player.STATE_IDLE; - this.durationUs = C.TIME_UNSET; - } - - public void setFakeClock(FakeClock clock) { - this.clock = clock; - } - - @Override - public void addListener(Player.EventListener listener) { - eventListeners.add(listener); - } - - @Override - public void removeListener(Player.EventListener listener) { - eventListeners.remove(listener); - } - - @Override - public int getPlaybackState() { - return playbackState; - } - - @Override - public void setPlayWhenReady(boolean playWhenReady) { - if (!playWhenReady) { - throw new UnsupportedOperationException(); - } - } - - @Override - public boolean getPlayWhenReady() { - return true; - } - - @Override - public int getRepeatMode() { - return Player.REPEAT_MODE_OFF; - } - - @Override - public boolean getShuffleModeEnabled() { - return false; - } - - @Override - public boolean isLoading() { - return isLoading; - } - - @Override - public PlaybackParameters getPlaybackParameters() { - return PlaybackParameters.DEFAULT; - } - - @Override - public void stop() { - stop(/* reset= */ false); - } - - @Override - public void stop(boolean reset) { - stopPlayback(/* quitPlaybackThread= */ false); - } - - @Override - @SuppressWarnings("ThreadJoinLoop") - public void release() { - stopPlayback(/* quitPlaybackThread= */ true); - while (playbackThread.isAlive()) { - try { - playbackThread.join(); - } catch (InterruptedException e) { - // Ignore interrupt. - } - } - } - - @Override - public int getRendererCount() { - return renderers.length; - } - - @Override - public int getRendererType(int index) { - return renderers[index].getTrackType(); - } - - @Override - public TrackGroupArray getCurrentTrackGroups() { - return selectorResult != null ? selectorResult.groups : null; - } - - @Override - public TrackSelectionArray getCurrentTrackSelections() { - return selectorResult != null ? selectorResult.selections : null; - } - - @Nullable - @Override - public Object getCurrentManifest() { - return manifest; - } - - @Override - public Timeline getCurrentTimeline() { - return timeline; - } - - @Override - public int getCurrentPeriodIndex() { - return 0; - } - - @Override - public int getCurrentWindowIndex() { - return 0; - } - - @Override - public int getNextWindowIndex() { - return C.INDEX_UNSET; - } - - @Override - public int getPreviousWindowIndex() { - return C.INDEX_UNSET; - } - - @Override - public long getDuration() { - return C.usToMs(durationUs); - } - - @Override - public long getCurrentPosition() { - return currentPositionMs; - } - - @Override - public long getBufferedPosition() { - return bufferedPositionMs == C.TIME_END_OF_SOURCE ? getDuration() : bufferedPositionMs; - } - - @Override - public int getBufferedPercentage() { - long duration = getDuration(); - return duration == C.TIME_UNSET ? 0 : (int) (getBufferedPosition() * 100 / duration); - } - - @Override - public boolean isCurrentWindowDynamic() { - return false; - } - - @Override - public boolean isCurrentWindowSeekable() { - return false; - } - - @Override - public boolean isPlayingAd() { - return false; - } - - @Override - public int getCurrentAdGroupIndex() { - return 0; - } - - @Override - public int getCurrentAdIndexInAdGroup() { - return 0; - } - - @Override - public long getContentPosition() { - return getCurrentPosition(); - } - - @Override - public Looper getPlaybackLooper() { - return playbackThread.getLooper(); - } - - @Override - public void prepare(MediaSource mediaSource) { - prepare(mediaSource, true, true); - } - - @Override - public void prepare(final MediaSource mediaSource, boolean resetPosition, boolean resetState) { - if (!resetPosition || !resetState) { - throw new UnsupportedOperationException(); - } - this.mediaSource = mediaSource; - playbackHandler.post(new Runnable() { - @Override - public void run() { - mediaSource.prepareSource(FakeExoPlayer.this, true, FakeExoPlayer.this); - } - }); - } - - // MediaSource.Listener - - @Override - public void onSourceInfoRefreshed(MediaSource source, final Timeline timeline, - final @Nullable Object manifest) { - if (this.timeline != null) { - throw new UnsupportedOperationException(); - } - Assertions.checkArgument(timeline.getPeriodCount() == 1); - Assertions.checkArgument(timeline.getWindowCount() == 1); - final ConditionVariable waitForNotification = new ConditionVariable(); - eventListenerHandler.post(new Runnable() { - @Override - public void run() { - for (Player.EventListener eventListener : eventListeners) { - FakeExoPlayer.this.durationUs = timeline.getPeriod(0, new Period()).durationUs; - FakeExoPlayer.this.timeline = timeline; - FakeExoPlayer.this.manifest = manifest; - eventListener.onTimelineChanged(timeline, manifest, - Player.TIMELINE_CHANGE_REASON_PREPARED); - waitForNotification.open(); - } - } - }); - waitForNotification.block(); - this.mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(0), loadControl.getAllocator()); - mediaPeriod.prepare(this, 0); - } - - // MediaPeriod.Callback - - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - maybeContinueLoading(); - } - - @Override - public void onPrepared(MediaPeriod mediaPeriod) { - try { - initializePlaybackLoop(); - } catch (ExoPlaybackException e) { - handlePlayerError(e); - } - } - - // Runnable (Playback loop). - - @Override - public void run() { - try { - maybeContinueLoading(); - mediaPeriod.discardBuffer(rendererPositionUs, /* toKeyframe= */ false); - boolean allRenderersEnded = true; - boolean allRenderersReadyOrEnded = true; - if (playbackState == Player.STATE_READY) { - for (Renderer renderer : renderers) { - renderer.render(rendererPositionUs, C.msToUs(clock.elapsedRealtime())); - if (!renderer.isEnded()) { - allRenderersEnded = false; - } - if (!(renderer.isReady() || renderer.isEnded())) { - allRenderersReadyOrEnded = false; - } - } - } - if (rendererPositionUs >= durationUs && allRenderersEnded) { - changePlaybackState(Player.STATE_ENDED); - return; - } - long bufferedPositionUs = mediaPeriod.getBufferedPositionUs(); - if (playbackState == Player.STATE_BUFFERING && allRenderersReadyOrEnded - && haveSufficientBuffer(!isStartingUp, rendererPositionUs, bufferedPositionUs)) { - changePlaybackState(Player.STATE_READY); - isStartingUp = false; - } else if (playbackState == Player.STATE_READY && !allRenderersReadyOrEnded) { - changePlaybackState(Player.STATE_BUFFERING); - } - // Advance simulated time by 10ms. - clock.advanceTime(10); - if (playbackState == Player.STATE_READY) { - rendererPositionUs += 10000; - } - this.currentPositionMs = C.usToMs(rendererPositionUs); - this.bufferedPositionMs = C.usToMs(bufferedPositionUs); - playbackHandler.post(this); - } catch (ExoPlaybackException e) { - handlePlayerError(e); - } - } - - // Internal logic - - private void initializePlaybackLoop() throws ExoPlaybackException { - Assertions.checkNotNull(clock); - trackSelector.init(new InvalidationListener() { - @Override - public void onTrackSelectionsInvalidated() { - throw new IllegalStateException(); - } - }); - RendererCapabilities[] rendererCapabilities = new RendererCapabilities[renderers.length]; - for (int i = 0; i < renderers.length; i++) { - rendererCapabilities[i] = renderers[i].getCapabilities(); - } - selectorResult = trackSelector.selectTracks(rendererCapabilities, - mediaPeriod.getTrackGroups()); - SampleStream[] sampleStreams = new SampleStream[renderers.length]; - boolean[] mayRetainStreamFlags = new boolean[renderers.length]; - Arrays.fill(mayRetainStreamFlags, true); - mediaPeriod.selectTracks( - selectorResult.selections.getAll(), - mayRetainStreamFlags, - sampleStreams, - new boolean[renderers.length], - /* positionUs = */ 0); - eventListenerHandler.post(new Runnable() { - @Override - public void run() { - for (Player.EventListener eventListener : eventListeners) { - eventListener.onTracksChanged(selectorResult.groups, selectorResult.selections); - } - } - }); - - loadControl.onPrepared(); - loadControl.onTracksSelected(renderers, selectorResult.groups, selectorResult.selections); - - for (int i = 0; i < renderers.length; i++) { - TrackSelection selection = selectorResult.selections.get(i); - Format[] formats = new Format[selection.length()]; - for (int j = 0; j < formats.length; j++) { - formats[j] = selection.getFormat(j); - } - renderers[i].enable(selectorResult.rendererConfigurations[i], formats, sampleStreams[i], 0, - false, 0); - renderers[i].setCurrentStreamFinal(); - } - - rendererPositionUs = 0; - changePlaybackState(Player.STATE_BUFFERING); - playbackHandler.post(this); - } - - private void maybeContinueLoading() { - boolean newIsLoading = false; - long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs(); - if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { - long bufferedDurationUs = nextLoadPositionUs - rendererPositionUs; - if (loadControl.shouldContinueLoading(bufferedDurationUs, 1f)) { - newIsLoading = true; - mediaPeriod.continueLoading(rendererPositionUs); - } - } - if (newIsLoading != isLoading) { - isLoading = newIsLoading; - eventListenerHandler.post(new Runnable() { - @Override - public void run() { - for (Player.EventListener eventListener : eventListeners) { - eventListener.onLoadingChanged(isLoading); - } - } - }); - } - } - - private boolean haveSufficientBuffer(boolean rebuffering, long rendererPositionUs, - long bufferedPositionUs) { - return bufferedPositionUs == C.TIME_END_OF_SOURCE - || loadControl.shouldStartPlayback( - bufferedPositionUs - rendererPositionUs, 1f, rebuffering); - } - - private void handlePlayerError(final ExoPlaybackException e) { - eventListenerHandler.post(new Runnable() { - @Override - public void run() { - for (Player.EventListener listener : eventListeners) { - listener.onPlayerError(e); - } - } - }); - changePlaybackState(Player.STATE_ENDED); - } - - private void changePlaybackState(final int playbackState) { - this.playbackState = playbackState; - eventListenerHandler.post(new Runnable() { - @Override - public void run() { - for (Player.EventListener listener : eventListeners) { - listener.onPlayerStateChanged(true, playbackState); - } - } - }); - } - - private void releaseMedia() { - if (mediaSource != null) { - if (mediaPeriod != null) { - mediaSource.releasePeriod(mediaPeriod); - mediaPeriod = null; - } - mediaSource.releaseSource(); - mediaSource = null; - } - } - - private void stopPlayback(final boolean quitPlaybackThread) { - playbackHandler.post(new Runnable() { - @Override - public void run () { - playbackHandler.removeCallbacksAndMessages(null); - releaseMedia(); - changePlaybackState(Player.STATE_IDLE); - if (quitPlaybackThread) { - playbackThread.quit(); - } - } - }); - } - - } - -}