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
This commit is contained in:
tonihei 2018-03-15 10:20:46 -07:00 committed by Oliver Woodman
parent bb72d9eb6a
commit c7c9a1e9e4
9 changed files with 441 additions and 176 deletions

View File

@ -295,21 +295,16 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadStarted(LoadEventInfo, MediaLoadData)}. */ /** Dispatches {@link #onLoadStarted(LoadEventInfo, MediaLoadData)}. */
public void loadStarted( public void loadStarted(
final DataSpec dataSpec, DataSpec dataSpec,
final int dataType, int dataType,
final int trackType, int trackType,
final @Nullable Format trackFormat, @Nullable Format trackFormat,
final int trackSelectionReason, int trackSelectionReason,
final @Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
final long mediaStartTimeUs, long mediaStartTimeUs,
final long mediaEndTimeUs, long mediaEndTimeUs,
final long elapsedRealtimeMs) { long elapsedRealtimeMs) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) { loadStarted(
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onLoadStarted(
new LoadEventInfo( new LoadEventInfo(
dataSpec, elapsedRealtimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0), dataSpec, elapsedRealtimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0),
new MediaLoadData( new MediaLoadData(
@ -323,6 +318,18 @@ public interface MediaSourceEventListener {
adjustMediaTime(mediaStartTimeUs), adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs))); adjustMediaTime(mediaEndTimeUs)));
} }
/** Dispatches {@link #onLoadStarted(LoadEventInfo, MediaLoadData)}. */
public void loadStarted(final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onLoadStarted(loadEventInfo, mediaLoadData);
}
}); });
} }
} }
@ -350,23 +357,18 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadCompleted(LoadEventInfo, MediaLoadData)}. */ /** Dispatches {@link #onLoadCompleted(LoadEventInfo, MediaLoadData)}. */
public void loadCompleted( public void loadCompleted(
final DataSpec dataSpec, DataSpec dataSpec,
final int dataType, int dataType,
final int trackType, int trackType,
final @Nullable Format trackFormat, @Nullable Format trackFormat,
final int trackSelectionReason, int trackSelectionReason,
final @Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
final long mediaStartTimeUs, long mediaStartTimeUs,
final long mediaEndTimeUs, long mediaEndTimeUs,
final long elapsedRealtimeMs, long elapsedRealtimeMs,
final long loadDurationMs, long loadDurationMs,
final long bytesLoaded) { long bytesLoaded) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) { loadCompleted(
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onLoadCompleted(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData( new MediaLoadData(
windowIndex, windowIndex,
@ -379,6 +381,19 @@ public interface MediaSourceEventListener {
adjustMediaTime(mediaStartTimeUs), adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs))); adjustMediaTime(mediaEndTimeUs)));
} }
/** Dispatches {@link #onLoadCompleted(LoadEventInfo, MediaLoadData)}. */
public void loadCompleted(
final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onLoadCompleted(loadEventInfo, mediaLoadData);
}
}); });
} }
} }
@ -406,23 +421,18 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadCanceled(LoadEventInfo, MediaLoadData)}. */ /** Dispatches {@link #onLoadCanceled(LoadEventInfo, MediaLoadData)}. */
public void loadCanceled( public void loadCanceled(
final DataSpec dataSpec, DataSpec dataSpec,
final int dataType, int dataType,
final int trackType, int trackType,
final @Nullable Format trackFormat, @Nullable Format trackFormat,
final int trackSelectionReason, int trackSelectionReason,
final @Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
final long mediaStartTimeUs, long mediaStartTimeUs,
final long mediaEndTimeUs, long mediaEndTimeUs,
final long elapsedRealtimeMs, long elapsedRealtimeMs,
final long loadDurationMs, long loadDurationMs,
final long bytesLoaded) { long bytesLoaded) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) { loadCanceled(
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onLoadCanceled(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData( new MediaLoadData(
windowIndex, windowIndex,
@ -435,6 +445,18 @@ public interface MediaSourceEventListener {
adjustMediaTime(mediaStartTimeUs), adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs))); adjustMediaTime(mediaEndTimeUs)));
} }
/** Dispatches {@link #onLoadCanceled(LoadEventInfo, MediaLoadData)}. */
public void loadCanceled(final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onLoadCanceled(loadEventInfo, mediaLoadData);
}
}); });
} }
} }
@ -466,25 +488,20 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadError(LoadEventInfo, MediaLoadData, IOException, boolean)}. */ /** Dispatches {@link #onLoadError(LoadEventInfo, MediaLoadData, IOException, boolean)}. */
public void loadError( public void loadError(
final DataSpec dataSpec, DataSpec dataSpec,
final int dataType, int dataType,
final int trackType, int trackType,
final @Nullable Format trackFormat, @Nullable Format trackFormat,
final int trackSelectionReason, int trackSelectionReason,
final @Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
final long mediaStartTimeUs, long mediaStartTimeUs,
final long mediaEndTimeUs, long mediaEndTimeUs,
final long elapsedRealtimeMs, long elapsedRealtimeMs,
final long loadDurationMs, long loadDurationMs,
final long bytesLoaded, long bytesLoaded,
final IOException error, IOException error,
final boolean wasCanceled) { boolean wasCanceled) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) { loadError(
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onLoadError(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded), new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData( new MediaLoadData(
windowIndex, windowIndex,
@ -499,19 +516,29 @@ public interface MediaSourceEventListener {
error, error,
wasCanceled); wasCanceled);
} }
/** Dispatches {@link #onLoadError(LoadEventInfo, MediaLoadData, IOException, boolean)}. */
public void loadError(
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData,
final IOException error,
final boolean wasCanceled) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onLoadError(loadEventInfo, mediaLoadData, error, wasCanceled);
}
}); });
} }
} }
/** Dispatches {@link #onUpstreamDiscarded(MediaLoadData)}. */ /** Dispatches {@link #onUpstreamDiscarded(MediaLoadData)}. */
public void upstreamDiscarded( public void upstreamDiscarded(int trackType, long mediaStartTimeUs, long mediaEndTimeUs) {
final int trackType, final long mediaStartTimeUs, final long mediaEndTimeUs) { upstreamDiscarded(
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onUpstreamDiscarded(
new MediaLoadData( new MediaLoadData(
windowIndex, windowIndex,
mediaPeriodId, mediaPeriodId,
@ -523,23 +550,30 @@ public interface MediaSourceEventListener {
adjustMediaTime(mediaStartTimeUs), adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs))); adjustMediaTime(mediaEndTimeUs)));
} }
/** Dispatches {@link #onUpstreamDiscarded(MediaLoadData)}. */
public void upstreamDiscarded(final MediaLoadData mediaLoadData) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onUpstreamDiscarded(mediaLoadData);
}
}); });
} }
} }
/** Dispatches {@link #onDownstreamFormatChanged(MediaLoadData)}. */ /** Dispatches {@link #onDownstreamFormatChanged(MediaLoadData)}. */
public void downstreamFormatChanged( public void downstreamFormatChanged(
final int trackType, int trackType,
final @Nullable Format trackFormat, @Nullable Format trackFormat,
final int trackSelectionReason, int trackSelectionReason,
final @Nullable Object trackSelectionData, @Nullable Object trackSelectionData,
final long mediaTimeUs) { long mediaTimeUs) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) { downstreamFormatChanged(
listenerAndHandler.handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onDownstreamFormatChanged(
new MediaLoadData( new MediaLoadData(
windowIndex, windowIndex,
mediaPeriodId, mediaPeriodId,
@ -551,6 +585,18 @@ public interface MediaSourceEventListener {
adjustMediaTime(mediaTimeUs), adjustMediaTime(mediaTimeUs),
/* mediaEndTimeMs= */ C.TIME_UNSET)); /* mediaEndTimeMs= */ C.TIME_UNSET));
} }
/** Dispatches {@link #onDownstreamFormatChanged(MediaLoadData)}. */
public void downstreamFormatChanged(final MediaLoadData mediaLoadData) {
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listener.onDownstreamFormatChanged(mediaLoadData);
}
}); });
} }
} }

View File

@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState;
@ -573,8 +574,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod( protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id,
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setSeekToUsOffset(10); mediaPeriod.setSeekToUsOffset(10);
return mediaPeriod; return mediaPeriod;
} }
@ -604,8 +608,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod( protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id,
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setDiscontinuityPositionUs(10); mediaPeriod.setDiscontinuityPositionUs(10);
return mediaPeriod; return mediaPeriod;
} }
@ -626,8 +633,11 @@ public final class ExoPlayerTest {
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) { new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod( protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) { MediaPeriodId id,
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray); TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
mediaPeriod.setDiscontinuityPositionUs(0); mediaPeriod.setDiscontinuityPositionUs(0);
return mediaPeriod; return mediaPeriod;
} }
@ -878,10 +888,13 @@ public final class ExoPlayerTest {
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), null, Builder.VIDEO_FORMAT) { new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), null, Builder.VIDEO_FORMAT) {
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod( 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. // Defer completing preparation of the period until playback parameters have been set.
fakeMediaPeriodHolder[0] = fakeMediaPeriodHolder[0] =
new FakeMediaPeriod(trackGroupArray, /* deferOnPrepared= */ true); new FakeMediaPeriod(trackGroupArray, eventDispatcher, /* deferOnPrepared= */ true);
createPeriodCalledCountDownLatch.countDown(); createPeriodCalledCountDownLatch.countDown();
return fakeMediaPeriodHolder[0]; return fakeMediaPeriodHolder[0];
} }

View File

@ -92,7 +92,7 @@ public class SimpleDecoderAudioRendererTest {
audioRenderer.enable( audioRenderer.enable(
RendererConfiguration.DEFAULT, RendererConfiguration.DEFAULT,
new Format[] {FORMAT}, new Format[] {FORMAT},
new FakeSampleStream(FORMAT, false), new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false),
0, 0,
false, false,
0); 0);

View File

@ -35,7 +35,6 @@ import java.util.List;
public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
implements SequenceableLoader.Callback<ChunkSampleStream<FakeChunkSource>> { implements SequenceableLoader.Callback<ChunkSampleStream<FakeChunkSource>> {
private final EventDispatcher eventDispatcher;
private final Allocator allocator; private final Allocator allocator;
private final FakeChunkSource.Factory chunkSourceFactory; private final FakeChunkSource.Factory chunkSourceFactory;
private final long durationUs; private final long durationUs;
@ -50,8 +49,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
Allocator allocator, Allocator allocator,
FakeChunkSource.Factory chunkSourceFactory, FakeChunkSource.Factory chunkSourceFactory,
long durationUs) { long durationUs) {
super(trackGroupArray); super(trackGroupArray, eventDispatcher);
this.eventDispatcher = eventDispatcher;
this.allocator = allocator; this.allocator = allocator;
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
this.durationUs = durationUs; this.durationUs = durationUs;

View File

@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener; 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.source.TrackGroupArray;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
@ -44,10 +45,12 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource {
} }
@Override @Override
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray, protected FakeMediaPeriod createFakeMediaPeriod(
Allocator allocator) { MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
EventDispatcher eventDispatcher) {
Period period = timeline.getPeriod(id.periodIndex, new Period()); Period period = timeline.getPeriod(id.periodIndex, new Period());
MediaSourceEventListener.EventDispatcher eventDispatcher = createEventDispatcher(id);
return new FakeAdaptiveMediaPeriod( return new FakeAdaptiveMediaPeriod(
trackGroupArray, eventDispatcher, allocator, chunkSourceFactory, period.durationUs); trackGroupArray, eventDispatcher, allocator, chunkSourceFactory, period.durationUs);
} }

View File

@ -17,24 +17,32 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.source.MediaPeriod; 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.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.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException; import java.io.IOException;
/** /**
* Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting * 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 class FakeMediaPeriod implements MediaPeriod {
public static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://fake.uri"));
private final TrackGroupArray trackGroupArray; private final TrackGroupArray trackGroupArray;
protected final EventDispatcher eventDispatcher;
@Nullable private Handler playerHandler; @Nullable private Handler playerHandler;
@Nullable private Callback prepareCallback; @Nullable private Callback prepareCallback;
@ -46,19 +54,23 @@ public class FakeMediaPeriod implements MediaPeriod {
/** /**
* @param trackGroupArray The track group array. * @param trackGroupArray The track group array.
* @param eventDispatcher A dispatcher for media source events.
*/ */
public FakeMediaPeriod(TrackGroupArray trackGroupArray) { public FakeMediaPeriod(TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher) {
this(trackGroupArray, false); this(trackGroupArray, eventDispatcher, /* deferOnPrepared */ false);
} }
/** /**
* @param trackGroupArray The track group array. * @param trackGroupArray The track group array.
* @param eventDispatcher A dispatcher for media source events.
* @param deferOnPrepared Whether {@link MediaPeriod.Callback#onPrepared(MediaPeriod)} should be * @param deferOnPrepared Whether {@link MediaPeriod.Callback#onPrepared(MediaPeriod)} should be
* called only after {@link #setPreparationComplete()} has been called. If {@code false} * called only after {@link #setPreparationComplete()} has been called. If {@code false}
* preparation completes immediately. * preparation completes immediately.
*/ */
public FakeMediaPeriod(TrackGroupArray trackGroupArray, boolean deferOnPrepared) { public FakeMediaPeriod(
TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher, boolean deferOnPrepared) {
this.trackGroupArray = trackGroupArray; this.trackGroupArray = trackGroupArray;
this.eventDispatcher = eventDispatcher;
this.deferOnPrepared = deferOnPrepared; this.deferOnPrepared = deferOnPrepared;
discontinuityPositionUs = C.TIME_UNSET; discontinuityPositionUs = C.TIME_UNSET;
} }
@ -79,11 +91,11 @@ public class FakeMediaPeriod implements MediaPeriod {
public synchronized void setPreparationComplete() { public synchronized void setPreparationComplete() {
deferOnPrepared = false; deferOnPrepared = false;
if (playerHandler != null && prepareCallback != null) { if (playerHandler != null && prepareCallback != null) {
playerHandler.post(new Runnable() { playerHandler.post(
new Runnable() {
@Override @Override
public void run() { public void run() {
prepared = true; finishPreparation();
prepareCallback.onPrepared(FakeMediaPeriod.this);
} }
}); });
} }
@ -104,12 +116,21 @@ public class FakeMediaPeriod implements MediaPeriod {
@Override @Override
public synchronized void prepare(Callback callback, long positionUs) { 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) { if (deferOnPrepared) {
playerHandler = new Handler(); playerHandler = new Handler();
prepareCallback = callback;
} else { } else {
prepared = true; finishPreparation();
callback.onPrepared(this);
} }
} }
@ -196,7 +217,24 @@ public class FakeMediaPeriod implements MediaPeriod {
} }
protected SampleStream createSampleStream(TrackSelection selection) { 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);
}
} }

View File

@ -17,17 +17,25 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline; 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.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod; 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.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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,6 +47,9 @@ import java.util.List;
*/ */
public class FakeMediaSource extends BaseMediaSource { 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 TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods; private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private final ArrayList<MediaPeriodId> createdMediaPeriods; private final ArrayList<MediaPeriodId> createdMediaPeriods;
@ -81,7 +92,7 @@ public class FakeMediaSource extends BaseMediaSource {
releasedSource = false; releasedSource = false;
sourceInfoRefreshHandler = new Handler(); sourceInfoRefreshHandler = new Handler();
if (timeline != null) { if (timeline != null) {
refreshSourceInfo(timeline, manifest); finishSourcePreparation();
} }
} }
@ -95,7 +106,11 @@ public class FakeMediaSource extends BaseMediaSource {
assertThat(preparedSource).isTrue(); assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse(); assertThat(releasedSource).isFalse();
Assertions.checkIndex(id.periodIndex, 0, timeline.getPeriodCount()); 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); activeMediaPeriods.add(mediaPeriod);
createdMediaPeriods.add(id); createdMediaPeriods.add(id);
return mediaPeriod; return mediaPeriod;
@ -135,7 +150,7 @@ public class FakeMediaSource extends BaseMediaSource {
assertThat(preparedSource).isTrue(); assertThat(preparedSource).isTrue();
timeline = newTimeline; timeline = newTimeline;
manifest = newManifest; manifest = newManifest;
refreshSourceInfo(timeline, manifest); finishSourcePreparation();
} }
}); });
} else { } else {
@ -163,9 +178,51 @@ public class FakeMediaSource extends BaseMediaSource {
return createdMediaPeriods; return createdMediaPeriods;
} }
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray, /**
Allocator allocator) { * Creates a {@link FakeMediaPeriod} for this media source.
return new FakeMediaPeriod(trackGroupArray); *
* @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) { private static TrackGroupArray buildTrackGroupArray(Format... formats) {

View File

@ -15,10 +15,12 @@
*/ */
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import java.io.IOException; import java.io.IOException;
@ -29,16 +31,23 @@ import java.io.IOException;
public final class FakeSampleStream implements SampleStream { public final class FakeSampleStream implements SampleStream {
private final Format format; private final Format format;
private final @Nullable EventDispatcher eventDispatcher;
private boolean readFormat; private boolean readFormat;
private boolean readSample; private boolean readSample;
public FakeSampleStream(Format format) { /**
this(format, true); * Creates fake sample stream which outputs the given {@link Format}, optionally one sample with
} * zero bytes, then end of stream.
*
public FakeSampleStream(Format format, boolean shouldOutputSample) { * @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.format = format;
this.eventDispatcher = eventDispatcher;
readSample = !shouldOutputSample; readSample = !shouldOutputSample;
} }
@ -60,6 +69,14 @@ public final class FakeSampleStream implements SampleStream {
buffer.data.put((byte) 0); buffer.data.put((byte) 0);
buffer.flip(); buffer.flip();
readSample = true; 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; return C.RESULT_BUFFER_READ;
} else { } else {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import android.os.ConditionVariable; 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.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 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.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; 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.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -50,6 +59,7 @@ public class MediaSourceTestRunner {
private final Allocator allocator; private final Allocator allocator;
private final LinkedBlockingDeque<Timeline> timelines; private final LinkedBlockingDeque<Timeline> timelines;
private final CopyOnWriteArraySet<MediaLoadData> completedLoads;
private Timeline timeline; private Timeline timeline;
/** /**
@ -66,6 +76,8 @@ public class MediaSourceTestRunner {
player = new EventHandlingExoPlayer(playbackLooper); player = new EventHandlingExoPlayer(playbackLooper);
mediaSourceListener = new MediaSourceListener(); mediaSourceListener = new MediaSourceListener();
timelines = new LinkedBlockingDeque<>(); timelines = new LinkedBlockingDeque<>();
completedLoads = new CopyOnWriteArraySet<>();
mediaSource.addEventListener(playbackHandler, mediaSourceListener);
} }
/** /**
@ -280,18 +292,99 @@ public class MediaSourceTestRunner {
releasePeriod(secondMediaPeriod); 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. */ /** Releases the runner. Should be called when the runner is no longer required. */
public void release() { public void release() {
playbackThread.quit(); playbackThread.quit();
} }
private class MediaSourceListener implements MediaSource.SourceInfoRefreshListener { private class MediaSourceListener
implements MediaSource.SourceInfoRefreshListener, MediaSourceEventListener {
// SourceInfoRefreshListener methods.
@Override @Override
public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper()); Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
timelines.addLast(timeline); 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 private static class EventHandlingExoPlayer extends StubExoPlayer