Skip to content

Commit

Permalink
Add a player test for renderers reading ahead.
Browse files Browse the repository at this point in the history
Also move timeline, manifest and format verifications into the test* methods.

Issue: #2252

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=143761969
  • Loading branch information
andrewlewis authored and ojw28 committed Jan 6, 2017
1 parent 9d48d4e commit b2a153d
Showing 1 changed file with 142 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -51,34 +52,46 @@ public final class ExoPlayerTest extends TestCase {
private static final Format TEST_VIDEO_FORMAT = Format.createVideoSampleFormat(null,
MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE,
null, null);
private static final Format TEST_AUDIO_FORMAT = Format.createAudioSampleFormat(null,
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);

/**
* Tests playback of a source that exposes an empty timeline. Playback is expected to end without
* error.
*/
public void testPlayEmptyTimeline() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper();
playerWrapper.setup(Timeline.EMPTY, null, null);
Timeline timeline = Timeline.EMPTY;
MediaSource mediaSource = new FakeMediaSource(timeline, null);
FakeRenderer renderer = new FakeRenderer(null);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
assertEquals(0, playerWrapper.videoRenderer.formatReadCount);
assertEquals(0, playerWrapper.videoRenderer.bufferReadCount);
assertFalse(playerWrapper.videoRenderer.isEnded);
assertEquals(0, renderer.formatReadCount);
assertEquals(0, renderer.bufferReadCount);
assertFalse(renderer.isEnded);
assertEquals(timeline, playerWrapper.timeline);
assertNull(playerWrapper.manifest);
}

/**
* Tests playback of a source that exposes a single period.
*/
public void testPlaySinglePeriodTimeline() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 0));
playerWrapper.setup(timeline, null, TEST_VIDEO_FORMAT);
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
Object manifest = new Object();
MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
assertEquals(1, playerWrapper.videoRenderer.formatReadCount);
assertEquals(1, playerWrapper.videoRenderer.bufferReadCount);
assertTrue(playerWrapper.videoRenderer.isEnded);
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(timeline, playerWrapper.timeline);
assertEquals(manifest, playerWrapper.manifest);
assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups);
}

/**
Expand All @@ -90,12 +103,58 @@ public void testPlayMultiPeriodTimeline() throws Exception {
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0));
playerWrapper.setup(timeline, null, TEST_VIDEO_FORMAT);
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
assertEquals(3, playerWrapper.videoRenderer.formatReadCount);
assertEquals(1, playerWrapper.videoRenderer.bufferReadCount);
assertTrue(playerWrapper.videoRenderer.isEnded);
assertEquals(3, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(timeline, playerWrapper.timeline);
assertNull(playerWrapper.manifest);
}

/**
* Tests that the player does not unnecessarily reset renderers when playing a multi-period
* source.
*/
public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
final PlayerWrapper playerWrapper = new PlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10));
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT,
TEST_AUDIO_FORMAT);

FakeRenderer videoRenderer = new FakeRenderer(TEST_VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(TEST_AUDIO_FORMAT) {

@Override
public long getPositionUs() {
// Simulate the playback position lagging behind the reading position: the renderer media
// clock position will be the start of the timeline until the stream is set to be final, at
// which point it jumps to the end of the timeline allowing the playing period to advance.
// TODO: Avoid hard-coding ExoPlayerImplInternal.RENDERER_TIMESTAMP_OFFSET_US.
return isCurrentStreamFinal() ? 60000030 : 60000000;
}

@Override
public boolean isEnded() {
// Allow playback to end once the final period is playing.
return playerWrapper.positionDiscontinuityCount == 2;
}

};
playerWrapper.setup(mediaSource, videoRenderer, audioRenderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
assertEquals(1, audioRenderer.positionResetCount);
assertTrue(videoRenderer.isEnded);
assertTrue(audioRenderer.isEnded);
assertEquals(timeline, playerWrapper.timeline);
assertNull(playerWrapper.manifest);
}

/**
Expand All @@ -107,14 +166,14 @@ private static final class PlayerWrapper implements ExoPlayer.EventListener {
private final HandlerThread playerThread;
private final Handler handler;

private Timeline expectedTimeline;
private Object expectedManifest;
private Format expectedFormat;
private FakeVideoRenderer videoRenderer;
private ExoPlayer player;
private Timeline timeline;
private Object manifest;
private TrackGroupArray trackGroups;
private Exception exception;

private int positionDiscontinuityCount;
// Written only on the main thread.
private volatile int positionDiscontinuityCount;

public PlayerWrapper() {
endedCountDownLatch = new CountDownLatch(1);
Expand All @@ -136,20 +195,15 @@ public void blockUntilEnded(long timeoutMs) throws Exception {
}
}

public void setup(final Timeline timeline, final Object manifest, final Format format) {
expectedTimeline = timeline;
expectedManifest = manifest;
expectedFormat = format;
public void setup(final MediaSource mediaSource, final Renderer... renderers) {
handler.post(new Runnable() {
@Override
public void run() {
try {
videoRenderer = new FakeVideoRenderer(expectedFormat);
player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer},
new DefaultTrackSelector());
player = ExoPlayerFactory.newInstance(renderers, new DefaultTrackSelector());
player.addListener(PlayerWrapper.this);
player.setPlayWhenReady(true);
player.prepare(new FakeMediaSource(timeline, manifest, format));
player.prepare(mediaSource);
} catch (Exception e) {
handleError(e);
}
Expand Down Expand Up @@ -198,21 +252,21 @@ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {

@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
assertEquals(expectedTimeline, timeline);
assertEquals(expectedManifest, manifest);
this.timeline = timeline;
this.manifest = manifest;
}

@Override
public void onTracksChanged(TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
assertEquals(new TrackGroupArray(new TrackGroup(expectedFormat)), trackGroups);
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++;
Expand Down Expand Up @@ -287,16 +341,20 @@ private static final class FakeMediaSource implements MediaSource {

private final Timeline timeline;
private final Object manifest;
private final Format format;
private final TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;

private boolean preparedSource;
private boolean releasedSource;

public FakeMediaSource(Timeline timeline, Object manifest, Format format) {
public FakeMediaSource(Timeline timeline, Object manifest, Format... formats) {
this.timeline = timeline;
this.manifest = manifest;
this.format = format;
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<>();
}

Expand All @@ -318,7 +376,7 @@ public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs)
assertTrue(preparedSource);
assertFalse(releasedSource);
assertEquals(0, positionUs);
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(format);
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
activeMediaPeriods.add(mediaPeriod);
return mediaPeriod;
}
Expand All @@ -327,8 +385,9 @@ public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs)
public void releasePeriod(MediaPeriod mediaPeriod) {
assertTrue(preparedSource);
assertFalse(releasedSource);
assertTrue(activeMediaPeriods.remove(mediaPeriod));
((FakeMediaPeriod) mediaPeriod).release();
FakeMediaPeriod fakeMediaPeriod = (FakeMediaPeriod) mediaPeriod;
assertTrue(activeMediaPeriods.remove(fakeMediaPeriod));
fakeMediaPeriod.release();
}

@Override
Expand All @@ -347,12 +406,12 @@ public void releaseSource() {
*/
private static final class FakeMediaPeriod implements MediaPeriod {

private final TrackGroup trackGroup;
private final TrackGroupArray trackGroupArray;

private boolean preparedPeriod;

public FakeMediaPeriod(Format format) {
trackGroup = new TrackGroup(format);
public FakeMediaPeriod(TrackGroupArray trackGroupArray) {
this.trackGroupArray = trackGroupArray;
}

public void release() {
Expand All @@ -374,26 +433,29 @@ public void maybeThrowPrepareError() throws IOException {
@Override
public TrackGroupArray getTrackGroups() {
assertTrue(preparedPeriod);
return new TrackGroupArray(trackGroup);
return trackGroupArray;
}

@Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
assertTrue(preparedPeriod);
assertEquals(1, selections.length);
assertEquals(1, mayRetainStreamFlags.length);
assertEquals(1, streams.length);
assertEquals(1, streamResetFlags.length);
assertEquals(0, positionUs);
if (streams[0] != null && (selections[0] == null || !mayRetainStreamFlags[0])) {
streams[0] = null;
int rendererCount = selections.length;
for (int i = 0; i < rendererCount; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
streams[i] = null;
}
}
if (streams[0] == null && selections[0] != null) {
FakeSampleStream stream = new FakeSampleStream(trackGroup.getFormat(0));
assertEquals(trackGroup, selections[0].getTrackGroup());
streams[0] = stream;
streamResetFlags[0] = true;
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;
}
Expand Down Expand Up @@ -474,24 +536,27 @@ public void skipToKeyframeBefore(long timeUs) {
}

/**
* Fake {@link Renderer} that supports any video format. The renderer verifies that it reads a
* given {@link Format}.
* Fake {@link Renderer} that supports any format with the matching MIME type. The renderer
* verifies that it reads a given {@link Format}.
*/
private static final class FakeVideoRenderer extends BaseRenderer {
private static class FakeRenderer extends BaseRenderer {

private final Format expectedFormat;

private int formatReadCount;
private int bufferReadCount;
private boolean isEnded;
public int positionResetCount;
public int formatReadCount;
public int bufferReadCount;
public boolean isEnded;

public FakeVideoRenderer(Format expectedFormat) {
super(C.TRACK_TYPE_VIDEO);
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;
}

Expand Down Expand Up @@ -529,7 +594,21 @@ public boolean isEnded() {

@Override
public int supportsFormat(Format format) throws ExoPlaybackException {
return MimeTypes.isVideo(format.sampleMimeType) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
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;
}

}
Expand Down

0 comments on commit b2a153d

Please sign in to comment.