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,33 +295,40 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadStarted(LoadEventInfo, MediaLoadData)}. */
public void loadStarted(
final DataSpec dataSpec,
final int dataType,
final int trackType,
final @Nullable Format trackFormat,
final int trackSelectionReason,
final @Nullable Object trackSelectionData,
final long mediaStartTimeUs,
final long mediaEndTimeUs,
final long elapsedRealtimeMs) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
DataSpec dataSpec,
int dataType,
int trackType,
@Nullable Format trackFormat,
int trackSelectionReason,
@Nullable Object trackSelectionData,
long mediaStartTimeUs,
long mediaEndTimeUs,
long elapsedRealtimeMs) {
loadStarted(
new LoadEventInfo(
dataSpec, elapsedRealtimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
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() {
listenerAndHandler.listener.onLoadStarted(
new LoadEventInfo(
dataSpec, elapsedRealtimeMs, /* loadDurationMs= */ 0, /* bytesLoaded= */ 0),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)));
listener.onLoadStarted(loadEventInfo, mediaLoadData);
}
});
}
@ -350,34 +357,42 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadCompleted(LoadEventInfo, MediaLoadData)}. */
public void loadCompleted(
final DataSpec dataSpec,
final int dataType,
final int trackType,
final @Nullable Format trackFormat,
final int trackSelectionReason,
final @Nullable Object trackSelectionData,
final long mediaStartTimeUs,
final long mediaEndTimeUs,
final long elapsedRealtimeMs,
final long loadDurationMs,
final long bytesLoaded) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
DataSpec dataSpec,
int dataType,
int trackType,
@Nullable Format trackFormat,
int trackSelectionReason,
@Nullable Object trackSelectionData,
long mediaStartTimeUs,
long mediaEndTimeUs,
long elapsedRealtimeMs,
long loadDurationMs,
long bytesLoaded) {
loadCompleted(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
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() {
listenerAndHandler.listener.onLoadCompleted(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)));
listener.onLoadCompleted(loadEventInfo, mediaLoadData);
}
});
}
@ -406,34 +421,41 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadCanceled(LoadEventInfo, MediaLoadData)}. */
public void loadCanceled(
final DataSpec dataSpec,
final int dataType,
final int trackType,
final @Nullable Format trackFormat,
final int trackSelectionReason,
final @Nullable Object trackSelectionData,
final long mediaStartTimeUs,
final long mediaEndTimeUs,
final long elapsedRealtimeMs,
final long loadDurationMs,
final long bytesLoaded) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
DataSpec dataSpec,
int dataType,
int trackType,
@Nullable Format trackFormat,
int trackSelectionReason,
@Nullable Object trackSelectionData,
long mediaStartTimeUs,
long mediaEndTimeUs,
long elapsedRealtimeMs,
long loadDurationMs,
long bytesLoaded) {
loadCanceled(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
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() {
listenerAndHandler.listener.onLoadCanceled(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)));
listener.onLoadCanceled(loadEventInfo, mediaLoadData);
}
});
}
@ -466,62 +488,79 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onLoadError(LoadEventInfo, MediaLoadData, IOException, boolean)}. */
public void loadError(
final DataSpec dataSpec,
final int dataType,
final int trackType,
final @Nullable Format trackFormat,
final int trackSelectionReason,
final @Nullable Object trackSelectionData,
final long mediaStartTimeUs,
final long mediaEndTimeUs,
final long elapsedRealtimeMs,
final long loadDurationMs,
final long bytesLoaded,
DataSpec dataSpec,
int dataType,
int trackType,
@Nullable Format trackFormat,
int trackSelectionReason,
@Nullable Object trackSelectionData,
long mediaStartTimeUs,
long mediaEndTimeUs,
long elapsedRealtimeMs,
long loadDurationMs,
long bytesLoaded,
IOException error,
boolean wasCanceled) {
loadError(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)),
error,
wasCanceled);
}
/** Dispatches {@link #onLoadError(LoadEventInfo, MediaLoadData, IOException, boolean)}. */
public void loadError(
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData,
final IOException error,
final boolean wasCanceled) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
Handler handler = listenerAndHandler.handler;
final MediaSourceEventListener listener = listenerAndHandler.listener;
handler.post(
new Runnable() {
@Override
public void run() {
listenerAndHandler.listener.onLoadError(
new LoadEventInfo(dataSpec, elapsedRealtimeMs, loadDurationMs, bytesLoaded),
new MediaLoadData(
windowIndex,
mediaPeriodId,
dataType,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)),
error,
wasCanceled);
listener.onLoadError(loadEventInfo, mediaLoadData, error, wasCanceled);
}
});
}
}
/** Dispatches {@link #onUpstreamDiscarded(MediaLoadData)}. */
public void upstreamDiscarded(
final int trackType, final long mediaStartTimeUs, final long mediaEndTimeUs) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
public void upstreamDiscarded(int trackType, long mediaStartTimeUs, long mediaEndTimeUs) {
upstreamDiscarded(
new MediaLoadData(
windowIndex,
mediaPeriodId,
C.DATA_TYPE_MEDIA,
trackType,
/* trackFormat= */ null,
C.SELECTION_REASON_ADAPTIVE,
/* trackSelectionData= */ null,
adjustMediaTime(mediaStartTimeUs),
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() {
listenerAndHandler.listener.onUpstreamDiscarded(
new MediaLoadData(
windowIndex,
mediaPeriodId,
C.DATA_TYPE_MEDIA,
trackType,
/* trackFormat= */ null,
C.SELECTION_REASON_ADAPTIVE,
/* trackSelectionData= */ null,
adjustMediaTime(mediaStartTimeUs),
adjustMediaTime(mediaEndTimeUs)));
listener.onUpstreamDiscarded(mediaLoadData);
}
});
}
@ -529,27 +568,34 @@ public interface MediaSourceEventListener {
/** Dispatches {@link #onDownstreamFormatChanged(MediaLoadData)}. */
public void downstreamFormatChanged(
final int trackType,
final @Nullable Format trackFormat,
final int trackSelectionReason,
final @Nullable Object trackSelectionData,
final long mediaTimeUs) {
for (final ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
listenerAndHandler.handler.post(
int trackType,
@Nullable Format trackFormat,
int trackSelectionReason,
@Nullable Object trackSelectionData,
long mediaTimeUs) {
downstreamFormatChanged(
new MediaLoadData(
windowIndex,
mediaPeriodId,
C.DATA_TYPE_MEDIA,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaTimeUs),
/* 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() {
listenerAndHandler.listener.onDownstreamFormatChanged(
new MediaLoadData(
windowIndex,
mediaPeriodId,
C.DATA_TYPE_MEDIA,
trackType,
trackFormat,
trackSelectionReason,
trackSelectionData,
adjustMediaTime(mediaTimeUs),
/* mediaEndTimeMs= */ C.TIME_UNSET));
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.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];
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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