SimpleExoplayer Builder for testing
Create a Builder that creates SimpleExoPlayer instances with fake components, suitable for testing. Basically extracts the Builder from ExoPlayerTestRunner to a standalone class that can be re-used. PiperOrigin-RevId: 305458419
This commit is contained in:
parent
703fb777c4
commit
e7fd6a0e01
@ -98,6 +98,8 @@
|
|||||||
* MP4: Store the Android capture frame rate only in `Format.metadata`.
|
* MP4: Store the Android capture frame rate only in `Format.metadata`.
|
||||||
`Format.frameRate` now stores the calculated frame rate.
|
`Format.frameRate` now stores the calculated frame rate.
|
||||||
* Testing
|
* Testing
|
||||||
|
* Add `TestExoPlayer`, a utility class with APIs to create
|
||||||
|
`SimpleExoPlayer` instances with fake components for testing.
|
||||||
* Upgrade Truth dependency from 0.44 to 1.0.
|
* Upgrade Truth dependency from 0.44 to 1.0.
|
||||||
* Upgrade to JUnit 4.13-rc-2.
|
* Upgrade to JUnit 4.13-rc-2.
|
||||||
* UI
|
* UI
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A functional interface representing a supplier of results.
|
||||||
|
*
|
||||||
|
* @param <T> The type of results supplied by this supplier.
|
||||||
|
*/
|
||||||
|
public interface Supplier<T> {
|
||||||
|
|
||||||
|
/** Gets a result. */
|
||||||
|
T get();
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,6 @@ package com.google.android.exoplayer2.analytics;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@ -32,7 +30,6 @@ import com.google.android.exoplayer2.RenderersFactory;
|
|||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Timeline.Window;
|
import com.google.android.exoplayer2.Timeline.Window;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
@ -45,13 +42,13 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
|||||||
import com.google.android.exoplayer2.testutil.ActionSchedule;
|
import com.google.android.exoplayer2.testutil.ActionSchedule;
|
||||||
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
|
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
|
||||||
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
|
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeAudioRenderer;
|
||||||
import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
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;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -131,9 +128,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
public void emptyTimeline() throws Exception {
|
public void emptyTimeline() throws Exception {
|
||||||
FakeMediaSource mediaSource =
|
FakeMediaSource mediaSource =
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
Timeline.EMPTY,
|
Timeline.EMPTY, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT);
|
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||||
|
|
||||||
assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED))
|
assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED))
|
||||||
@ -149,8 +144,8 @@ public final class AnalyticsCollectorTest {
|
|||||||
FakeMediaSource mediaSource =
|
FakeMediaSource mediaSource =
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
SINGLE_PERIOD_TIMELINE,
|
SINGLE_PERIOD_TIMELINE,
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
ExoPlayerTestRunner.VIDEO_FORMAT,
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT);
|
ExoPlayerTestRunner.AUDIO_FORMAT);
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
@ -193,12 +188,12 @@ public final class AnalyticsCollectorTest {
|
|||||||
new ConcatenatingMediaSource(
|
new ConcatenatingMediaSource(
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
SINGLE_PERIOD_TIMELINE,
|
SINGLE_PERIOD_TIMELINE,
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
ExoPlayerTestRunner.VIDEO_FORMAT,
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT),
|
ExoPlayerTestRunner.AUDIO_FORMAT),
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
SINGLE_PERIOD_TIMELINE,
|
SINGLE_PERIOD_TIMELINE,
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
ExoPlayerTestRunner.VIDEO_FORMAT,
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
|
ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
@ -252,8 +247,8 @@ public final class AnalyticsCollectorTest {
|
|||||||
public void periodTransitionWithRendererChange() throws Exception {
|
public void periodTransitionWithRendererChange() throws Exception {
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new ConcatenatingMediaSource(
|
new ConcatenatingMediaSource(
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT),
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT),
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||||
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
|
||||||
|
|
||||||
populateEventIds(listener.lastReportedTimeline);
|
populateEventIds(listener.lastReportedTimeline);
|
||||||
@ -307,9 +302,9 @@ public final class AnalyticsCollectorTest {
|
|||||||
new ConcatenatingMediaSource(
|
new ConcatenatingMediaSource(
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
SINGLE_PERIOD_TIMELINE,
|
SINGLE_PERIOD_TIMELINE,
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
ExoPlayerTestRunner.VIDEO_FORMAT,
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT),
|
ExoPlayerTestRunner.AUDIO_FORMAT),
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
@ -378,11 +373,11 @@ public final class AnalyticsCollectorTest {
|
|||||||
public void seekBackAfterReadingAhead() throws Exception {
|
public void seekBackAfterReadingAhead() throws Exception {
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new ConcatenatingMediaSource(
|
new ConcatenatingMediaSource(
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT),
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT),
|
||||||
new FakeMediaSource(
|
new FakeMediaSource(
|
||||||
SINGLE_PERIOD_TIMELINE,
|
SINGLE_PERIOD_TIMELINE,
|
||||||
ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
|
ExoPlayerTestRunner.VIDEO_FORMAT,
|
||||||
ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
|
ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||||
long periodDurationMs =
|
long periodDurationMs =
|
||||||
SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs();
|
SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs();
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
@ -462,9 +457,9 @@ public final class AnalyticsCollectorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void prepareNewSource() throws Exception {
|
public void prepareNewSource() throws Exception {
|
||||||
MediaSource mediaSource1 =
|
MediaSource mediaSource1 =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
MediaSource mediaSource2 =
|
MediaSource mediaSource2 =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
@ -543,7 +538,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void reprepareAfterError() throws Exception {
|
public void reprepareAfterError() throws Exception {
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
@ -616,7 +611,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void dynamicTimelineChange() throws Exception {
|
public void dynamicTimelineChange() throws Exception {
|
||||||
MediaSource childMediaSource =
|
MediaSource childMediaSource =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
final ConcatenatingMediaSource concatenatedMediaSource =
|
final ConcatenatingMediaSource concatenatedMediaSource =
|
||||||
new ConcatenatingMediaSource(childMediaSource, childMediaSource);
|
new ConcatenatingMediaSource(childMediaSource, childMediaSource);
|
||||||
long periodDurationMs =
|
long periodDurationMs =
|
||||||
@ -697,7 +692,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void playlistOperations() throws Exception {
|
public void playlistOperations() throws Exception {
|
||||||
MediaSource fakeMediaSource =
|
MediaSource fakeMediaSource =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
@ -792,7 +787,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
contentDurationsUs,
|
contentDurationsUs,
|
||||||
adPlaybackState.get()));
|
adPlaybackState.get()));
|
||||||
FakeMediaSource fakeMediaSource =
|
FakeMediaSource fakeMediaSource =
|
||||||
new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(adTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.executeRunnable(
|
.executeRunnable(
|
||||||
@ -1031,7 +1026,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
|
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
|
||||||
+ 5 * C.MICROS_PER_SECOND)));
|
+ 5 * C.MICROS_PER_SECOND)));
|
||||||
FakeMediaSource fakeMediaSource =
|
FakeMediaSource fakeMediaSource =
|
||||||
new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
|
new FakeMediaSource(adTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
@ -1223,12 +1218,12 @@ public final class AnalyticsCollectorTest {
|
|||||||
};
|
};
|
||||||
TestAnalyticsListener listener = new TestAnalyticsListener();
|
TestAnalyticsListener listener = new TestAnalyticsListener();
|
||||||
try {
|
try {
|
||||||
new ExoPlayerTestRunner.Builder()
|
new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext())
|
||||||
.setMediaSources(mediaSource)
|
.setMediaSources(mediaSource)
|
||||||
.setRenderersFactory(renderersFactory)
|
.setRenderersFactory(renderersFactory)
|
||||||
.setAnalyticsListener(listener)
|
.setAnalyticsListener(listener)
|
||||||
.setActionSchedule(actionSchedule)
|
.setActionSchedule(actionSchedule)
|
||||||
.build(ApplicationProvider.getApplicationContext())
|
.build()
|
||||||
.start()
|
.start()
|
||||||
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
@ -1238,140 +1233,6 @@ public final class AnalyticsCollectorTest {
|
|||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FakeVideoRenderer extends FakeRenderer {
|
|
||||||
|
|
||||||
private final VideoRendererEventListener.EventDispatcher eventDispatcher;
|
|
||||||
private final DecoderCounters decoderCounters;
|
|
||||||
private Format format;
|
|
||||||
private long streamOffsetUs;
|
|
||||||
private boolean renderedFirstFrameAfterReset;
|
|
||||||
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
|
|
||||||
private boolean renderedFirstFrameAfterEnable;
|
|
||||||
|
|
||||||
public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) {
|
|
||||||
super(C.TRACK_TYPE_VIDEO);
|
|
||||||
eventDispatcher = new VideoRendererEventListener.EventDispatcher(handler, eventListener);
|
|
||||||
decoderCounters = new DecoderCounters();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
|
||||||
throws ExoPlaybackException {
|
|
||||||
super.onEnabled(joining, mayRenderStartOfStream);
|
|
||||||
eventDispatcher.enabled(decoderCounters);
|
|
||||||
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
|
|
||||||
renderedFirstFrameAfterEnable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
|
|
||||||
super.onStreamChanged(formats, offsetUs);
|
|
||||||
streamOffsetUs = offsetUs;
|
|
||||||
if (renderedFirstFrameAfterReset) {
|
|
||||||
renderedFirstFrameAfterReset = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopped() throws ExoPlaybackException {
|
|
||||||
super.onStopped();
|
|
||||||
eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
|
|
||||||
eventDispatcher.reportVideoFrameProcessingOffset(
|
|
||||||
/* totalProcessingOffsetUs= */ 400000, /* frameCount= */ 10, this.format);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDisabled() {
|
|
||||||
super.onDisabled();
|
|
||||||
eventDispatcher.disabled(decoderCounters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
|
||||||
super.onPositionReset(positionUs, joining);
|
|
||||||
renderedFirstFrameAfterReset = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFormatChanged(Format format) {
|
|
||||||
eventDispatcher.inputFormatChanged(format);
|
|
||||||
eventDispatcher.decoderInitialized(
|
|
||||||
/* decoderName= */ "fake.video.decoder",
|
|
||||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
|
||||||
/* initializationDurationMs= */ 0);
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
|
|
||||||
boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
|
|
||||||
boolean shouldRenderFirstFrame =
|
|
||||||
!renderedFirstFrameAfterEnable
|
|
||||||
? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted)
|
|
||||||
: !renderedFirstFrameAfterReset;
|
|
||||||
shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs;
|
|
||||||
if (shouldProcess && !renderedFirstFrameAfterReset) {
|
|
||||||
eventDispatcher.videoSizeChanged(
|
|
||||||
format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
|
|
||||||
eventDispatcher.renderedFirstFrame(/* surface= */ null);
|
|
||||||
renderedFirstFrameAfterReset = true;
|
|
||||||
renderedFirstFrameAfterEnable = true;
|
|
||||||
}
|
|
||||||
return shouldProcess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class FakeAudioRenderer extends FakeRenderer {
|
|
||||||
|
|
||||||
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
|
|
||||||
private final DecoderCounters decoderCounters;
|
|
||||||
private boolean notifiedAudioSessionId;
|
|
||||||
|
|
||||||
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
|
|
||||||
super(C.TRACK_TYPE_AUDIO);
|
|
||||||
eventDispatcher = new AudioRendererEventListener.EventDispatcher(handler, eventListener);
|
|
||||||
decoderCounters = new DecoderCounters();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
|
||||||
throws ExoPlaybackException {
|
|
||||||
super.onEnabled(joining, mayRenderStartOfStream);
|
|
||||||
eventDispatcher.enabled(decoderCounters);
|
|
||||||
notifiedAudioSessionId = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDisabled() {
|
|
||||||
super.onDisabled();
|
|
||||||
eventDispatcher.disabled(decoderCounters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
|
||||||
super.onPositionReset(positionUs, joining);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFormatChanged(Format format) {
|
|
||||||
eventDispatcher.inputFormatChanged(format);
|
|
||||||
eventDispatcher.decoderInitialized(
|
|
||||||
/* decoderName= */ "fake.audio.decoder",
|
|
||||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
|
||||||
/* initializationDurationMs= */ 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
|
|
||||||
boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
|
|
||||||
if (shouldProcess && !notifiedAudioSessionId) {
|
|
||||||
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
|
|
||||||
notifiedAudioSessionId = true;
|
|
||||||
}
|
|
||||||
return shouldProcess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class EventWindowAndPeriodId {
|
private static final class EventWindowAndPeriodId {
|
||||||
|
|
||||||
private final int windowIndex;
|
private final int windowIndex;
|
||||||
|
@ -17,14 +17,12 @@ package com.google.android.exoplayer2.testutil;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static junit.framework.TestCase.assertFalse;
|
import static junit.framework.TestCase.assertFalse;
|
||||||
import static junit.framework.TestCase.assertTrue;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.LoadControl;
|
import com.google.android.exoplayer2.LoadControl;
|
||||||
@ -33,22 +31,18 @@ import com.google.android.exoplayer2.Renderer;
|
|||||||
import com.google.android.exoplayer2.RenderersFactory;
|
import com.google.android.exoplayer2.RenderersFactory;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer2.util.Clock;
|
import com.google.android.exoplayer2.util.Clock;
|
||||||
import com.google.android.exoplayer2.util.HandlerWrapper;
|
import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@ -56,13 +50,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
/** Helper class to run an ExoPlayer test. */
|
/** Helper class to run an ExoPlayer test. */
|
||||||
public final class ExoPlayerTestRunner implements Player.EventListener, ActionSchedule.Callback {
|
public final class ExoPlayerTestRunner implements Player.EventListener, ActionSchedule.Callback {
|
||||||
|
|
||||||
/**
|
/** A generic video {@link Format} which can be used to set up a {@link FakeMediaSource}. */
|
||||||
* Builder to set-up a {@link ExoPlayerTestRunner}. Default fake implementations will be used for
|
|
||||||
* unset test properties.
|
|
||||||
*/
|
|
||||||
public static final class Builder {
|
|
||||||
|
|
||||||
/** A generic video {@link Format} which can be used to set up media sources and renderers. */
|
|
||||||
public static final Format VIDEO_FORMAT =
|
public static final Format VIDEO_FORMAT =
|
||||||
new Format.Builder()
|
new Format.Builder()
|
||||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||||
@ -71,7 +59,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
.setHeight(720)
|
.setHeight(720)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
/** A generic audio {@link Format} which can be used to set up media sources and renderers. */
|
/** A generic audio {@link Format} which can be used to set up a {@link FakeMediaSource}. */
|
||||||
public static final Format AUDIO_FORMAT =
|
public static final Format AUDIO_FORMAT =
|
||||||
new Format.Builder()
|
new Format.Builder()
|
||||||
.setSampleMimeType(MimeTypes.AUDIO_AAC)
|
.setSampleMimeType(MimeTypes.AUDIO_AAC)
|
||||||
@ -80,28 +68,29 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
.setSampleRate(44100)
|
.setSampleRate(44100)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private Clock clock;
|
/**
|
||||||
|
* Builder to set-up a {@link ExoPlayerTestRunner}. Default fake implementations will be used for
|
||||||
|
* unset test properties.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
private final TestExoPlayer.Builder testPlayerBuilder;
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
private List<MediaSource> mediaSources;
|
private List<MediaSource> mediaSources;
|
||||||
private Object manifest;
|
|
||||||
private DefaultTrackSelector trackSelector;
|
|
||||||
private LoadControl loadControl;
|
|
||||||
private BandwidthMeter bandwidthMeter;
|
|
||||||
private Format[] supportedFormats;
|
private Format[] supportedFormats;
|
||||||
private Renderer[] renderers;
|
private Object manifest;
|
||||||
private RenderersFactory renderersFactory;
|
|
||||||
private ActionSchedule actionSchedule;
|
private ActionSchedule actionSchedule;
|
||||||
private Player.EventListener eventListener;
|
private Player.EventListener eventListener;
|
||||||
private AnalyticsListener analyticsListener;
|
private AnalyticsListener analyticsListener;
|
||||||
private Integer expectedPlayerEndedCount;
|
private Integer expectedPlayerEndedCount;
|
||||||
private boolean useLazyPreparation;
|
|
||||||
private boolean pauseAtEndOfMediaItems;
|
private boolean pauseAtEndOfMediaItems;
|
||||||
private int initialWindowIndex;
|
private int initialWindowIndex;
|
||||||
private long initialPositionMs;
|
private long initialPositionMs;
|
||||||
private boolean skipSettingMediaSources;
|
private boolean skipSettingMediaSources;
|
||||||
|
|
||||||
public Builder() {
|
public Builder(Context context) {
|
||||||
|
testPlayerBuilder = new TestExoPlayer.Builder(context);
|
||||||
mediaSources = new ArrayList<>();
|
mediaSources = new ArrayList<>();
|
||||||
|
supportedFormats = new Format[] {VIDEO_FORMAT};
|
||||||
initialWindowIndex = C.INDEX_UNSET;
|
initialWindowIndex = C.INDEX_UNSET;
|
||||||
initialPositionMs = C.TIME_UNSET;
|
initialPositionMs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
@ -170,6 +159,20 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a list of {@link Format}s to be used by a {@link FakeMediaSource} to create media
|
||||||
|
* periods. The default value is a single {@link #VIDEO_FORMAT}. Note that this parameter
|
||||||
|
* doesn't have any influence if a media source with {@link #setMediaSources(MediaSource...)} is
|
||||||
|
* set.
|
||||||
|
*
|
||||||
|
* @param supportedFormats A list of supported {@link Format}s.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public Builder setSupportedFormats(Format... supportedFormats) {
|
||||||
|
this.supportedFormats = supportedFormats;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips calling {@link com.google.android.exoplayer2.ExoPlayer#setMediaSources(List)} before
|
* Skips calling {@link com.google.android.exoplayer2.ExoPlayer#setMediaSources(List)} before
|
||||||
* preparing. Calling this method is not allowed after calls to {@link
|
* preparing. Calling this method is not allowed after calls to {@link
|
||||||
@ -181,19 +184,17 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
public Builder skipSettingMediaSources() {
|
public Builder skipSettingMediaSources() {
|
||||||
assertThat(timeline).isNull();
|
assertThat(timeline).isNull();
|
||||||
assertThat(manifest).isNull();
|
assertThat(manifest).isNull();
|
||||||
assertTrue(mediaSources.isEmpty());
|
assertThat(mediaSources).isEmpty();
|
||||||
skipSettingMediaSources = true;
|
skipSettingMediaSources = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether to use lazy preparation.
|
* @see TestExoPlayer.Builder#setUseLazyPreparation(boolean)
|
||||||
*
|
|
||||||
* @param useLazyPreparation Whether to use lazy preparation.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setUseLazyPreparation(boolean useLazyPreparation) {
|
public Builder setUseLazyPreparation(boolean useLazyPreparation) {
|
||||||
this.useLazyPreparation = useLazyPreparation;
|
testPlayerBuilder.setUseLazyPreparation(useLazyPreparation);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,94 +210,56 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a {@link DefaultTrackSelector} to be used by the test runner. The default value is a
|
* @see TestExoPlayer.Builder#setTrackSelector(DefaultTrackSelector)
|
||||||
* {@link DefaultTrackSelector} in its initial configuration.
|
|
||||||
*
|
|
||||||
* @param trackSelector A {@link DefaultTrackSelector} to be used by the test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
|
public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
|
||||||
this.trackSelector = trackSelector;
|
testPlayerBuilder.setTrackSelector(trackSelector);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a {@link LoadControl} to be used by the test runner. The default value is a
|
* @see TestExoPlayer.Builder#setLoadControl(LoadControl)
|
||||||
* {@link DefaultLoadControl}.
|
|
||||||
*
|
|
||||||
* @param loadControl A {@link LoadControl} to be used by the test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setLoadControl(LoadControl loadControl) {
|
public Builder setLoadControl(LoadControl loadControl) {
|
||||||
this.loadControl = loadControl;
|
testPlayerBuilder.setLoadControl(loadControl);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link BandwidthMeter} to be used by the test runner. The default value is a {@link
|
* @see TestExoPlayer.Builder#setBandwidthMeter(BandwidthMeter)
|
||||||
* DefaultBandwidthMeter} in its default configuration.
|
|
||||||
*
|
|
||||||
* @param bandwidthMeter The {@link BandwidthMeter} to be used by the test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
|
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
this.testPlayerBuilder.setBandwidthMeter(bandwidthMeter);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a list of {@link Format}s to be used by a {@link FakeMediaSource} to create media
|
* @see TestExoPlayer.Builder#setRenderers(Renderer...)
|
||||||
* periods and for setting up a {@link FakeRenderer}. The default value is a single {@link
|
|
||||||
* #VIDEO_FORMAT}. Note that this parameter doesn't have any influence if both a media source
|
|
||||||
* with {@link #setMediaSources(MediaSource...)} and renderers with {@link
|
|
||||||
* #setRenderers(Renderer...)} or {@link #setRenderersFactory(RenderersFactory)} are set.
|
|
||||||
*
|
|
||||||
* @param supportedFormats A list of supported {@link Format}s.
|
|
||||||
* @return This builder.
|
|
||||||
*/
|
|
||||||
public Builder setSupportedFormats(Format... supportedFormats) {
|
|
||||||
this.supportedFormats = supportedFormats;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link Renderer}s to be used by the test runner. The default value is a single
|
|
||||||
* {@link FakeRenderer} supporting the formats set by {@link #setSupportedFormats(Format...)}.
|
|
||||||
* 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 test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setRenderers(Renderer... renderers) {
|
public Builder setRenderers(Renderer... renderers) {
|
||||||
assertThat(renderersFactory).isNull();
|
testPlayerBuilder.setRenderers(renderers);
|
||||||
this.renderers = renderers;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link RenderersFactory} to be used by the test runner. The default factory creates
|
* @see TestExoPlayer.Builder#setRenderersFactory(RenderersFactory)
|
||||||
* 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 test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
|
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
|
||||||
assertThat(renderers).isNull();
|
testPlayerBuilder.setRenderersFactory(renderersFactory);
|
||||||
this.renderersFactory = renderersFactory;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link Clock} to be used by the test runner. The default value is a {@link
|
* @see TestExoPlayer.Builder#setClock(Clock)
|
||||||
* AutoAdvancingFakeClock}.
|
|
||||||
*
|
|
||||||
* @param clock A {@link Clock} to be used by the test runner.
|
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setClock(Clock clock) {
|
public Builder setClock(Clock clock) {
|
||||||
this.clock = clock;
|
testPlayerBuilder.setClock(clock);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,45 +315,9 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
/**
|
/**
|
||||||
* Builds an {@link ExoPlayerTestRunner} using the provided values or their defaults.
|
* Builds an {@link ExoPlayerTestRunner} using the provided values or their defaults.
|
||||||
*
|
*
|
||||||
* @param context The context.
|
|
||||||
* @return The built {@link ExoPlayerTestRunner}.
|
* @return The built {@link ExoPlayerTestRunner}.
|
||||||
*/
|
*/
|
||||||
public ExoPlayerTestRunner build(Context context) {
|
public ExoPlayerTestRunner build() {
|
||||||
if (supportedFormats == null) {
|
|
||||||
supportedFormats = new Format[] {VIDEO_FORMAT};
|
|
||||||
}
|
|
||||||
if (trackSelector == null) {
|
|
||||||
trackSelector = new DefaultTrackSelector(context);
|
|
||||||
}
|
|
||||||
if (bandwidthMeter == null) {
|
|
||||||
bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
|
|
||||||
}
|
|
||||||
if (renderersFactory == null) {
|
|
||||||
if (renderers == null) {
|
|
||||||
Set<Integer> trackTypes = new HashSet<>();
|
|
||||||
for (Format format : supportedFormats) {
|
|
||||||
trackTypes.add(MimeTypes.getTrackType(format.sampleMimeType));
|
|
||||||
}
|
|
||||||
renderers = new Renderer[trackTypes.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (int trackType : trackTypes) {
|
|
||||||
renderers[i] = new FakeRenderer(trackType);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
renderersFactory =
|
|
||||||
(eventHandler,
|
|
||||||
videoRendererEventListener,
|
|
||||||
audioRendererEventListener,
|
|
||||||
textRendererOutput,
|
|
||||||
metadataRendererOutput) -> renderers;
|
|
||||||
}
|
|
||||||
if (loadControl == null) {
|
|
||||||
loadControl = new DefaultLoadControl();
|
|
||||||
}
|
|
||||||
if (clock == null) {
|
|
||||||
clock = new AutoAdvancingFakeClock();
|
|
||||||
}
|
|
||||||
if (mediaSources.isEmpty() && !skipSettingMediaSources) {
|
if (mediaSources.isEmpty() && !skipSettingMediaSources) {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
timeline = new FakeTimeline(/* windowCount= */ 1, manifest);
|
timeline = new FakeTimeline(/* windowCount= */ 1, manifest);
|
||||||
@ -401,34 +328,24 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
expectedPlayerEndedCount = 1;
|
expectedPlayerEndedCount = 1;
|
||||||
}
|
}
|
||||||
return new ExoPlayerTestRunner(
|
return new ExoPlayerTestRunner(
|
||||||
context,
|
testPlayerBuilder,
|
||||||
clock,
|
|
||||||
initialWindowIndex,
|
|
||||||
initialPositionMs,
|
|
||||||
mediaSources,
|
mediaSources,
|
||||||
skipSettingMediaSources,
|
skipSettingMediaSources,
|
||||||
useLazyPreparation,
|
initialWindowIndex,
|
||||||
pauseAtEndOfMediaItems,
|
initialPositionMs,
|
||||||
renderersFactory,
|
|
||||||
trackSelector,
|
|
||||||
loadControl,
|
|
||||||
bandwidthMeter,
|
|
||||||
actionSchedule,
|
actionSchedule,
|
||||||
eventListener,
|
eventListener,
|
||||||
analyticsListener,
|
analyticsListener,
|
||||||
expectedPlayerEndedCount);
|
expectedPlayerEndedCount,
|
||||||
|
pauseAtEndOfMediaItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final TestExoPlayer.Builder playerBuilder;
|
||||||
private final Clock clock;
|
private final List<MediaSource> mediaSources;
|
||||||
|
private final boolean skipSettingMediaSources;
|
||||||
private final int initialWindowIndex;
|
private final int initialWindowIndex;
|
||||||
private final long initialPositionMs;
|
private final long initialPositionMs;
|
||||||
private final List<MediaSource> mediaSources;
|
|
||||||
private final RenderersFactory renderersFactory;
|
|
||||||
private final DefaultTrackSelector trackSelector;
|
|
||||||
private final LoadControl loadControl;
|
|
||||||
private final BandwidthMeter bandwidthMeter;
|
|
||||||
@Nullable private final ActionSchedule actionSchedule;
|
@Nullable private final ActionSchedule actionSchedule;
|
||||||
@Nullable private final Player.EventListener eventListener;
|
@Nullable private final Player.EventListener eventListener;
|
||||||
@Nullable private final AnalyticsListener analyticsListener;
|
@Nullable private final AnalyticsListener analyticsListener;
|
||||||
@ -442,8 +359,6 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
private final ArrayList<Integer> periodIndices;
|
private final ArrayList<Integer> periodIndices;
|
||||||
private final ArrayList<Integer> discontinuityReasons;
|
private final ArrayList<Integer> discontinuityReasons;
|
||||||
private final ArrayList<Integer> playbackStates;
|
private final ArrayList<Integer> playbackStates;
|
||||||
private final boolean skipSettingMediaSources;
|
|
||||||
private final boolean useLazyPreparation;
|
|
||||||
private final boolean pauseAtEndOfMediaItems;
|
private final boolean pauseAtEndOfMediaItems;
|
||||||
|
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
@ -452,47 +367,36 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
private boolean playerWasPrepared;
|
private boolean playerWasPrepared;
|
||||||
|
|
||||||
private ExoPlayerTestRunner(
|
private ExoPlayerTestRunner(
|
||||||
Context context,
|
TestExoPlayer.Builder playerBuilder,
|
||||||
Clock clock,
|
|
||||||
int initialWindowIndex,
|
|
||||||
long initialPositionMs,
|
|
||||||
List<MediaSource> mediaSources,
|
List<MediaSource> mediaSources,
|
||||||
boolean skipSettingMediaSources,
|
boolean skipSettingMediaSources,
|
||||||
boolean useLazyPreparation,
|
int initialWindowIndex,
|
||||||
boolean pauseAtEndOfMediaItems,
|
long initialPositionMs,
|
||||||
RenderersFactory renderersFactory,
|
|
||||||
DefaultTrackSelector trackSelector,
|
|
||||||
LoadControl loadControl,
|
|
||||||
BandwidthMeter bandwidthMeter,
|
|
||||||
@Nullable ActionSchedule actionSchedule,
|
@Nullable ActionSchedule actionSchedule,
|
||||||
@Nullable Player.EventListener eventListener,
|
@Nullable Player.EventListener eventListener,
|
||||||
@Nullable AnalyticsListener analyticsListener,
|
@Nullable AnalyticsListener analyticsListener,
|
||||||
int expectedPlayerEndedCount) {
|
int expectedPlayerEndedCount,
|
||||||
this.context = context;
|
boolean pauseAtEndOfMediaItems) {
|
||||||
this.clock = clock;
|
this.playerBuilder = playerBuilder;
|
||||||
this.initialWindowIndex = initialWindowIndex;
|
|
||||||
this.initialPositionMs = initialPositionMs;
|
|
||||||
this.mediaSources = mediaSources;
|
this.mediaSources = mediaSources;
|
||||||
this.skipSettingMediaSources = skipSettingMediaSources;
|
this.skipSettingMediaSources = skipSettingMediaSources;
|
||||||
this.useLazyPreparation = useLazyPreparation;
|
this.initialWindowIndex = initialWindowIndex;
|
||||||
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
this.initialPositionMs = initialPositionMs;
|
||||||
this.renderersFactory = renderersFactory;
|
|
||||||
this.trackSelector = trackSelector;
|
|
||||||
this.loadControl = loadControl;
|
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
|
||||||
this.actionSchedule = actionSchedule;
|
this.actionSchedule = actionSchedule;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
this.analyticsListener = analyticsListener;
|
this.analyticsListener = analyticsListener;
|
||||||
this.timelines = new ArrayList<>();
|
timelines = new ArrayList<>();
|
||||||
this.timelineChangeReasons = new ArrayList<>();
|
timelineChangeReasons = new ArrayList<>();
|
||||||
this.periodIndices = new ArrayList<>();
|
periodIndices = new ArrayList<>();
|
||||||
this.discontinuityReasons = new ArrayList<>();
|
discontinuityReasons = new ArrayList<>();
|
||||||
this.playbackStates = new ArrayList<>();
|
playbackStates = new ArrayList<>();
|
||||||
this.endedCountDownLatch = new CountDownLatch(expectedPlayerEndedCount);
|
endedCountDownLatch = new CountDownLatch(expectedPlayerEndedCount);
|
||||||
this.actionScheduleFinishedCountDownLatch = new CountDownLatch(actionSchedule != null ? 1 : 0);
|
actionScheduleFinishedCountDownLatch = new CountDownLatch(actionSchedule != null ? 1 : 0);
|
||||||
this.playerThread = new HandlerThread("ExoPlayerTest thread");
|
playerThread = new HandlerThread("ExoPlayerTest thread");
|
||||||
playerThread.start();
|
playerThread.start();
|
||||||
this.handler = clock.createHandler(playerThread.getLooper(), /* callback= */ null);
|
handler =
|
||||||
|
playerBuilder.getClock().createHandler(playerThread.getLooper(), /* callback= */ null);
|
||||||
|
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on the test thread to run the test.
|
// Called on the test thread to run the test.
|
||||||
@ -519,16 +423,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
handler.post(
|
handler.post(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
player =
|
player = playerBuilder.setLooper(Looper.myLooper()).build();
|
||||||
new SimpleExoPlayer.Builder(context, renderersFactory)
|
|
||||||
.setTrackSelector(trackSelector)
|
|
||||||
.setLoadControl(loadControl)
|
|
||||||
.setBandwidthMeter(bandwidthMeter)
|
|
||||||
.setAnalyticsCollector(new AnalyticsCollector(clock))
|
|
||||||
.setClock(clock)
|
|
||||||
.setUseLazyPreparation(useLazyPreparation)
|
|
||||||
.setLooper(Looper.myLooper())
|
|
||||||
.build();
|
|
||||||
player.addListener(ExoPlayerTestRunner.this);
|
player.addListener(ExoPlayerTestRunner.this);
|
||||||
if (eventListener != null) {
|
if (eventListener != null) {
|
||||||
player.addListener(eventListener);
|
player.addListener(eventListener);
|
||||||
@ -541,7 +436,12 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
|||||||
}
|
}
|
||||||
player.play();
|
player.play();
|
||||||
if (actionSchedule != null) {
|
if (actionSchedule != null) {
|
||||||
actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this);
|
actionSchedule.start(
|
||||||
|
player,
|
||||||
|
playerBuilder.getTrackSelector(),
|
||||||
|
/* surface= */ null,
|
||||||
|
handler,
|
||||||
|
/* callback= */ ExoPlayerTestRunner.this);
|
||||||
}
|
}
|
||||||
if (initialWindowIndex != C.INDEX_UNSET) {
|
if (initialWindowIndex != C.INDEX_UNSET) {
|
||||||
player.seekTo(initialWindowIndex, initialPositionMs);
|
player.seekTo(initialWindowIndex, initialPositionMs);
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
|
||||||
|
/** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_AUDIO}. */
|
||||||
|
public class FakeAudioRenderer extends FakeRenderer {
|
||||||
|
|
||||||
|
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
|
||||||
|
private final DecoderCounters decoderCounters;
|
||||||
|
private boolean notifiedAudioSessionId;
|
||||||
|
|
||||||
|
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
|
||||||
|
super(C.TRACK_TYPE_AUDIO);
|
||||||
|
eventDispatcher = new AudioRendererEventListener.EventDispatcher(handler, eventListener);
|
||||||
|
decoderCounters = new DecoderCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
super.onEnabled(joining, mayRenderStartOfStream);
|
||||||
|
eventDispatcher.enabled(decoderCounters);
|
||||||
|
notifiedAudioSessionId = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisabled() {
|
||||||
|
super.onDisabled();
|
||||||
|
eventDispatcher.disabled(decoderCounters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFormatChanged(Format format) {
|
||||||
|
eventDispatcher.inputFormatChanged(format);
|
||||||
|
eventDispatcher.decoderInitialized(
|
||||||
|
/* decoderName= */ "fake.audio.decoder",
|
||||||
|
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||||
|
/* initializationDurationMs= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
|
||||||
|
boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
|
||||||
|
if (shouldProcess && !notifiedAudioSessionId) {
|
||||||
|
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
|
||||||
|
notifiedAudioSessionId = true;
|
||||||
|
}
|
||||||
|
return shouldProcess;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.Renderer;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
/** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */
|
||||||
|
public class FakeVideoRenderer extends FakeRenderer {
|
||||||
|
|
||||||
|
private final VideoRendererEventListener.EventDispatcher eventDispatcher;
|
||||||
|
private final DecoderCounters decoderCounters;
|
||||||
|
private @MonotonicNonNull Format format;
|
||||||
|
private long streamOffsetUs;
|
||||||
|
private boolean renderedFirstFrameAfterReset;
|
||||||
|
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
|
||||||
|
private boolean renderedFirstFrameAfterEnable;
|
||||||
|
|
||||||
|
public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) {
|
||||||
|
super(C.TRACK_TYPE_VIDEO);
|
||||||
|
eventDispatcher = new VideoRendererEventListener.EventDispatcher(handler, eventListener);
|
||||||
|
decoderCounters = new DecoderCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
super.onEnabled(joining, mayRenderStartOfStream);
|
||||||
|
eventDispatcher.enabled(decoderCounters);
|
||||||
|
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
|
||||||
|
renderedFirstFrameAfterEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
|
||||||
|
super.onStreamChanged(formats, offsetUs);
|
||||||
|
streamOffsetUs = offsetUs;
|
||||||
|
if (renderedFirstFrameAfterReset) {
|
||||||
|
renderedFirstFrameAfterReset = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopped() throws ExoPlaybackException {
|
||||||
|
super.onStopped();
|
||||||
|
eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
|
||||||
|
eventDispatcher.reportVideoFrameProcessingOffset(
|
||||||
|
/* totalProcessingOffsetUs= */ 400000,
|
||||||
|
/* frameCount= */ 10,
|
||||||
|
Assertions.checkNotNull(format));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisabled() {
|
||||||
|
super.onDisabled();
|
||||||
|
eventDispatcher.disabled(decoderCounters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
|
super.onPositionReset(positionUs, joining);
|
||||||
|
renderedFirstFrameAfterReset = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFormatChanged(Format format) {
|
||||||
|
eventDispatcher.inputFormatChanged(format);
|
||||||
|
eventDispatcher.decoderInitialized(
|
||||||
|
/* decoderName= */ "fake.video.decoder",
|
||||||
|
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||||
|
/* initializationDurationMs= */ 0);
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
|
||||||
|
boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
|
||||||
|
boolean shouldRenderFirstFrame =
|
||||||
|
!renderedFirstFrameAfterEnable
|
||||||
|
? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted)
|
||||||
|
: !renderedFirstFrameAfterReset;
|
||||||
|
shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs;
|
||||||
|
if (shouldProcess && !renderedFirstFrameAfterReset) {
|
||||||
|
@MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
|
||||||
|
eventDispatcher.videoSizeChanged(
|
||||||
|
format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
|
||||||
|
eventDispatcher.renderedFirstFrame(/* surface= */ null);
|
||||||
|
renderedFirstFrameAfterReset = true;
|
||||||
|
renderedFirstFrameAfterEnable = true;
|
||||||
|
}
|
||||||
|
return shouldProcess;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,456 @@
|
|||||||
|
/*
|
||||||
|
* 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.ExoPlaybackException;
|
||||||
|
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.Supplier;
|
||||||
|
import com.google.android.exoplayer2.video.VideoListener;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities to write unit/integration tests with a SimpleExoPlayer instance that uses fake
|
||||||
|
* components.
|
||||||
|
*/
|
||||||
|
public class TestExoPlayer {
|
||||||
|
|
||||||
|
/** Reflectively call Robolectric ShadowLooper#runOneTask. */
|
||||||
|
private static final Object shadowLooper;
|
||||||
|
|
||||||
|
private static final Method runOneTaskMethod;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName("org.robolectric.Shadows");
|
||||||
|
Method shadowOfMethod =
|
||||||
|
Assertions.checkNotNull(clazz.getDeclaredMethod("shadowOf", Looper.class));
|
||||||
|
shadowLooper =
|
||||||
|
Assertions.checkNotNull(shadowOfMethod.invoke(new Object(), Looper.getMainLooper()));
|
||||||
|
runOneTaskMethod = shadowLooper.getClass().getDeclaredMethod("runOneTask");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 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();
|
||||||
|
looper = Assertions.checkNotNull(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. */
|
||||||
|
public Looper getLooper() {
|
||||||
|
return looper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an {@link SimpleExoPlayer} using the provided values or their defaults.
|
||||||
|
*
|
||||||
|
* @return The built {@link ExoPlayerTestRunner}.
|
||||||
|
*/
|
||||||
|
public SimpleExoPlayer build() {
|
||||||
|
// 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() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player}'s state reaches the {@code
|
||||||
|
* expectedState}.
|
||||||
|
*/
|
||||||
|
public static void runUntilPlaybackState(
|
||||||
|
SimpleExoPlayer player, @Player.State int expectedState) {
|
||||||
|
verifyMainTestThread(player);
|
||||||
|
if (player.getPlaybackState() == expectedState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean receivedExpectedState = new AtomicBoolean(false);
|
||||||
|
Player.EventListener listener =
|
||||||
|
new Player.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPlaybackStateChanged(int state) {
|
||||||
|
if (state == expectedState) {
|
||||||
|
receivedExpectedState.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addListener(listener);
|
||||||
|
runUntil(() -> receivedExpectedState.get());
|
||||||
|
player.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player} calls the {@link
|
||||||
|
* Player.EventListener#onPlaybackSpeedChanged} callback with that matches {@code
|
||||||
|
* expectedPlayWhenReady}.
|
||||||
|
*/
|
||||||
|
public static void runUntilPlayWhenReady(SimpleExoPlayer player, boolean expectedPlayWhenReady) {
|
||||||
|
verifyMainTestThread(player);
|
||||||
|
if (player.getPlayWhenReady() == expectedPlayWhenReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean receivedExpectedPlayWhenReady = new AtomicBoolean(false);
|
||||||
|
Player.EventListener listener =
|
||||||
|
new Player.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
||||||
|
if (playWhenReady == expectedPlayWhenReady) {
|
||||||
|
receivedExpectedPlayWhenReady.set(true);
|
||||||
|
}
|
||||||
|
player.removeListener(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addListener(listener);
|
||||||
|
runUntil(() -> receivedExpectedPlayWhenReady.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player} calls the {@link
|
||||||
|
* Player.EventListener#onTimelineChanged} callback.
|
||||||
|
*
|
||||||
|
* @param player The {@link SimpleExoPlayer}.
|
||||||
|
* @param expectedTimeline A specific {@link Timeline} to wait for, or null if any timeline is
|
||||||
|
* accepted.
|
||||||
|
* @return The received {@link Timeline}.
|
||||||
|
*/
|
||||||
|
public static Timeline runUntilTimelineChanged(
|
||||||
|
SimpleExoPlayer player, @Nullable Timeline expectedTimeline) {
|
||||||
|
verifyMainTestThread(player);
|
||||||
|
|
||||||
|
if (expectedTimeline != null && expectedTimeline.equals(player.getCurrentTimeline())) {
|
||||||
|
return expectedTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<Timeline> receivedTimeline = new AtomicReference<>();
|
||||||
|
Player.EventListener listener =
|
||||||
|
new Player.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||||
|
if (expectedTimeline == null || expectedTimeline.equals(timeline)) {
|
||||||
|
receivedTimeline.set(timeline);
|
||||||
|
}
|
||||||
|
player.removeListener(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addListener(listener);
|
||||||
|
runUntil(() -> receivedTimeline.get() != null);
|
||||||
|
return receivedTimeline.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player} calls the {@link
|
||||||
|
* Player.EventListener#onPositionDiscontinuity} callback with the specified {@link
|
||||||
|
* Player.DiscontinuityReason}.
|
||||||
|
*/
|
||||||
|
public static void runUntilPositionDiscontinuity(
|
||||||
|
SimpleExoPlayer player, @Player.DiscontinuityReason int expectedReason) {
|
||||||
|
AtomicBoolean receivedCallback = new AtomicBoolean(false);
|
||||||
|
Player.EventListener listener =
|
||||||
|
new Player.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int reason) {
|
||||||
|
if (reason == expectedReason) {
|
||||||
|
receivedCallback.set(true);
|
||||||
|
player.removeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addListener(listener);
|
||||||
|
runUntil(() -> receivedCallback.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player} calls the {@link
|
||||||
|
* Player.EventListener#onPlayerError} callback.
|
||||||
|
*
|
||||||
|
* @param player The {@link SimpleExoPlayer}.
|
||||||
|
* @return The raised error.
|
||||||
|
*/
|
||||||
|
public static ExoPlaybackException runUntilError(SimpleExoPlayer player) {
|
||||||
|
verifyMainTestThread(player);
|
||||||
|
AtomicReference<ExoPlaybackException> receivedError = new AtomicReference<>();
|
||||||
|
Player.EventListener listener =
|
||||||
|
new Player.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
|
receivedError.set(error);
|
||||||
|
player.removeListener(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addListener(listener);
|
||||||
|
runUntil(() -> receivedError.get() != null);
|
||||||
|
return receivedError.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tasks of the main {@link Looper} until the {@code player} calls the {@link
|
||||||
|
* com.google.android.exoplayer2.video.VideoRendererEventListener#onRenderedFirstFrame} callback.
|
||||||
|
*/
|
||||||
|
public static void runUntilRenderedFirstFrame(SimpleExoPlayer player) {
|
||||||
|
verifyMainTestThread(player);
|
||||||
|
AtomicBoolean receivedCallback = new AtomicBoolean(false);
|
||||||
|
VideoListener listener =
|
||||||
|
new VideoListener() {
|
||||||
|
@Override
|
||||||
|
public void onRenderedFirstFrame() {
|
||||||
|
receivedCallback.set(true);
|
||||||
|
player.removeVideoListener(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addVideoListener(listener);
|
||||||
|
runUntil(() -> receivedCallback.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Run tasks of the main {@link Looper} until the {@code condition} returns {@code true}. */
|
||||||
|
public static void runUntil(Supplier<Boolean> condition) {
|
||||||
|
verifyMainTestThread();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!condition.get()) {
|
||||||
|
runOneTaskMethod.invoke(shadowLooper);
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyMainTestThread(SimpleExoPlayer player) {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()
|
||||||
|
|| player.getApplicationLooper() != Looper.getMainLooper()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyMainTestThread() {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user