From c7c9a1e9e4805387348aa7aee6eeafff3378cb74 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 15 Mar 2018 10:20:46 -0700 Subject: [PATCH] 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 --- .../source/MediaSourceEventListener.java | 314 ++++++++++-------- .../android/exoplayer2/ExoPlayerTest.java | 29 +- .../audio/SimpleDecoderAudioRendererTest.java | 2 +- .../testutil/FakeAdaptiveMediaPeriod.java | 4 +- .../testutil/FakeAdaptiveMediaSource.java | 9 +- .../exoplayer2/testutil/FakeMediaPeriod.java | 68 +++- .../exoplayer2/testutil/FakeMediaSource.java | 69 +++- .../exoplayer2/testutil/FakeSampleStream.java | 27 +- .../testutil/MediaSourceTestRunner.java | 95 +++++- 9 files changed, 441 insertions(+), 176 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java index f24c047a0d..f2d15f3e6e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java @@ -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); } }); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index c0beabdeef..69296f823a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -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]; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 9d5533e8ab..8dc60a15a4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -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); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index d32dda65f4..0cf847e227 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -35,7 +35,6 @@ import java.util.List; public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod implements SequenceableLoader.Callback> { - 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; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java index 3a4f4a0882..41488b2a3b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java @@ -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); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java index ae2ed0d824..19ceeb25e9 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java @@ -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); + } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 85e19409de..68728eb312 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -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 activeMediaPeriods; private final ArrayList 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) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java index 228169b6b3..0575b261a9 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java @@ -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); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 318e495872..95bcd28bd8 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -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 timelines; + private final CopyOnWriteArraySet 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 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 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