Commit c7c9a1e9 by tonihei Committed by Oliver Woodman

Send media source events for fake media sources.

This allows to test sending events when using fake media sources.
The FakeMediaSource now simulates a manifest load when being prepared.
And the FakeMediaPeriod simulates a media load when being prepared.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=189205285
parent bb72d9eb
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
......@@ -573,8 +574,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setSeekToUsOffset(10);
return mediaPeriod;
}
......@@ -604,8 +608,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setDiscontinuityPositionUs(10);
return mediaPeriod;
}
......@@ -626,8 +633,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray);
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setDiscontinuityPositionUs(0);
return mediaPeriod;
}
......@@ -878,10 +888,13 @@ public final class ExoPlayerTest {
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), null, Builder.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) {
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
// Defer completing preparation of the period until playback parameters have been set.
fakeMediaPeriodHolder[0] =
new FakeMediaPeriod(trackGroupArray, /* deferOnPrepared= */ true);
new FakeMediaPeriod(trackGroupArray, eventDispatcher, /* deferOnPrepared= */ true);
createPeriodCalledCountDownLatch.countDown();
return fakeMediaPeriodHolder[0];
}
......
......@@ -92,7 +92,7 @@ public class SimpleDecoderAudioRendererTest {
audioRenderer.enable(
RendererConfiguration.DEFAULT,
new Format[] {FORMAT},
new FakeSampleStream(FORMAT, false),
new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false),
0,
false,
0);
......
......@@ -35,7 +35,6 @@ import java.util.List;
public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
implements SequenceableLoader.Callback<ChunkSampleStream<FakeChunkSource>> {
private final EventDispatcher eventDispatcher;
private final Allocator allocator;
private final FakeChunkSource.Factory chunkSourceFactory;
private final long durationUs;
......@@ -50,8 +49,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
Allocator allocator,
FakeChunkSource.Factory chunkSourceFactory,
long durationUs) {
super(trackGroupArray);
this.eventDispatcher = eventDispatcher;
super(trackGroupArray, eventDispatcher);
this.allocator = allocator;
this.chunkSourceFactory = chunkSourceFactory;
this.durationUs = durationUs;
......
......@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.upstream.Allocator;
......@@ -44,10 +45,12 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource {
}
@Override
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray,
Allocator allocator) {
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
Period period = timeline.getPeriod(id.periodIndex, new Period());
MediaSourceEventListener.EventDispatcher eventDispatcher = createEventDispatcher(id);
return new FakeAdaptiveMediaPeriod(
trackGroupArray, eventDispatcher, allocator, chunkSourceFactory, period.durationUs);
}
......
......@@ -17,24 +17,32 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
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 com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException;
/**
* Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting
* tracks will give the player {@link FakeSampleStream}s.
* tracks will give the player {@link FakeSampleStream}s. Loading data completes immediately after
* the period has finished preparing.
*/
public class FakeMediaPeriod implements MediaPeriod {
public static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://fake.uri"));
private final TrackGroupArray trackGroupArray;
protected final EventDispatcher eventDispatcher;
@Nullable private Handler playerHandler;
@Nullable private Callback prepareCallback;
......@@ -46,19 +54,23 @@ public class FakeMediaPeriod implements MediaPeriod {
/**
* @param trackGroupArray The track group array.
* @param eventDispatcher A dispatcher for media source events.
*/
public FakeMediaPeriod(TrackGroupArray trackGroupArray) {
this(trackGroupArray, false);
public FakeMediaPeriod(TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher) {
this(trackGroupArray, eventDispatcher, /* deferOnPrepared */ false);
}
/**
* @param trackGroupArray The track group array.
* @param eventDispatcher A dispatcher for media source events.
* @param deferOnPrepared Whether {@link MediaPeriod.Callback#onPrepared(MediaPeriod)} should be
* called only after {@link #setPreparationComplete()} has been called. If {@code false}
* preparation completes immediately.
*/
public FakeMediaPeriod(TrackGroupArray trackGroupArray, boolean deferOnPrepared) {
public FakeMediaPeriod(
TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher, boolean deferOnPrepared) {
this.trackGroupArray = trackGroupArray;
this.eventDispatcher = eventDispatcher;
this.deferOnPrepared = deferOnPrepared;
discontinuityPositionUs = C.TIME_UNSET;
}
......@@ -79,13 +91,13 @@ public class FakeMediaPeriod implements MediaPeriod {
public synchronized void setPreparationComplete() {
deferOnPrepared = false;
if (playerHandler != null && prepareCallback != null) {
playerHandler.post(new Runnable() {
@Override
public void run() {
prepared = true;
prepareCallback.onPrepared(FakeMediaPeriod.this);
}
});
playerHandler.post(
new Runnable() {
@Override
public void run() {
finishPreparation();
}
});
}
}
......@@ -104,12 +116,21 @@ public class FakeMediaPeriod implements MediaPeriod {
@Override
public synchronized void prepare(Callback callback, long positionUs) {
eventDispatcher.loadStarted(
FAKE_DATA_SPEC,
C.DATA_TYPE_MEDIA,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeUs= */ 0,
/* mediaEndTimeUs = */ C.TIME_UNSET,
SystemClock.elapsedRealtime());
prepareCallback = callback;
if (deferOnPrepared) {
playerHandler = new Handler();
prepareCallback = callback;
} else {
prepared = true;
callback.onPrepared(this);
finishPreparation();
}
}
......@@ -196,7 +217,24 @@ public class FakeMediaPeriod implements MediaPeriod {
}
protected SampleStream createSampleStream(TrackSelection selection) {
return new FakeSampleStream(selection.getSelectedFormat());
return new FakeSampleStream(
selection.getSelectedFormat(), eventDispatcher, /* shouldOutputSample= */ true);
}
private void finishPreparation() {
prepared = true;
prepareCallback.onPrepared(this);
eventDispatcher.loadCompleted(
FAKE_DATA_SPEC,
C.DATA_TYPE_MEDIA,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeUs= */ 0,
/* mediaEndTimeUs = */ C.TIME_UNSET,
SystemClock.elapsedRealtime(),
/* loadDurationMs= */ 0,
/* bytesLoaded= */ 100);
}
}
......@@ -17,17 +17,25 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
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.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
......@@ -39,6 +47,9 @@ import java.util.List;
*/
public class FakeMediaSource extends BaseMediaSource {
private static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://manifest.uri"));
private static final int MANIFEST_LOAD_BYTES = 100;
private final TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private final ArrayList<MediaPeriodId> createdMediaPeriods;
......@@ -81,7 +92,7 @@ public class FakeMediaSource extends BaseMediaSource {
releasedSource = false;
sourceInfoRefreshHandler = new Handler();
if (timeline != null) {
refreshSourceInfo(timeline, manifest);
finishSourcePreparation();
}
}
......@@ -95,7 +106,11 @@ public class FakeMediaSource extends BaseMediaSource {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
Assertions.checkIndex(id.periodIndex, 0, timeline.getPeriodCount());
FakeMediaPeriod mediaPeriod = createFakeMediaPeriod(id, trackGroupArray, allocator);
Period period = timeline.getPeriod(id.periodIndex, new Period());
EventDispatcher eventDispatcher =
createEventDispatcher(period.windowIndex, id, period.getPositionInWindowMs());
FakeMediaPeriod mediaPeriod =
createFakeMediaPeriod(id, trackGroupArray, allocator, eventDispatcher);
activeMediaPeriods.add(mediaPeriod);
createdMediaPeriods.add(id);
return mediaPeriod;
......@@ -135,7 +150,7 @@ public class FakeMediaSource extends BaseMediaSource {
assertThat(preparedSource).isTrue();
timeline = newTimeline;
manifest = newManifest;
refreshSourceInfo(timeline, manifest);
finishSourcePreparation();
}
});
} else {
......@@ -163,9 +178,51 @@ public class FakeMediaSource extends BaseMediaSource {
return createdMediaPeriods;
}
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray,
Allocator allocator) {
return new FakeMediaPeriod(trackGroupArray);
/**
* Creates a {@link FakeMediaPeriod} for this media source.
*
* @param id The identifier of the period.
* @param trackGroupArray The {@link TrackGroupArray} supported by the media period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param eventDispatcher An {@link EventDispatcher} to dispatch media source events.
* @return A new {@link FakeMediaPeriod}.
*/
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
return new FakeMediaPeriod(trackGroupArray, eventDispatcher);
}
private void finishSourcePreparation() {
refreshSourceInfo(timeline, manifest);
if (!timeline.isEmpty()) {
MediaLoadData mediaLoadData =
new MediaLoadData(
/* windowIndex= */ 0,
/* mediaPeriodId= */ null,
C.DATA_TYPE_MANIFEST,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeMs= */ C.TIME_UNSET,
/* mediaEndTimeMs = */ C.TIME_UNSET);
long elapsedRealTimeMs = SystemClock.elapsedRealtime();
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
eventDispatcher.loadStarted(
new LoadEventInfo(
FAKE_DATA_SPEC, elapsedRealTimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0),
mediaLoadData);
eventDispatcher.loadCompleted(
new LoadEventInfo(
FAKE_DATA_SPEC,
elapsedRealTimeMs,
/* loadDurationMs= */ 0,
/* bytesLoaded= */ MANIFEST_LOAD_BYTES),
mediaLoadData);
}
}
private static TrackGroupArray buildTrackGroupArray(Format... formats) {
......
......@@ -15,10 +15,12 @@
*/
package com.google.android.exoplayer2.testutil;
import android.support.annotation.Nullable;
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.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleStream;
import java.io.IOException;
......@@ -29,16 +31,23 @@ import java.io.IOException;
public final class FakeSampleStream implements SampleStream {
private final Format format;
private final @Nullable EventDispatcher eventDispatcher;
private boolean readFormat;
private boolean readSample;
public FakeSampleStream(Format format) {
this(format, true);
}
public FakeSampleStream(Format format, boolean shouldOutputSample) {
/**
* Creates fake sample stream which outputs the given {@link Format}, optionally one sample with
* zero bytes, then end of stream.
*
* @param format The {@link Format} to output.
* @param eventDispatcher An {@link EventDispatcher} to notify of read events.
* @param shouldOutputSample Whether the sample stream should output a sample.
*/
public FakeSampleStream(
Format format, @Nullable EventDispatcher eventDispatcher, boolean shouldOutputSample) {
this.format = format;
this.eventDispatcher = eventDispatcher;
readSample = !shouldOutputSample;
}
......@@ -60,6 +69,14 @@ public final class FakeSampleStream implements SampleStream {
buffer.data.put((byte) 0);
buffer.flip();
readSample = true;
if (eventDispatcher != null) {
eventDispatcher.downstreamFormatChanged(
C.TRACK_TYPE_UNKNOWN,
format,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaTimeUs= */ 0);
}
return C.RESULT_BUFFER_READ;
} else {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import android.os.ConditionVariable;
......@@ -29,10 +30,18 @@ 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.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
......@@ -50,6 +59,7 @@ public class MediaSourceTestRunner {
private final Allocator allocator;
private final LinkedBlockingDeque<Timeline> timelines;
private final CopyOnWriteArraySet<MediaLoadData> completedLoads;
private Timeline timeline;
/**
......@@ -66,6 +76,8 @@ public class MediaSourceTestRunner {
player = new EventHandlingExoPlayer(playbackLooper);
mediaSourceListener = new MediaSourceListener();
timelines = new LinkedBlockingDeque<>();
completedLoads = new CopyOnWriteArraySet<>();
mediaSource.addEventListener(playbackHandler, mediaSourceListener);
}
/**
......@@ -280,18 +292,99 @@ public class MediaSourceTestRunner {
releasePeriod(secondMediaPeriod);
}
/**
* Asserts that the media source reported completed loads via {@link
* MediaSourceEventListener#onLoadCompleted(LoadEventInfo, MediaLoadData)} for each specified
* window index and a null period id. Also asserts that no other loads with media period id null
* are reported.
*/
public void assertCompletedManifestLoads(Integer... windowIndices) {
List<Integer> expectedWindowIndices = new ArrayList<>(Arrays.asList(windowIndices));
for (MediaLoadData mediaLoadData : completedLoads) {
if (mediaLoadData.mediaPeriodId == null) {
boolean loadExpected = expectedWindowIndices.remove((Integer) mediaLoadData.windowIndex);
assertThat(loadExpected).isTrue();
}
}
assertWithMessage("Not all expected media source loads have been completed.")
.that(expectedWindowIndices)
.isEmpty();
}
/**
* Asserts that the media source reported completed loads via {@link
* MediaSourceEventListener#onLoadCompleted(LoadEventInfo, MediaLoadData)} for each specified
* media period id, and asserts that the associated window index matches the one in the last known
* timeline returned from {@link #prepareSource()}, {@link #assertTimelineChange()} or {@link
* #assertTimelineChangeBlocking()}.
*/
public void assertCompletedMediaPeriodLoads(MediaPeriodId... mediaPeriodIds) {
Timeline.Period period = new Timeline.Period();
HashSet<MediaPeriodId> expectedLoads = new HashSet<>(Arrays.asList(mediaPeriodIds));
for (MediaLoadData mediaLoadData : completedLoads) {
if (expectedLoads.remove(mediaLoadData.mediaPeriodId)) {
assertThat(mediaLoadData.windowIndex)
.isEqualTo(
timeline.getPeriod(mediaLoadData.mediaPeriodId.periodIndex, period).windowIndex);
}
}
assertWithMessage("Not all expected media source loads have been completed.")
.that(expectedLoads)
.isEmpty();
}
/** Releases the runner. Should be called when the runner is no longer required. */
public void release() {
playbackThread.quit();
}
private class MediaSourceListener implements MediaSource.SourceInfoRefreshListener {
private class MediaSourceListener
implements MediaSource.SourceInfoRefreshListener, MediaSourceEventListener {
// SourceInfoRefreshListener methods.
@Override
public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
timelines.addLast(timeline);
}
// MediaSourceEventListener methods.
@Override
public void onLoadStarted(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
@Override
public void onLoadCompleted(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
completedLoads.add(mediaLoadData);
}
@Override
public void onLoadCanceled(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
@Override
public void onLoadError(
LoadEventInfo loadEventInfo,
MediaLoadData mediaLoadData,
IOException error,
boolean wasCanceled) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
@Override
public void onUpstreamDiscarded(MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
@Override
public void onDownstreamFormatChanged(MediaLoadData mediaLoadData) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
}
}
private static class EventHandlingExoPlayer extends StubExoPlayer
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment