Move fake ExoPlayer component test classes to testutils.

Fake ExoPlayer componenets used within ExoPlayerTest.java can be useful for
other test classes and are therefore made available in testutils.
They can also be merged with other existing fake components.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=160632908
This commit is contained in:
tonihei 2017-06-30 04:11:22 -07:00 committed by Oliver Woodman
parent d54df32f25
commit 9db0b8cce0
8 changed files with 700 additions and 521 deletions

View File

@ -15,28 +15,20 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
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.testutil.ExoPlayerWrapper;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
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 junit.framework.TestCase; import junit.framework.TestCase;
/** /**
@ -62,7 +54,7 @@ public final class ExoPlayerTest extends TestCase {
* error. * error.
*/ */
public void testPlayEmptyTimeline() throws Exception { public void testPlayEmptyTimeline() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper(); ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = Timeline.EMPTY; Timeline timeline = Timeline.EMPTY;
MediaSource mediaSource = new FakeMediaSource(timeline, null); MediaSource mediaSource = new FakeMediaSource(timeline, null);
FakeRenderer renderer = new FakeRenderer(null); FakeRenderer renderer = new FakeRenderer(null);
@ -79,7 +71,7 @@ public final class ExoPlayerTest extends TestCase {
* Tests playback of a source that exposes a single period. * Tests playback of a source that exposes a single period.
*/ */
public void testPlaySinglePeriodTimeline() throws Exception { public void testPlaySinglePeriodTimeline() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper(); ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
Object manifest = new Object(); Object manifest = new Object();
MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT); MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT);
@ -98,7 +90,7 @@ public final class ExoPlayerTest extends TestCase {
* Tests playback of a source that exposes three periods. * Tests playback of a source that exposes three periods.
*/ */
public void testPlayMultiPeriodTimeline() throws Exception { public void testPlayMultiPeriodTimeline() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper(); ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline( Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 0), new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0), new TimelineWindowDefinition(false, false, 0),
@ -119,7 +111,7 @@ public final class ExoPlayerTest extends TestCase {
* source. * source.
*/ */
public void testReadAheadToEndDoesNotResetRenderer() throws Exception { public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
final PlayerWrapper playerWrapper = new PlayerWrapper(); final ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline( Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 10), new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10), new TimelineWindowDefinition(false, false, 10),
@ -166,7 +158,7 @@ public final class ExoPlayerTest extends TestCase {
} }
public void testRepreparationGivesFreshSourceInfo() throws Exception { public void testRepreparationGivesFreshSourceInfo() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper(); ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
@ -237,7 +229,7 @@ public final class ExoPlayerTest extends TestCase {
int[] expectedWindowIndices = {1, 1, 2, 2, 0, 0, 0, 1, 2}; int[] expectedWindowIndices = {1, 1, 2, 2, 0, 0, 0, 1, 2};
final LinkedList<Integer> windowIndices = new LinkedList<>(); final LinkedList<Integer> windowIndices = new LinkedList<>();
final CountDownLatch actionCounter = new CountDownLatch(actionSchedule.length); final CountDownLatch actionCounter = new CountDownLatch(actionSchedule.length);
PlayerWrapper playerWrapper = new PlayerWrapper() { ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper() {
@Override @Override
@SuppressWarnings("ResourceType") @SuppressWarnings("ResourceType")
public void onPositionDiscontinuity() { public void onPositionDiscontinuity() {
@ -268,505 +260,4 @@ public final class ExoPlayerTest extends TestCase {
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
} }
/**
* Wraps a player with its own handler thread.
*/
private static class PlayerWrapper implements ExoPlayer.EventListener {
private final CountDownLatch sourceInfoCountDownLatch;
private final CountDownLatch endedCountDownLatch;
private final HandlerThread playerThread;
private final Handler handler;
private final LinkedList<Pair<Timeline, Object>> sourceInfos;
/* package */ ExoPlayer player;
private TrackGroupArray trackGroups;
private Exception exception;
// Written only on the main thread.
private volatile int positionDiscontinuityCount;
public PlayerWrapper() {
sourceInfoCountDownLatch = new CountDownLatch(1);
endedCountDownLatch = new CountDownLatch(1);
playerThread = new HandlerThread("ExoPlayerTest thread");
playerThread.start();
handler = new Handler(playerThread.getLooper());
sourceInfos = new LinkedList<>();
}
// Called on the test thread.
public void blockUntilEnded(long timeoutMs) throws Exception {
if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
exception = new TimeoutException("Test playback timed out waiting for playback to end.");
}
release();
// Throw any pending exception (from playback, timing out or releasing).
if (exception != null) {
throw exception;
}
}
public void blockUntilSourceInfoRefreshed(long timeoutMs) throws Exception {
if (!sourceInfoCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("Test playback timed out waiting for source info.");
}
}
public void setup(final MediaSource mediaSource, final Renderer... renderers) {
handler.post(new Runnable() {
@Override
public void run() {
try {
player = ExoPlayerFactory.newInstance(renderers, new DefaultTrackSelector());
player.addListener(PlayerWrapper.this);
player.setPlayWhenReady(true);
player.prepare(mediaSource);
} catch (Exception e) {
handleError(e);
}
}
});
}
public void prepare(final MediaSource mediaSource) {
handler.post(new Runnable() {
@Override
public void run() {
try {
player.prepare(mediaSource);
} catch (Exception e) {
handleError(e);
}
}
});
}
public void release() throws InterruptedException {
handler.post(new Runnable() {
@Override
public void run() {
try {
if (player != null) {
player.release();
}
} catch (Exception e) {
handleError(e);
} finally {
playerThread.quit();
}
}
});
playerThread.join();
}
private void handleError(Exception exception) {
if (this.exception == null) {
this.exception = exception;
}
endedCountDownLatch.countDown();
}
@SafeVarargs
public final void assertSourceInfosEquals(Pair<Timeline, Object>... sourceInfos) {
assertEquals(sourceInfos.length, this.sourceInfos.size());
for (Pair<Timeline, Object> sourceInfo : sourceInfos) {
assertEquals(sourceInfo, this.sourceInfos.remove());
}
}
// ExoPlayer.EventListener implementation.
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
endedCountDownLatch.countDown();
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
sourceInfos.add(Pair.create(timeline, manifest));
sourceInfoCountDownLatch.countDown();
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
this.trackGroups = trackGroups;
}
@Override
public void onPlayerError(ExoPlaybackException exception) {
handleError(exception);
}
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public void onPositionDiscontinuity() {
positionDiscontinuityCount++;
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
}
private static final class TimelineWindowDefinition {
public final boolean isSeekable;
public final boolean isDynamic;
public final long durationUs;
public TimelineWindowDefinition(boolean isSeekable, boolean isDynamic, long durationUs) {
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.durationUs = durationUs;
}
}
private static final class FakeTimeline extends Timeline {
private final TimelineWindowDefinition[] windowDefinitions;
public FakeTimeline(TimelineWindowDefinition... windowDefinitions) {
this.windowDefinitions = windowDefinitions;
}
@Override
public int getWindowCount() {
return windowDefinitions.length;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
Object id = setIds ? windowIndex : null;
return window.set(id, C.TIME_UNSET, C.TIME_UNSET, windowDefinition.isSeekable,
windowDefinition.isDynamic, 0, windowDefinition.durationUs, windowIndex, windowIndex, 0);
}
@Override
public int getPeriodCount() {
return windowDefinitions.length;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
TimelineWindowDefinition windowDefinition = windowDefinitions[periodIndex];
Object id = setIds ? periodIndex : null;
return period.set(id, id, periodIndex, windowDefinition.durationUs, 0);
}
@Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Integer)) {
return C.INDEX_UNSET;
}
int index = (Integer) uid;
return index >= 0 && index < windowDefinitions.length ? index : C.INDEX_UNSET;
}
}
/**
* Fake {@link MediaSource} that provides a given timeline (which must have one period). Creating
* the period will return a {@link FakeMediaPeriod}.
*/
private static class FakeMediaSource implements MediaSource {
private final Timeline timeline;
private final Object manifest;
private final TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private boolean preparedSource;
private boolean releasedSource;
public FakeMediaSource(Timeline timeline, Object manifest, Format... formats) {
this.timeline = timeline;
this.manifest = manifest;
TrackGroup[] trackGroups = new TrackGroup[formats.length];
for (int i = 0; i < formats.length; i++) {
trackGroups[i] = new TrackGroup(formats[i]);
}
trackGroupArray = new TrackGroupArray(trackGroups);
activeMediaPeriods = new ArrayList<>();
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
assertFalse(preparedSource);
preparedSource = true;
listener.onSourceInfoRefreshed(timeline, manifest);
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
assertTrue(preparedSource);
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
Assertions.checkIndex(id.periodIndex, 0, timeline.getPeriodCount());
assertTrue(preparedSource);
assertFalse(releasedSource);
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
activeMediaPeriods.add(mediaPeriod);
return mediaPeriod;
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
assertTrue(preparedSource);
assertFalse(releasedSource);
FakeMediaPeriod fakeMediaPeriod = (FakeMediaPeriod) mediaPeriod;
assertTrue(activeMediaPeriods.remove(fakeMediaPeriod));
fakeMediaPeriod.release();
}
@Override
public void releaseSource() {
assertTrue(preparedSource);
assertFalse(releasedSource);
assertTrue(activeMediaPeriods.isEmpty());
releasedSource = true;
}
}
/**
* Fake {@link MediaPeriod} that provides one track with a given {@link Format}. Selecting that
* track will give the player a {@link FakeSampleStream}.
*/
private static final class FakeMediaPeriod implements MediaPeriod {
private final TrackGroupArray trackGroupArray;
private boolean preparedPeriod;
public FakeMediaPeriod(TrackGroupArray trackGroupArray) {
this.trackGroupArray = trackGroupArray;
}
public void release() {
preparedPeriod = false;
}
@Override
public void prepare(Callback callback, long positionUs) {
assertFalse(preparedPeriod);
assertEquals(0, positionUs);
preparedPeriod = true;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException {
assertTrue(preparedPeriod);
}
@Override
public TrackGroupArray getTrackGroups() {
assertTrue(preparedPeriod);
return trackGroupArray;
}
@Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
assertTrue(preparedPeriod);
int rendererCount = selections.length;
for (int i = 0; i < rendererCount; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
streams[i] = null;
}
}
for (int i = 0; i < rendererCount; i++) {
if (streams[i] == null && selections[i] != null) {
TrackSelection selection = selections[i];
assertEquals(1, selection.length());
assertEquals(0, selection.getIndexInTrackGroup(0));
TrackGroup trackGroup = selection.getTrackGroup();
assertTrue(trackGroupArray.indexOf(trackGroup) != C.INDEX_UNSET);
streams[i] = new FakeSampleStream(trackGroup.getFormat(0));
streamResetFlags[i] = true;
}
}
return 0;
}
@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}
@Override
public long readDiscontinuity() {
assertTrue(preparedPeriod);
return C.TIME_UNSET;
}
@Override
public long getBufferedPositionUs() {
assertTrue(preparedPeriod);
return C.TIME_END_OF_SOURCE;
}
@Override
public long seekToUs(long positionUs) {
assertTrue(preparedPeriod);
return positionUs;
}
@Override
public long getNextLoadPositionUs() {
assertTrue(preparedPeriod);
return C.TIME_END_OF_SOURCE;
}
@Override
public boolean continueLoading(long positionUs) {
assertTrue(preparedPeriod);
return false;
}
}
/**
* Fake {@link SampleStream} that outputs a given {@link Format} then sets the end of stream flag
* on its input buffer.
*/
private static final class FakeSampleStream implements SampleStream {
private final Format format;
private boolean readFormat;
public FakeSampleStream(Format format) {
this.format = format;
}
@Override
public boolean isReady() {
return true;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (formatRequired || !readFormat) {
formatHolder.format = format;
readFormat = true;
return C.RESULT_FORMAT_READ;
} else {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
}
@Override
public void maybeThrowError() throws IOException {
// Do nothing.
}
@Override
public void skipData(long positionUs) {
// Do nothing.
}
}
/**
* Fake {@link Renderer} that supports any format with the matching MIME type. The renderer
* verifies that it reads a given {@link Format}.
*/
private static class FakeRenderer extends BaseRenderer {
private final Format expectedFormat;
public int positionResetCount;
public int formatReadCount;
public int bufferReadCount;
public boolean isEnded;
public FakeRenderer(Format expectedFormat) {
super(expectedFormat == null ? C.TRACK_TYPE_UNKNOWN
: MimeTypes.getTrackType(expectedFormat.sampleMimeType));
this.expectedFormat = expectedFormat;
}
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
positionResetCount++;
isEnded = false;
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (isEnded) {
return;
}
// Verify the format matches the expected format.
FormatHolder formatHolder = new FormatHolder();
DecoderInputBuffer buffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_FORMAT_READ) {
formatReadCount++;
assertEquals(expectedFormat, formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
bufferReadCount++;
if (buffer.isEndOfStream()) {
isEnded = true;
}
}
}
@Override
public boolean isReady() {
return isSourceReady();
}
@Override
public boolean isEnded() {
return isEnded;
}
@Override
public int supportsFormat(Format format) throws ExoPlaybackException {
return getTrackType() == MimeTypes.getTrackType(format.sampleMimeType) ? FORMAT_HANDLED
: FORMAT_UNSUPPORTED_TYPE;
}
}
private abstract static class FakeMediaClockRenderer extends FakeRenderer implements MediaClock {
public FakeMediaClockRenderer(Format expectedFormat) {
super(expectedFormat);
}
@Override
public MediaClock getMediaClock() {
return this;
}
}
} }

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Pair;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import junit.framework.Assert;
/**
* Wraps a player with its own handler thread.
*/
public class ExoPlayerWrapper implements ExoPlayer.EventListener {
private final CountDownLatch sourceInfoCountDownLatch;
private final CountDownLatch endedCountDownLatch;
private final HandlerThread playerThread;
private final Handler handler;
private final LinkedList<Pair<Timeline, Object>> sourceInfos;
public ExoPlayer player;
public TrackGroupArray trackGroups;
public Exception exception;
// Written only on the main thread.
public volatile int positionDiscontinuityCount;
public ExoPlayerWrapper() {
sourceInfoCountDownLatch = new CountDownLatch(1);
endedCountDownLatch = new CountDownLatch(1);
playerThread = new HandlerThread("ExoPlayerTest thread");
playerThread.start();
handler = new Handler(playerThread.getLooper());
sourceInfos = new LinkedList<>();
}
// Called on the test thread.
public void blockUntilEnded(long timeoutMs) throws Exception {
if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
exception = new TimeoutException("Test playback timed out waiting for playback to end.");
}
release();
// Throw any pending exception (from playback, timing out or releasing).
if (exception != null) {
throw exception;
}
}
public void blockUntilSourceInfoRefreshed(long timeoutMs) throws Exception {
if (!sourceInfoCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("Test playback timed out waiting for source info.");
}
}
public void setup(final MediaSource mediaSource, final Renderer... renderers) {
handler.post(new Runnable() {
@Override
public void run() {
try {
player = ExoPlayerFactory.newInstance(renderers, new DefaultTrackSelector());
player.addListener(ExoPlayerWrapper.this);
player.setPlayWhenReady(true);
player.prepare(mediaSource);
} catch (Exception e) {
handleError(e);
}
}
});
}
public void prepare(final MediaSource mediaSource) {
handler.post(new Runnable() {
@Override
public void run() {
try {
player.prepare(mediaSource);
} catch (Exception e) {
handleError(e);
}
}
});
}
public void release() throws InterruptedException {
handler.post(new Runnable() {
@Override
public void run() {
try {
if (player != null) {
player.release();
}
} catch (Exception e) {
handleError(e);
} finally {
playerThread.quit();
}
}
});
playerThread.join();
}
private void handleError(Exception exception) {
if (this.exception == null) {
this.exception = exception;
}
endedCountDownLatch.countDown();
}
@SafeVarargs
public final void assertSourceInfosEquals(Pair<Timeline, Object>... sourceInfos) {
Assert.assertEquals(sourceInfos.length, this.sourceInfos.size());
for (Pair<Timeline, Object> sourceInfo : sourceInfos) {
Assert.assertEquals(sourceInfo, this.sourceInfos.remove());
}
}
// ExoPlayer.EventListener implementation.
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
endedCountDownLatch.countDown();
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
sourceInfos.add(Pair.create(timeline, manifest));
sourceInfoCountDownLatch.countDown();
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
this.trackGroups = trackGroups;
}
@Override
public void onPlayerError(ExoPlaybackException exception) {
handleError(exception);
}
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public void onPositionDiscontinuity() {
positionDiscontinuityCount++;
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.util.MediaClock;
/**
* Fake abstract {@link Renderer} which is also a {@link MediaClock}.
*/
public abstract class FakeMediaClockRenderer extends FakeRenderer implements MediaClock {
public FakeMediaClockRenderer(Format expectedFormat) {
super(expectedFormat);
}
@Override
public MediaClock getMediaClock() {
return this;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.io.IOException;
import junit.framework.Assert;
/**
* Fake {@link MediaPeriod} that provides one track with a given {@link Format}. Selecting that
* track will give the player a {@link FakeSampleStream}.
*/
public final class FakeMediaPeriod implements MediaPeriod {
private final TrackGroupArray trackGroupArray;
private boolean preparedPeriod;
public FakeMediaPeriod(TrackGroupArray trackGroupArray) {
this.trackGroupArray = trackGroupArray;
}
public void release() {
preparedPeriod = false;
}
@Override
public void prepare(Callback callback, long positionUs) {
Assert.assertFalse(preparedPeriod);
Assert.assertEquals(0, positionUs);
preparedPeriod = true;
callback.onPrepared(this);
}
@Override
public void maybeThrowPrepareError() throws IOException {
Assert.assertTrue(preparedPeriod);
}
@Override
public TrackGroupArray getTrackGroups() {
Assert.assertTrue(preparedPeriod);
return trackGroupArray;
}
@Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
Assert.assertTrue(preparedPeriod);
int rendererCount = selections.length;
for (int i = 0; i < rendererCount; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
streams[i] = null;
}
}
for (int i = 0; i < rendererCount; i++) {
if (streams[i] == null && selections[i] != null) {
TrackSelection selection = selections[i];
Assert.assertEquals(1, selection.length());
Assert.assertEquals(0, selection.getIndexInTrackGroup(0));
TrackGroup trackGroup = selection.getTrackGroup();
Assert.assertTrue(trackGroupArray.indexOf(trackGroup) != C.INDEX_UNSET);
streams[i] = new FakeSampleStream(trackGroup.getFormat(0));
streamResetFlags[i] = true;
}
}
return 0;
}
@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}
@Override
public long readDiscontinuity() {
Assert.assertTrue(preparedPeriod);
return C.TIME_UNSET;
}
@Override
public long getBufferedPositionUs() {
Assert.assertTrue(preparedPeriod);
return C.TIME_END_OF_SOURCE;
}
@Override
public long seekToUs(long positionUs) {
Assert.assertTrue(preparedPeriod);
return positionUs;
}
@Override
public long getNextLoadPositionUs() {
Assert.assertTrue(preparedPeriod);
return C.TIME_END_OF_SOURCE;
}
@Override
public boolean continueLoading(long positionUs) {
Assert.assertTrue(preparedPeriod);
return false;
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import junit.framework.Assert;
/**
* Fake {@link MediaSource} that provides a given timeline (which must have one period). Creating
* the period will return a {@link FakeMediaPeriod}.
*/
public class FakeMediaSource implements MediaSource {
private final Timeline timeline;
private final Object manifest;
private final TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private boolean preparedSource;
private boolean releasedSource;
public FakeMediaSource(Timeline timeline, Object manifest, Format... formats) {
this.timeline = timeline;
this.manifest = manifest;
TrackGroup[] trackGroups = new TrackGroup[formats.length];
for (int i = 0; i < formats.length; i++) {
trackGroups[i] = new TrackGroup(formats[i]);
}
trackGroupArray = new TrackGroupArray(trackGroups);
activeMediaPeriods = new ArrayList<>();
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assert.assertFalse(preparedSource);
preparedSource = true;
listener.onSourceInfoRefreshed(timeline, manifest);
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
Assert.assertTrue(preparedSource);
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
Assertions.checkIndex(id.periodIndex, 0, timeline.getPeriodCount());
Assert.assertTrue(preparedSource);
Assert.assertFalse(releasedSource);
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
activeMediaPeriods.add(mediaPeriod);
return mediaPeriod;
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
Assert.assertTrue(preparedSource);
Assert.assertFalse(releasedSource);
FakeMediaPeriod fakeMediaPeriod = (FakeMediaPeriod) mediaPeriod;
Assert.assertTrue(activeMediaPeriods.remove(fakeMediaPeriod));
fakeMediaPeriod.release();
}
@Override
public void releaseSource() {
Assert.assertTrue(preparedSource);
Assert.assertFalse(releasedSource);
Assert.assertTrue(activeMediaPeriods.isEmpty());
releasedSource = true;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.util.MimeTypes;
import junit.framework.Assert;
/**
* Fake {@link Renderer} that supports any format with the matching MIME type. The renderer
* verifies that it reads a given {@link Format}.
*/
public class FakeRenderer extends BaseRenderer {
private final Format expectedFormat;
public int positionResetCount;
public int formatReadCount;
public int bufferReadCount;
public boolean isEnded;
public FakeRenderer(Format expectedFormat) {
super(expectedFormat == null ? C.TRACK_TYPE_UNKNOWN
: MimeTypes.getTrackType(expectedFormat.sampleMimeType));
this.expectedFormat = expectedFormat;
}
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
positionResetCount++;
isEnded = false;
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (isEnded) {
return;
}
// Verify the format matches the expected format.
FormatHolder formatHolder = new FormatHolder();
DecoderInputBuffer buffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_FORMAT_READ) {
formatReadCount++;
Assert.assertEquals(expectedFormat, formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
bufferReadCount++;
if (buffer.isEndOfStream()) {
isEnded = true;
}
}
}
@Override
public boolean isReady() {
return isSourceReady();
}
@Override
public boolean isEnded() {
return isEnded;
}
@Override
public int supportsFormat(Format format) throws ExoPlaybackException {
return getTrackType() == MimeTypes.getTrackType(format.sampleMimeType) ? FORMAT_HANDLED
: FORMAT_UNSUPPORTED_TYPE;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream;
import java.io.IOException;
/**
* Fake {@link SampleStream} that outputs a given {@link Format} then sets the end of stream flag
* on its input buffer.
*/
public final class FakeSampleStream implements SampleStream {
private final Format format;
private boolean readFormat;
public FakeSampleStream(Format format) {
this.format = format;
}
@Override
public boolean isReady() {
return true;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (formatRequired || !readFormat) {
formatHolder.format = format;
readFormat = true;
return C.RESULT_FORMAT_READ;
} else {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
}
@Override
public void maybeThrowError() throws IOException {
// Do nothing.
}
@Override
public void skipData(long positionUs) {
// Do nothing.
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
/**
* Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s.
*/
public final class FakeTimeline extends Timeline {
/**
* Definition used to define a {@link FakeTimeline}.
*/
public static final class TimelineWindowDefinition {
public final boolean isSeekable;
public final boolean isDynamic;
public final long durationUs;
public TimelineWindowDefinition(boolean isSeekable, boolean isDynamic, long durationUs) {
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.durationUs = durationUs;
}
}
private final TimelineWindowDefinition[] windowDefinitions;
public FakeTimeline(TimelineWindowDefinition... windowDefinitions) {
this.windowDefinitions = windowDefinitions;
}
@Override
public int getWindowCount() {
return windowDefinitions.length;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
Object id = setIds ? windowIndex : null;
return window.set(id, C.TIME_UNSET, C.TIME_UNSET, windowDefinition.isSeekable,
windowDefinition.isDynamic, 0, windowDefinition.durationUs, windowIndex, windowIndex, 0);
}
@Override
public int getPeriodCount() {
return windowDefinitions.length;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
TimelineWindowDefinition windowDefinition = windowDefinitions[periodIndex];
Object id = setIds ? periodIndex : null;
return period.set(id, id, periodIndex, windowDefinition.durationUs, 0);
}
@Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Integer)) {
return C.INDEX_UNSET;
}
int index = (Integer) uid;
return index >= 0 && index < windowDefinitions.length ? index : C.INDEX_UNSET;
}
}