Commit ca3b420c by ibaker Committed by kim-vde

Split TestExoPlayer into Builder and RunHelper classes

TestExoPlayerBuilder can be used from both emulator and robolectric
tests, TestPlayerRunHelper uses Robolectric Looper behaviour, meaning
it can be moved to the robolectricutils module in a follow-up change.

PiperOrigin-RevId: 336634225
parent cf2f9fd7
......@@ -28,7 +28,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.robolectric.RandomizedMp3Decoder;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.TestExoPlayer;
import com.google.android.exoplayer2.testutil.TestPlayerRunHelper;
import com.google.android.exoplayer2.util.Assertions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
......@@ -90,7 +90,7 @@ public class EndToEndGaplessTest {
MediaItem.fromUri("asset:///media/mp3/test.mp3")));
player.prepare();
player.play();
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
Format playerAudioFormat = player.getAudioFormat();
assertThat(playerAudioFormat).isNotNull();
......
......@@ -27,7 +27,7 @@ import com.google.android.exoplayer2.robolectric.PlaybackOutput;
import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.testutil.TestExoPlayer;
import com.google.android.exoplayer2.testutil.TestPlayerRunHelper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -54,7 +54,7 @@ public class Mp4PlaybackTest {
player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4"));
player.prepare();
player.play();
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
DumpFileAsserts.assertOutput(
......
......@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.robolectric.PlaybackOutput;
import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.testutil.TestExoPlayer;
import com.google.android.exoplayer2.testutil.TestPlayerRunHelper;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.Test;
......@@ -88,7 +88,7 @@ public class TsPlaybackTest {
player.setMediaItem(MediaItem.fromUri("asset:///media/ts/" + inputFile));
player.prepare();
player.play();
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
DumpFileAsserts.assertOutput(
......
......@@ -28,7 +28,7 @@ import com.google.android.exoplayer2.robolectric.PlaybackOutput;
import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.testutil.TestExoPlayer;
import com.google.android.exoplayer2.testutil.TestPlayerRunHelper;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import org.junit.Rule;
import org.junit.Test;
......@@ -62,7 +62,7 @@ public final class DashPlaybackTest {
player.setMediaItem(MediaItem.fromUri("asset:///media/dash/webvtt-in-mp4/sample.mpd"));
player.prepare();
player.play();
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
DumpFileAsserts.assertOutput(
......
......@@ -74,7 +74,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
* unset test properties.
*/
public static final class Builder {
private final TestExoPlayer.Builder testPlayerBuilder;
private final TestExoPlayerBuilder testPlayerBuilder;
private Timeline timeline;
private List<MediaSource> mediaSources;
private Format[] supportedFormats;
......@@ -89,7 +89,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
private boolean skipSettingMediaSources;
public Builder(Context context) {
testPlayerBuilder = new TestExoPlayer.Builder(context);
testPlayerBuilder = new TestExoPlayerBuilder(context);
mediaSources = new ArrayList<>();
supportedFormats = new Format[] {VIDEO_FORMAT};
initialWindowIndex = C.INDEX_UNSET;
......@@ -191,7 +191,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setUseLazyPreparation(boolean)
* @see TestExoPlayerBuilder#setUseLazyPreparation(boolean)
* @return This builder.
*/
public Builder setUseLazyPreparation(boolean useLazyPreparation) {
......@@ -211,7 +211,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setTrackSelector(DefaultTrackSelector)
* @see TestExoPlayerBuilder#setTrackSelector(DefaultTrackSelector)
* @return This builder.
*/
public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
......@@ -220,7 +220,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setLoadControl(LoadControl)
* @see TestExoPlayerBuilder#setLoadControl(LoadControl)
* @return This builder.
*/
public Builder setLoadControl(LoadControl loadControl) {
......@@ -229,7 +229,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setBandwidthMeter(BandwidthMeter)
* @see TestExoPlayerBuilder#setBandwidthMeter(BandwidthMeter)
* @return This builder.
*/
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
......@@ -238,7 +238,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setRenderers(Renderer...)
* @see TestExoPlayerBuilder#setRenderers(Renderer...)
* @return This builder.
*/
public Builder setRenderers(Renderer... renderers) {
......@@ -247,7 +247,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setRenderersFactory(RenderersFactory)
* @see TestExoPlayerBuilder#setRenderersFactory(RenderersFactory)
* @return This builder.
*/
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
......@@ -256,7 +256,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
/**
* @see TestExoPlayer.Builder#setClock(Clock)
* @see TestExoPlayerBuilder#setClock(Clock)
* @return This builder.
*/
public Builder setClock(Clock clock) {
......@@ -342,7 +342,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
}
}
private final TestExoPlayer.Builder playerBuilder;
private final TestExoPlayerBuilder playerBuilder;
private final List<MediaSource> mediaSources;
private final boolean skipSettingMediaSources;
private final int initialWindowIndex;
......@@ -370,7 +370,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
private boolean playerWasPrepared;
private ExoPlayerTestRunner(
TestExoPlayer.Builder playerBuilder,
TestExoPlayerBuilder playerBuilder,
List<MediaSource> mediaSources,
boolean skipSettingMediaSources,
int initialWindowIndex,
......
/*
* Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A builder of {@link SimpleExoPlayer} instances for testing. */
public class TestExoPlayerBuilder {
private final Context context;
private Clock clock;
private DefaultTrackSelector trackSelector;
private LoadControl loadControl;
private BandwidthMeter bandwidthMeter;
@Nullable private Renderer[] renderers;
@Nullable private RenderersFactory renderersFactory;
private boolean useLazyPreparation;
private @MonotonicNonNull Looper looper;
public TestExoPlayerBuilder(Context context) {
this.context = context;
clock = new AutoAdvancingFakeClock();
trackSelector = new DefaultTrackSelector(context);
loadControl = new DefaultLoadControl();
bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
@Nullable Looper myLooper = Looper.myLooper();
if (myLooper != null) {
looper = myLooper;
}
}
/**
* Sets whether to use lazy preparation.
*
* @param useLazyPreparation Whether to use lazy preparation.
* @return This builder.
*/
public TestExoPlayerBuilder setUseLazyPreparation(boolean useLazyPreparation) {
this.useLazyPreparation = useLazyPreparation;
return this;
}
/** Returns whether the player will use lazy preparation. */
public boolean getUseLazyPreparation() {
return useLazyPreparation;
}
/**
* Sets a {@link DefaultTrackSelector}. The default value is a {@link DefaultTrackSelector} in its
* initial configuration.
*
* @param trackSelector The {@link DefaultTrackSelector} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setTrackSelector(DefaultTrackSelector trackSelector) {
Assertions.checkNotNull(trackSelector);
this.trackSelector = trackSelector;
return this;
}
/** Returns the track selector used by the player. */
public DefaultTrackSelector getTrackSelector() {
return trackSelector;
}
/**
* Sets a {@link LoadControl} to be used by the player. The default value is a {@link
* DefaultLoadControl}.
*
* @param loadControl The {@link LoadControl} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setLoadControl(LoadControl loadControl) {
this.loadControl = loadControl;
return this;
}
/** Returns the {@link LoadControl} that will be used by the player. */
public LoadControl getLoadControl() {
return loadControl;
}
/**
* Sets the {@link BandwidthMeter}. The default value is a {@link DefaultBandwidthMeter} in its
* default configuration.
*
* @param bandwidthMeter The {@link BandwidthMeter} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
Assertions.checkNotNull(bandwidthMeter);
this.bandwidthMeter = bandwidthMeter;
return this;
}
/** Returns the bandwidth meter used by the player. */
public BandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
}
/**
* Sets the {@link Renderer}s. If not set, the player will use a {@link FakeVideoRenderer} and a
* {@link FakeAudioRenderer}. Setting the renderers is not allowed after a call to {@link
* #setRenderersFactory(RenderersFactory)}.
*
* @param renderers A list of {@link Renderer}s to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setRenderers(Renderer... renderers) {
assertThat(renderersFactory).isNull();
this.renderers = renderers;
return this;
}
/**
* Returns the {@link Renderer Renderers} that have been set with {@link #setRenderers} or null if
* no {@link Renderer Renderers} have been explicitly set. Note that these renderers may not be
* the ones used by the built player, for example if a {@link #setRenderersFactory Renderer
* factory} has been set.
*/
@Nullable
public Renderer[] getRenderers() {
return renderers;
}
/**
* Sets the {@link RenderersFactory}. The default factory creates all renderers set by {@link
* #setRenderers(Renderer...)}. Setting the renderer factory is not allowed after a call to {@link
* #setRenderers(Renderer...)}.
*
* @param renderersFactory A {@link RenderersFactory} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setRenderersFactory(RenderersFactory renderersFactory) {
assertThat(renderers).isNull();
this.renderersFactory = renderersFactory;
return this;
}
/**
* Returns the {@link RenderersFactory} that has been set with {@link #setRenderersFactory} or
* null if no factory has been explicitly set.
*/
@Nullable
public RenderersFactory getRenderersFactory() {
return renderersFactory;
}
/**
* Sets the {@link Clock} to be used by the player. The default value is a {@link
* AutoAdvancingFakeClock}.
*
* @param clock A {@link Clock} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setClock(Clock clock) {
assertThat(clock).isNotNull();
this.clock = clock;
return this;
}
/** Returns the clock used by the player. */
public Clock getClock() {
return clock;
}
/**
* Sets the {@link Looper} to be used by the player.
*
* @param looper The {@link Looper} to be used by the player.
* @return This builder.
*/
public TestExoPlayerBuilder setLooper(Looper looper) {
this.looper = looper;
return this;
}
/**
* Returns the {@link Looper} that will be used by the player, or null if no {@link Looper} has
* been set yet and no default is available.
*/
@Nullable
public Looper getLooper() {
return looper;
}
/**
* Builds an {@link SimpleExoPlayer} using the provided values or their defaults.
*
* @return The built {@link ExoPlayerTestRunner}.
*/
public SimpleExoPlayer build() {
Assertions.checkNotNull(
looper, "TestExoPlayer builder run on a thread without Looper and no Looper specified.");
// Do not update renderersFactory and renderers here, otherwise their getters may
// return different values before and after build() is called, making them confusing.
RenderersFactory playerRenderersFactory = renderersFactory;
if (playerRenderersFactory == null) {
playerRenderersFactory =
(eventHandler,
videoRendererEventListener,
audioRendererEventListener,
textRendererOutput,
metadataRendererOutput) ->
renderers != null
? renderers
: new Renderer[] {
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
new FakeAudioRenderer(eventHandler, audioRendererEventListener)
};
}
return new SimpleExoPlayer.Builder(context, playerRenderersFactory)
.setTrackSelector(trackSelector)
.setLoadControl(loadControl)
.setBandwidthMeter(bandwidthMeter)
.setAnalyticsCollector(new AnalyticsCollector(clock))
.setClock(clock)
.setUseLazyPreparation(useLazyPreparation)
.setLooper(looper)
.build();
}
}
......@@ -17,27 +17,15 @@
package com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.LoadControl;
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.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
......@@ -45,233 +33,14 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Utilities to write unit/integration tests with a SimpleExoPlayer instance that uses fake
* components.
* Helper methods to block the calling thread until the provided {@link SimpleExoPlayer} instance
* reaches a particular state.
*/
public class TestExoPlayer {
public class TestPlayerRunHelper {
/** A builder of {@link SimpleExoPlayer} instances for testing. */
public static class Builder {
private final Context context;
private Clock clock;
private DefaultTrackSelector trackSelector;
private LoadControl loadControl;
private BandwidthMeter bandwidthMeter;
@Nullable private Renderer[] renderers;
@Nullable private RenderersFactory renderersFactory;
private boolean useLazyPreparation;
private @MonotonicNonNull Looper looper;
public Builder(Context context) {
this.context = context;
clock = new AutoAdvancingFakeClock();
trackSelector = new DefaultTrackSelector(context);
loadControl = new DefaultLoadControl();
bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
@Nullable Looper myLooper = Looper.myLooper();
if (myLooper != null) {
looper = myLooper;
}
}
/**
* Sets whether to use lazy preparation.
*
* @param useLazyPreparation Whether to use lazy preparation.
* @return This builder.
*/
public Builder setUseLazyPreparation(boolean useLazyPreparation) {
this.useLazyPreparation = useLazyPreparation;
return this;
}
/** Returns whether the player will use lazy preparation. */
public boolean getUseLazyPreparation() {
return useLazyPreparation;
}
/**
* Sets a {@link DefaultTrackSelector}. The default value is a {@link DefaultTrackSelector} in
* its initial configuration.
*
* @param trackSelector The {@link DefaultTrackSelector} to be used by the player.
* @return This builder.
*/
public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
Assertions.checkNotNull(trackSelector);
this.trackSelector = trackSelector;
return this;
}
/** Returns the track selector used by the player. */
public DefaultTrackSelector getTrackSelector() {
return trackSelector;
}
/**
* Sets a {@link LoadControl} to be used by the player. The default value is a {@link
* DefaultLoadControl}.
*
* @param loadControl The {@link LoadControl} to be used by the player.
* @return This builder.
*/
public Builder setLoadControl(LoadControl loadControl) {
this.loadControl = loadControl;
return this;
}
/** Returns the {@link LoadControl} that will be used by the player. */
public LoadControl getLoadControl() {
return loadControl;
}
/**
* Sets the {@link BandwidthMeter}. The default value is a {@link DefaultBandwidthMeter} in its
* default configuration.
*
* @param bandwidthMeter The {@link BandwidthMeter} to be used by the player.
* @return This builder.
*/
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
Assertions.checkNotNull(bandwidthMeter);
this.bandwidthMeter = bandwidthMeter;
return this;
}
/** Returns the bandwidth meter used by the player. */
public BandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
}
/**
* Sets the {@link Renderer}s. If not set, the player will use a {@link FakeVideoRenderer} and a
* {@link FakeAudioRenderer}. Setting the renderers is not allowed after a call to {@link
* #setRenderersFactory(RenderersFactory)}.
*
* @param renderers A list of {@link Renderer}s to be used by the player.
* @return This builder.
*/
public Builder setRenderers(Renderer... renderers) {
assertThat(renderersFactory).isNull();
this.renderers = renderers;
return this;
}
/**
* Returns the {@link Renderer Renderers} that have been set with {@link #setRenderers} or null
* if no {@link Renderer Renderers} have been explicitly set. Note that these renderers may not
* be the ones used by the built player, for example if a {@link #setRenderersFactory Renderer
* factory} has been set.
*/
@Nullable
public Renderer[] getRenderers() {
return renderers;
}
/**
* Sets the {@link RenderersFactory}. The default factory creates all renderers set by {@link
* #setRenderers(Renderer...)}. Setting the renderer factory is not allowed after a call to
* {@link #setRenderers(Renderer...)}.
*
* @param renderersFactory A {@link RenderersFactory} to be used by the player.
* @return This builder.
*/
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
assertThat(renderers).isNull();
this.renderersFactory = renderersFactory;
return this;
}
/**
* Returns the {@link RenderersFactory} that has been set with {@link #setRenderersFactory} or
* null if no factory has been explicitly set.
*/
@Nullable
public RenderersFactory getRenderersFactory() {
return renderersFactory;
}
/**
* Sets the {@link Clock} to be used by the player. The default value is a {@link
* AutoAdvancingFakeClock}.
*
* @param clock A {@link Clock} to be used by the player.
* @return This builder.
*/
public Builder setClock(Clock clock) {
assertThat(clock).isNotNull();
this.clock = clock;
return this;
}
/** Returns the clock used by the player. */
public Clock getClock() {
return clock;
}
/**
* Sets the {@link Looper} to be used by the player.
*
* @param looper The {@link Looper} to be used by the player.
* @return This builder.
*/
public Builder setLooper(Looper looper) {
this.looper = looper;
return this;
}
/**
* Returns the {@link Looper} that will be used by the player, or null if no {@link Looper} has
* been set yet and no default is available.
*/
@Nullable
public Looper getLooper() {
return looper;
}
/**
* Builds an {@link SimpleExoPlayer} using the provided values or their defaults.
*
* @return The built {@link ExoPlayerTestRunner}.
*/
public SimpleExoPlayer build() {
Assertions.checkNotNull(
looper, "TestExoPlayer builder run on a thread without Looper and no Looper specified.");
// Do not update renderersFactory and renderers here, otherwise their getters may
// return different values before and after build() is called, making them confusing.
RenderersFactory playerRenderersFactory = renderersFactory;
if (playerRenderersFactory == null) {
playerRenderersFactory =
(eventHandler,
videoRendererEventListener,
audioRendererEventListener,
textRendererOutput,
metadataRendererOutput) ->
renderers != null
? renderers
: new Renderer[] {
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
new FakeAudioRenderer(eventHandler, audioRendererEventListener)
};
}
return new SimpleExoPlayer.Builder(context, playerRenderersFactory)
.setTrackSelector(trackSelector)
.setLoadControl(loadControl)
.setBandwidthMeter(bandwidthMeter)
.setAnalyticsCollector(new AnalyticsCollector(clock))
.setClock(clock)
.setUseLazyPreparation(useLazyPreparation)
.setLooper(looper)
.build();
}
}
private TestExoPlayer() {}
private TestPlayerRunHelper() {}
/**
* Runs tasks of the main {@link Looper} until {@link Player#getPlaybackState()} matches the
......
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