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 8b989bc5a1..d4e66abf4e 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 @@ -70,7 +70,6 @@ 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.SampleStream; import com.google.android.exoplayer2.source.SilenceMediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -90,6 +89,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeRenderer; import com.google.android.exoplayer2.testutil.FakeSampleStream; +import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; @@ -98,7 +98,6 @@ import com.google.android.exoplayer2.testutil.FakeTrackSelector; import com.google.android.exoplayer2.testutil.NoUidTimeline; import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocation; import com.google.android.exoplayer2.upstream.Allocator; @@ -663,6 +662,7 @@ public final class ExoPlayerTest { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher, drmSessionManager, @@ -707,6 +707,7 @@ public final class ExoPlayerTest { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher); mediaPeriod.setDiscontinuityPositionUs(10); @@ -739,6 +740,7 @@ public final class ExoPlayerTest { FakeMediaPeriod mediaPeriod = new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher); // Set a discontinuity at the position this period is supposed to start at anyway. @@ -986,6 +988,7 @@ public final class ExoPlayerTest { fakeMediaPeriodHolder[0] = new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher, drmSessionManager, @@ -1039,6 +1042,7 @@ public final class ExoPlayerTest { fakeMediaPeriodHolder[0] = new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher, drmSessionManager, @@ -4292,17 +4296,19 @@ public final class ExoPlayerTest { DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, @Nullable TransferListener transferListener) { - FakeMediaPeriod fakeMediaPeriod = - new FakeMediaPeriod( - trackGroupArray, - FakeMediaPeriod.TrackDataFactory.singleSampleWithTimeUs(/* sampleTimeUs= */ 0), - mediaSourceEventDispatcher, - drmSessionManager, - drmEventDispatcher, - /* deferOnPrepared= */ false); - fakeMediaPeriod.setBufferedPositionUs( - windowOffsetInFirstPeriodUs + C.msToUs(maxBufferedPositionMs)); - return fakeMediaPeriod; + return new FakeMediaPeriod( + trackGroupArray, + allocator, + /* trackDataFactory= */ (format, mediaPeriodId) -> + ImmutableList.of( + oneByteSample(windowOffsetInFirstPeriodUs, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample( + windowOffsetInFirstPeriodUs + C.msToUs(maxBufferedPositionMs), + C.BUFFER_FLAG_KEY_FRAME)), + mediaSourceEventDispatcher, + drmSessionManager, + drmEventDispatcher, + /* deferOnPrepared= */ false); } }; } @@ -4336,12 +4342,10 @@ public final class ExoPlayerTest { boolean[] isPlayingAd = new boolean[3]; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) - .waitForPlaybackState(Player.STATE_READY) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) .pause() + .waitForIsLoading(true) + .waitForIsLoading(false) + .waitForPlaybackState(Player.STATE_READY) .executeRunnable( new PlayerRunnable() { @Override @@ -4395,19 +4399,19 @@ public final class ExoPlayerTest { assertThat(isPlayingAd[0]).isTrue(); assertThat(positionMs[0]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs); - assertThat(totalBufferedDurationMs[0]).isEqualTo(adDurationMs - positionMs[0]); + assertThat(totalBufferedDurationMs[0]).isAtLeast(adDurationMs - positionMs[0]); assertThat(windowIndex[1]).isEqualTo(0); assertThat(isPlayingAd[1]).isTrue(); assertThat(positionMs[1]).isAtMost(adDurationMs); assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs); - assertThat(totalBufferedDurationMs[1]).isEqualTo(adDurationMs - positionMs[1]); + assertThat(totalBufferedDurationMs[1]).isAtLeast(adDurationMs - positionMs[1]); assertThat(windowIndex[2]).isEqualTo(0); assertThat(isPlayingAd[2]).isFalse(); assertThat(positionMs[2]).isGreaterThan(8000); assertThat(bufferedPositionMs[2]).isEqualTo(contentDurationMs); - assertThat(totalBufferedDurationMs[2]).isEqualTo(contentDurationMs - positionMs[2]); + assertThat(totalBufferedDurationMs[2]).isAtLeast(contentDurationMs - positionMs[2]); } @Test @@ -4669,6 +4673,7 @@ public final class ExoPlayerTest { @Nullable TransferListener transferListener) { return new FakeMediaPeriod( trackGroupArray, + allocator, TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, mediaSourceEventDispatcher) { @Override @@ -7054,8 +7059,6 @@ public final class ExoPlayerTest { // Wait until fully buffered so that the new renderer can be enabled immediately. .waitForIsLoading(true) .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) .removeMediaItem(0) .build(); ExoPlayerTestRunner testRunner = @@ -7220,6 +7223,7 @@ public final class ExoPlayerTest { @Nullable TransferListener transferListener) { return new FakeMediaPeriod( trackGroupArray, + allocator, trackDataWithoutEos, mediaSourceEventDispatcher, drmSessionManager, @@ -7284,6 +7288,14 @@ public final class ExoPlayerTest { @Override public void cancelLoad() {} }; + // Create 3 samples without end of stream signal to test that all 3 samples are + // still played before the sample stream exception is thrown. + FakeSampleStreamItem sample = + oneByteSample( + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + C.BUFFER_FLAG_KEY_FRAME); + FakeMediaPeriod.TrackDataFactory threeSamplesWithoutEos = + (format, mediaPeriodId) -> ImmutableList.of(sample, sample, sample); MediaSource largeBufferAllocatingMediaSource = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT) { @Override @@ -7297,7 +7309,8 @@ public final class ExoPlayerTest { @Nullable TransferListener transferListener) { return new FakeMediaPeriod( trackGroupArray, - TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + allocator, + threeSamplesWithoutEos, mediaSourceEventDispatcher, drmSessionManager, drmEventDispatcher, @@ -7306,30 +7319,29 @@ public final class ExoPlayerTest { @Override public boolean continueLoading(long positionUs) { - loader.startLoading( - loadable, new FakeLoaderCallback(), /* defaultMinRetryCount= */ 1); + super.continueLoading(positionUs); + if (!loader.isLoading()) { + loader.startLoading( + loadable, new FakeLoaderCallback(), /* defaultMinRetryCount= */ 1); + } return true; } @Override - protected SampleStream createSampleStream( - long positionUs, - TrackSelection selection, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, + protected FakeSampleStream createSampleStream( + Allocator allocator, + @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher) { - // Create 3 samples without end of stream signal to test that all 3 samples are - // still played before the exception is thrown. + DrmSessionEventListener.EventDispatcher drmEventDispatcher, + Format initialFormat, + List fakeSampleStreamItems) { return new FakeSampleStream( + allocator, mediaSourceEventDispatcher, drmSessionManager, drmEventDispatcher, - selection.getSelectedFormat(), - ImmutableList.of( - oneByteSample(positionUs), - oneByteSample(positionUs), - oneByteSample(positionUs))) { - + initialFormat, + fakeSampleStreamItems) { @Override public void maybeThrowError() throws IOException { loader.maybeThrowError(); @@ -7498,14 +7510,16 @@ public final class ExoPlayerTest { /* timeline= */ null, DrmSessionManager.DUMMY, (unusedFormat, unusedMediaPeriodId) -> - ImmutableList.of(oneByteSample(firstSampleTimeUs), END_OF_STREAM_ITEM), + ImmutableList.of( + oneByteSample(firstSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM), ExoPlayerTestRunner.VIDEO_FORMAT); FakeMediaSource secondMediaSource = new FakeMediaSource( timelineWithOffsets, DrmSessionManager.DUMMY, (unusedFormat, unusedMediaPeriodId) -> - ImmutableList.of(oneByteSample(firstSampleTimeUs), END_OF_STREAM_ITEM), + ImmutableList.of( + oneByteSample(firstSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM), ExoPlayerTestRunner.VIDEO_FORMAT); player.setMediaSources(ImmutableList.of(firstMediaSource, secondMediaSource)); @@ -8033,6 +8047,7 @@ public final class ExoPlayerTest { @Nullable TransferListener transferListener) { return new FakeMediaPeriod( trackGroupArray, + allocator, /* singleSampleTimeUs= */ 0, mediaSourceEventDispatcher, DrmSessionManager.DUMMY, @@ -8077,20 +8092,28 @@ public final class ExoPlayerTest { DrmSessionEventListener.EventDispatcher drmEventDispatcher, @Nullable TransferListener transferListener) { return new FakeMediaPeriod( - trackGroupArray, /* singleSampleTimeUs= */ 0, mediaSourceEventDispatcher) { + trackGroupArray, + allocator, + /* trackDataFactory= */ (format, mediaPeriodId) -> ImmutableList.of(), + mediaSourceEventDispatcher, + drmSessionManager, + drmEventDispatcher, + /* deferOnPrepared= */ false) { @Override - protected SampleStream createSampleStream( - long positionUs, - TrackSelection selection, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, + protected FakeSampleStream createSampleStream( + Allocator allocator, + @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher) { + DrmSessionEventListener.EventDispatcher drmEventDispatcher, + Format initialFormat, + List fakeSampleStreamItems) { return new FakeSampleStream( + allocator, mediaSourceEventDispatcher, - DrmSessionManager.DUMMY, + drmSessionManager, drmEventDispatcher, - selection.getSelectedFormat(), - /* fakeSampleStreamItems= */ ImmutableList.of()) { + initialFormat, + fakeSampleStreamItems) { @Override public void maybeThrowError() throws IOException { throw new IOException(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index c3ebb12cfb..53f8e98bd9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -29,7 +29,6 @@ import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DR import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_LOADING_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_PLAYING_CHANGED; -import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_CANCELED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_COMPLETED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_ERROR; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_STARTED; @@ -283,7 +282,7 @@ public final class AnalyticsCollectorTest { .inOrder(); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) - .containsExactly(period0, period0, period0, period0) + .containsExactly(period0, period0) .inOrder(); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly(period0, period1) @@ -365,7 +364,7 @@ public final class AnalyticsCollectorTest { .inOrder(); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) - .containsExactly(period0, period0, period0, period0) + .containsExactly(period0, period0) .inOrder(); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly(period0, period1) @@ -428,8 +427,6 @@ public final class AnalyticsCollectorTest { // Wait until second period has fully loaded to assert loading events without flakiness. .waitForIsLoading(true) .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) .seek(/* windowIndex= */ 1, /* positionMs= */ 0) .play() .build(); @@ -453,9 +450,9 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period1); - List loadingEvents = listener.getEvents(EVENT_IS_LOADING_CHANGED); - assertThat(loadingEvents).hasSize(4); - assertThat(loadingEvents).containsAtLeast(period0, period0).inOrder(); + assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) + .containsExactly(period0, period0) + .inOrder(); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly(period0, period1, period1) .inOrder(); @@ -555,7 +552,7 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) - .containsExactly(period0, period0, period0, period0, period0, period0) + .containsExactly(period0, period0, period0, period0) .inOrder(); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly(period0, period1Seq2) @@ -851,8 +848,7 @@ public final class AnalyticsCollectorTest { period1Seq0 /* SOURCE_UPDATE (child sources in concatenating source moved) */) .inOrder(); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) - .containsExactly( - window0Period1Seq0, window0Period1Seq0, window0Period1Seq0, window0Period1Seq0); + .containsExactly(window0Period1Seq0, window0Period1Seq0, period1Seq0, period1Seq0); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(window0Period1Seq0); assertThat(listener.getEvents(EVENT_LOAD_STARTED)) .containsExactly( @@ -960,27 +956,27 @@ public final class AnalyticsCollectorTest { .containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */) .inOrder(); assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(period0Seq0, period0Seq1, period0Seq1) + .containsExactly(period0Seq0, period1Seq1, period0Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_DECODER_DISABLED)) .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)) - .containsExactly(period0Seq0, period0Seq1, period0Seq1) + .containsExactly(period0Seq0, period1Seq1, period0Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly(period0Seq0, period1Seq1) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); @@ -1022,14 +1018,19 @@ public final class AnalyticsCollectorTest { DrmSessionManager.DUMMY, (unusedFormat, mediaPeriodId) -> { if (mediaPeriodId.isAd()) { - return ImmutableList.of(oneByteSample(/* timeUs= */ 0), END_OF_STREAM_ITEM); + return ImmutableList.of( + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM); } else { // Provide a single sample before and after the midroll ad and another after the // postroll. return ImmutableList.of( - oneByteSample(windowOffsetInFirstPeriodUs + C.MICROS_PER_SECOND), - oneByteSample(windowOffsetInFirstPeriodUs + 6 * C.MICROS_PER_SECOND), - oneByteSample(windowOffsetInFirstPeriodUs + contentDurationsUs), + oneByteSample( + windowOffsetInFirstPeriodUs + C.MICROS_PER_SECOND, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample( + windowOffsetInFirstPeriodUs + 6 * C.MICROS_PER_SECOND, + C.BUFFER_FLAG_KEY_FRAME), + oneByteSample( + windowOffsetInFirstPeriodUs + contentDurationsUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM); } }, @@ -1073,16 +1074,6 @@ public final class AnalyticsCollectorTest { // Ensure everything is preloaded. .waitForIsLoading(true) .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) .waitForPlaybackState(Player.STATE_READY) // Wait in each content part to ensure previously triggered events get a chance to be // delivered. This prevents flakiness caused by playback progressing too fast. @@ -1161,9 +1152,7 @@ public final class AnalyticsCollectorTest { contentAfterPreroll, midrollAd, contentAfterMidroll, postrollAd, contentAfterPostroll) .inOrder(); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) - .containsExactly( - prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, - prerollAd, prerollAd, prerollAd, prerollAd) + .containsExactly(prerollAd, prerollAd) .inOrder(); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) .containsExactly( @@ -1289,12 +1278,16 @@ public final class AnalyticsCollectorTest { DrmSessionManager.DUMMY, (unusedFormat, mediaPeriodId) -> { if (mediaPeriodId.isAd()) { - return ImmutableList.of(oneByteSample(/* timeUs= */ 0), END_OF_STREAM_ITEM); + return ImmutableList.of( + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM); } else { // Provide a sample before the midroll and another after the seek point below (6s). return ImmutableList.of( - oneByteSample(windowOffsetInFirstPeriodUs + C.MICROS_PER_SECOND), - oneByteSample(windowOffsetInFirstPeriodUs + 7 * C.MICROS_PER_SECOND), + oneByteSample( + windowOffsetInFirstPeriodUs + C.MICROS_PER_SECOND, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample( + windowOffsetInFirstPeriodUs + 7 * C.MICROS_PER_SECOND, + C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM); } }, @@ -1305,10 +1298,6 @@ public final class AnalyticsCollectorTest { // Ensure everything is preloaded. .waitForIsLoading(true) .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) // Seek behind the midroll. .seek(6 * C.MICROS_PER_SECOND) // Wait until loading started again to assert loading events without flakiness. @@ -1359,10 +1348,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(contentAfterMidroll); assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED)) .containsExactly( - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, contentBeforeMidroll, contentBeforeMidroll, midrollAd, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java index d5e1871c69..db91637ca9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.testutil.FakeSampleStream; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import org.junit.Before; @@ -102,15 +103,19 @@ public class DecoderAudioRendererTest { @Test public void immediatelyReadEndOfStreamPlaysAudioSinkToEndOfStream() throws Exception { - audioRenderer.enable( - RendererConfiguration.DEFAULT, - new Format[] {FORMAT}, + FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), FORMAT, - ImmutableList.of(END_OF_STREAM_ITEM)), + ImmutableList.of(END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); + audioRenderer.enable( + RendererConfiguration.DEFAULT, + new Format[] {FORMAT}, + fakeSampleStream, /* positionUs= */ 0, /* joining= */ false, /* mayRenderStartOfStream= */ true, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java index 8266dc9b83..02cc40f528 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.testutil.FakeSampleStream; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; @@ -118,6 +119,7 @@ public class MediaCodecAudioRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), @@ -131,6 +133,7 @@ public class MediaCodecAudioRendererTest { oneByteSample(/* timeUs= */ 200, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 250, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecAudioRenderer.enable( RendererConfiguration.DEFAULT, @@ -173,6 +176,7 @@ public class MediaCodecAudioRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), @@ -186,6 +190,7 @@ public class MediaCodecAudioRendererTest { oneByteSample(/* timeUs= */ 200, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 250, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecAudioRenderer.enable( RendererConfiguration.DEFAULT, @@ -249,12 +254,14 @@ public class MediaCodecAudioRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ AUDIO_AAC, ImmutableList.of( oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); exceptionThrowingRenderer.enable( RendererConfiguration.DEFAULT, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java index 796f56becf..346aa95852 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java @@ -15,11 +15,14 @@ */ package com.google.android.exoplayer2.metadata; +import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; +import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.sample; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.drm.DrmSessionEventListener; @@ -29,8 +32,8 @@ import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.metadata.scte35.TimeSignalCommand; import com.google.android.exoplayer2.testutil.FakeSampleStream; -import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; @@ -144,16 +147,19 @@ public class MetadataRendererTest { private static List runRenderer(byte[] input) throws ExoPlaybackException { List metadata = new ArrayList<>(); MetadataRenderer renderer = new MetadataRenderer(metadata::add, /* outputLooper= */ null); - renderer.replaceStream( - new Format[] {EMSG_FORMAT}, + FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), EMSG_FORMAT, ImmutableList.of( - FakeSampleStreamItem.sample(/* timeUs= */ 0, /* flags= */ 0, input), - FakeSampleStreamItem.END_OF_STREAM_ITEM)), + sample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME, input), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); + renderer.replaceStream( + new Format[] {EMSG_FORMAT}, + fakeSampleStream, /* startPositionUs= */ 0L, /* offsetUs= */ 0L); renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the format diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java index 76f9267430..2007ecdb74 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java @@ -493,6 +493,7 @@ public class DownloadHelperTest { int periodIndex = TEST_TIMELINE.getIndexOfPeriod(id.periodUid); return new FakeMediaPeriod( trackGroupArrays[periodIndex], + allocator, TEST_TIMELINE.getWindow(0, new Timeline.Window()).positionInFirstPeriodUs, new EventDispatcher() .withParameters(/* windowIndex= */ 0, id, /* mediaTimeOffsetMs= */ 0)) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java index e28af160c3..26285d7e81 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.common.collect.ImmutableList; import java.util.concurrent.CountDownLatch; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -82,6 +83,7 @@ public final class MergingMediaPeriodTest { streams, /* streamResetFlags= */ new boolean[] {false, false, false, false}, /* positionUs= */ 0); + mergingMediaPeriod.continueLoading(/* positionUs= */ 0); assertThat(streams[0]).isNull(); assertThat(streams[3]).isNull(); @@ -126,6 +128,7 @@ public final class MergingMediaPeriodTest { streams, /* streamResetFlags= */ new boolean[] {false, false}, /* positionUs= */ 0); + mergingMediaPeriod.continueLoading(/* positionUs= */ 0); FormatHolder formatHolder = new FormatHolder(); DecoderInputBuffer inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); @@ -168,7 +171,8 @@ public final class MergingMediaPeriodTest { /* mediaTimeOffsetMs= */ 0), /* trackDataFactory= */ (unusedFormat, unusedMediaPeriodId) -> ImmutableList.of( - oneByteSample(definition.singleSampleTimeUs), END_OF_STREAM_ITEM)); + oneByteSample(definition.singleSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME), + END_OF_STREAM_ITEM)); } MergingMediaPeriod mergingMediaPeriod = new MergingMediaPeriod( @@ -203,6 +207,7 @@ public final class MergingMediaPeriodTest { TrackDataFactory trackDataFactory) { super( trackGroupArray, + new DefaultAllocator(/* trimOnReset= */ false, /* individualAllocationSize= */ 1024), trackDataFactory, mediaSourceEventDispatcher, DrmSessionManager.DUMMY, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java index 3a9c6e651d..d09c2e0b3d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.video; +import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @@ -39,7 +40,7 @@ import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.testutil.FakeSampleStream; -import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.concurrent.Phaser; @@ -184,11 +185,13 @@ public final class DecoderVideoRendererTest { public void enable_withMayRenderStartOfStream_rendersFirstFrameBeforeStart() throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, - ImmutableList.of(oneByteSample(/* timeUs= */ 0))); + ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); renderer.enable( RendererConfiguration.DEFAULT, @@ -213,11 +216,13 @@ public final class DecoderVideoRendererTest { throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, - ImmutableList.of(oneByteSample(/* timeUs= */ 0))); + ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); renderer.enable( RendererConfiguration.DEFAULT, @@ -241,11 +246,13 @@ public final class DecoderVideoRendererTest { public void enable_withoutMayRenderStartOfStream_rendersFirstFrameAfterStart() throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, - ImmutableList.of(oneByteSample(/* timeUs= */ 0))); + ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); renderer.enable( RendererConfiguration.DEFAULT, @@ -272,20 +279,23 @@ public final class DecoderVideoRendererTest { public void replaceStream_whenStarted_rendersFirstFrameOfNewStream() throws Exception { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, ImmutableList.of( - oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM)); + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream1.writeData(/* startPositionUs= */ 0); FakeSampleStream fakeSampleStream2 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, - ImmutableList.of( - oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM)); + ImmutableList.of(oneByteSample(/* timeUs= */ 0), END_OF_STREAM_ITEM)); + fakeSampleStream2.writeData(/* startPositionUs= */ 0); renderer.enable( RendererConfiguration.DEFAULT, new Format[] {H264_FORMAT}, @@ -321,20 +331,23 @@ public final class DecoderVideoRendererTest { public void replaceStream_whenNotStarted_doesNotRenderFirstFrameOfNewStream() throws Exception { FakeSampleStream fakeSampleStream1 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, ImmutableList.of( - oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM)); + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream1.writeData(/* startPositionUs= */ 0); FakeSampleStream fakeSampleStream2 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ H264_FORMAT, - ImmutableList.of( - oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM)); + ImmutableList.of(oneByteSample(/* timeUs= */ 0), END_OF_STREAM_ITEM)); + fakeSampleStream2.writeData(/* startPositionUs= */ 0); renderer.enable( RendererConfiguration.DEFAULT, new Format[] {H264_FORMAT}, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index b2313215ab..c0ec86d959 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.video; +import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.format; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.common.truth.Truth.assertThat; @@ -47,7 +48,7 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.testutil.FakeSampleStream; -import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; @@ -125,6 +126,7 @@ public class MediaCodecVideoRendererTest { public void render_dropsLateBuffer() throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), @@ -133,7 +135,8 @@ public class MediaCodecVideoRendererTest { oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), // First buffer. oneByteSample(/* timeUs= */ 50_000), // Late buffer. oneByteSample(/* timeUs= */ 100_000), // Last buffer. - FakeSampleStreamItem.END_OF_STREAM_ITEM)); + END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, new Format[] {VIDEO_H264}, @@ -160,17 +163,20 @@ public class MediaCodecVideoRendererTest { @Test public void render_sendsVideoSizeChangeWithCurrentFormatValues() throws Exception { - mediaCodecVideoRenderer.enable( - RendererConfiguration.DEFAULT, - new Format[] {VIDEO_H264}, + FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of( - oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - FakeSampleStreamItem.END_OF_STREAM_ITEM)), + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); + mediaCodecVideoRenderer.enable( + RendererConfiguration.DEFAULT, + new Format[] {VIDEO_H264}, + fakeSampleStream, /* positionUs= */ 0, /* joining= */ false, /* mayRenderStartOfStream= */ true, @@ -204,11 +210,13 @@ public class MediaCodecVideoRendererTest { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ pAsp1, ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, @@ -223,13 +231,16 @@ public class MediaCodecVideoRendererTest { mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecVideoRenderer.render(/* positionUs= */ 250, SystemClock.elapsedRealtime() * 1000); - fakeSampleStream.addFakeSampleStreamItem(format(pAsp2)); - fakeSampleStream.addFakeSampleStreamItem(oneByteSample(/* timeUs= */ 5_000)); - fakeSampleStream.addFakeSampleStreamItem(oneByteSample(/* timeUs= */ 10_000)); - fakeSampleStream.addFakeSampleStreamItem(format(pAsp3)); - fakeSampleStream.addFakeSampleStreamItem(oneByteSample(/* timeUs= */ 15_000)); - fakeSampleStream.addFakeSampleStreamItem(oneByteSample(/* timeUs= */ 20_000)); - fakeSampleStream.addFakeSampleStreamItem(FakeSampleStreamItem.END_OF_STREAM_ITEM); + fakeSampleStream.append( + ImmutableList.of( + format(pAsp2), + oneByteSample(/* timeUs= */ 5_000), + oneByteSample(/* timeUs= */ 10_000), + format(pAsp3), + oneByteSample(/* timeUs= */ 15_000), + oneByteSample(/* timeUs= */ 20_000), + END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 5_000); mediaCodecVideoRenderer.setCurrentStreamFinal(); int pos = 500; @@ -251,11 +262,13 @@ public class MediaCodecVideoRendererTest { throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, new Format[] {VIDEO_H264}, @@ -270,9 +283,10 @@ public class MediaCodecVideoRendererTest { mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecVideoRenderer.resetPosition(0); mediaCodecVideoRenderer.setCurrentStreamFinal(); - fakeSampleStream.addFakeSampleStreamItem( - oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME)); - fakeSampleStream.addFakeSampleStreamItem(FakeSampleStreamItem.END_OF_STREAM_ITEM); + fakeSampleStream.append( + ImmutableList.of( + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream.writeData(/* startPositionUs= */ 0); int positionUs = 10; do { mediaCodecVideoRenderer.render(positionUs, SystemClock.elapsedRealtime() * 1000); @@ -287,11 +301,13 @@ public class MediaCodecVideoRendererTest { public void enable_withMayRenderStartOfStream_rendersFirstFrameBeforeStart() throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, @@ -315,11 +331,13 @@ public class MediaCodecVideoRendererTest { throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, - ImmutableList.of(oneByteSample(/* timeUs= */ 0))); + ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, @@ -342,11 +360,13 @@ public class MediaCodecVideoRendererTest { public void enable_withoutMayRenderStartOfStream_rendersFirstFrameAfterStart() throws Exception { FakeSampleStream fakeSampleStream = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of(oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME))); + fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, @@ -371,22 +391,25 @@ public class MediaCodecVideoRendererTest { ShadowLooper shadowLooper = shadowOf(testMainLooper); FakeSampleStream fakeSampleStream1 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of( - oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - FakeSampleStreamItem.END_OF_STREAM_ITEM)); + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream1.writeData(/* startPositionUs= */ 0); FakeSampleStream fakeSampleStream2 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of( oneByteSample(/* timeUs= */ 1_000_000, C.BUFFER_FLAG_KEY_FRAME), - FakeSampleStreamItem.END_OF_STREAM_ITEM)); + END_OF_STREAM_ITEM)); + fakeSampleStream2.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, new Format[] {VIDEO_H264}, @@ -422,22 +445,24 @@ public class MediaCodecVideoRendererTest { ShadowLooper shadowLooper = shadowOf(testMainLooper); FakeSampleStream fakeSampleStream1 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of( - oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - FakeSampleStreamItem.END_OF_STREAM_ITEM)); + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream1.writeData(/* startPositionUs= */ 0); FakeSampleStream fakeSampleStream2 = new FakeSampleStream( + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* mediaSourceEventDispatcher= */ null, DrmSessionManager.DUMMY, new DrmSessionEventListener.EventDispatcher(), /* initialFormat= */ VIDEO_H264, ImmutableList.of( - oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - FakeSampleStreamItem.END_OF_STREAM_ITEM)); + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); + fakeSampleStream2.writeData(/* startPositionUs= */ 0); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, new Format[] {VIDEO_H264}, 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 2f6a91460e..2b79354575 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,7 +17,9 @@ package com.google.android.exoplayer2.testutil; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.min; import android.net.Uri; import android.os.Handler; @@ -37,29 +39,51 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem; import com.google.android.exoplayer2.trackselection.TrackSelection; +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.Util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import org.checkerframework.checker.nullness.compatqual.NullableType; -/** - * Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting - * tracks will give the player {@link FakeSampleStream}s. Loading data completes immediately after - * the period has finished preparing. - */ +/** Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. */ public class FakeMediaPeriod implements MediaPeriod { - public static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://fake.uri")); + private static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://fake.test")); + + /** A factory to create the test data for a particular track. */ + public interface TrackDataFactory { + + /** + * Returns the list of {@link FakeSampleStream.FakeSampleStreamItem}s that will be written the + * sample queue during playback. + * + * @param format The format of the track to provide data for. + * @param mediaPeriodId The {@link MediaPeriodId} to provide data for. + * @return The track data in the form of {@link FakeSampleStream.FakeSampleStreamItem}s. + */ + List create(Format format, MediaPeriodId mediaPeriodId); + + /** + * Returns a factory that always provides a single keyframe sample with {@code + * time=sampleTimeUs} and then end-of-stream. + */ + static TrackDataFactory singleSampleWithTimeUs(long sampleTimeUs) { + return (unusedFormat, unusedMediaPeriodId) -> + ImmutableList.of( + oneByteSample(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM); + } + } private final TrackGroupArray trackGroupArray; - private final List sampleStreams; + private final Set sampleStreams; private final TrackDataFactory trackDataFactory; private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; + private final Allocator allocator; private final DrmSessionManager drmSessionManager; private final DrmSessionEventListener.EventDispatcher drmEventDispatcher; private final long fakePreparationLoadTaskId; @@ -71,22 +95,25 @@ public class FakeMediaPeriod implements MediaPeriod { private boolean prepared; private long seekOffsetUs; private long discontinuityPositionUs; - private long bufferedPositionUs; + private long lastSeekPositionUs; /** * Constructs a FakeMediaPeriod with a single sample for each track in {@code trackGroupArray}. * * @param trackGroupArray The track group array. + * @param allocator An {@link Allocator}. * @param singleSampleTimeUs The timestamp to use for the single sample in each track, in * microseconds. * @param mediaSourceEventDispatcher A dispatcher for {@link MediaSourceEventListener} events. */ public FakeMediaPeriod( TrackGroupArray trackGroupArray, + Allocator allocator, long singleSampleTimeUs, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher) { this( trackGroupArray, + allocator, TrackDataFactory.singleSampleWithTimeUs(singleSampleTimeUs), mediaSourceEventDispatcher, DrmSessionManager.DUMMY, @@ -98,6 +125,7 @@ public class FakeMediaPeriod implements MediaPeriod { * Constructs a FakeMediaPeriod with a single sample for each track in {@code trackGroupArray}. * * @param trackGroupArray The track group array. + * @param allocator An {@link Allocator}. * @param singleSampleTimeUs The timestamp to use for the single sample in each track, in * microseconds. * @param mediaSourceEventDispatcher A dispatcher for {@link MediaSourceEventListener} events. @@ -109,6 +137,7 @@ public class FakeMediaPeriod implements MediaPeriod { */ public FakeMediaPeriod( TrackGroupArray trackGroupArray, + Allocator allocator, long singleSampleTimeUs, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, @@ -116,6 +145,7 @@ public class FakeMediaPeriod implements MediaPeriod { boolean deferOnPrepared) { this( trackGroupArray, + allocator, TrackDataFactory.singleSampleWithTimeUs(singleSampleTimeUs), mediaSourceEventDispatcher, drmSessionManager, @@ -127,10 +157,10 @@ public class FakeMediaPeriod implements MediaPeriod { * Constructs a FakeMediaPeriod. * * @param trackGroupArray The track group array. - * @param trackDataFactory A source for the underlying sample data for each track in {@code - * trackGroupArray}. + * @param allocator An {@link Allocator}. + * @param trackDataFactory The {@link TrackDataFactory} creating the data. * @param mediaSourceEventDispatcher A dispatcher for media source events. - * @param drmSessionManager The DrmSessionManager used for DRM interactions. + * @param drmSessionManager The {@link DrmSessionManager} used for DRM interactions. * @param drmEventDispatcher A dispatcher for {@link DrmSessionEventListener} events. * @param deferOnPrepared Whether {@link Callback#onPrepared(MediaPeriod)} should be called only * after {@link #setPreparationComplete()} has been called. If {@code false} preparation @@ -138,6 +168,7 @@ public class FakeMediaPeriod implements MediaPeriod { */ public FakeMediaPeriod( TrackGroupArray trackGroupArray, + Allocator allocator, TrackDataFactory trackDataFactory, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, @@ -145,13 +176,13 @@ public class FakeMediaPeriod implements MediaPeriod { boolean deferOnPrepared) { this.trackGroupArray = trackGroupArray; this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; - this.drmSessionManager = drmSessionManager; - this.drmEventDispatcher = drmEventDispatcher; this.deferOnPrepared = deferOnPrepared; this.trackDataFactory = trackDataFactory; - this.bufferedPositionUs = C.TIME_END_OF_SOURCE; + this.allocator = allocator; + this.drmSessionManager = drmSessionManager; + this.drmEventDispatcher = drmEventDispatcher; + sampleStreams = Sets.newIdentityHashSet(); discontinuityPositionUs = C.TIME_UNSET; - sampleStreams = new ArrayList<>(); fakePreparationLoadTaskId = LoadEventInfo.getNewId(); } @@ -184,11 +215,13 @@ public class FakeMediaPeriod implements MediaPeriod { this.seekOffsetUs = seekOffsetUs; } + /** Releases the media period. */ public void release() { prepared = false; - for (int i = 0; i < sampleStreams.size(); i++) { - releaseSampleStream(sampleStreams.get(i)); + for (FakeSampleStream sampleStream : sampleStreams) { + sampleStream.release(); } + sampleStreams.clear(); } @Override @@ -229,10 +262,11 @@ public class FakeMediaPeriod implements MediaPeriod { boolean[] streamResetFlags, long positionUs) { assertThat(prepared).isTrue(); - sampleStreams.clear(); int rendererCount = selections.length; for (int i = 0; i < rendererCount; i++) { if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + ((FakeSampleStream) streams[i]).release(); + sampleStreams.remove(streams[i]); streams[i] = null; } if (streams[i] == null && selections[i] != null) { @@ -243,23 +277,31 @@ public class FakeMediaPeriod implements MediaPeriod { int indexInTrackGroup = selection.getIndexInTrackGroup(selection.getSelectedIndex()); assertThat(indexInTrackGroup).isAtLeast(0); assertThat(indexInTrackGroup).isLessThan(trackGroup.length); - streams[i] = + List sampleStreamItems = + trackDataFactory.create( + selection.getSelectedFormat(), + checkNotNull(mediaSourceEventDispatcher.mediaPeriodId)); + FakeSampleStream sampleStream = createSampleStream( - positionUs, - selection, + allocator, mediaSourceEventDispatcher, drmSessionManager, - drmEventDispatcher); - sampleStreams.add(streams[i]); + drmEventDispatcher, + selection.getSelectedFormat(), + sampleStreamItems); + sampleStreams.add(sampleStream); + streams[i] = sampleStream; streamResetFlags[i] = true; } } - return positionUs; + return seekToUs(positionUs); } @Override public void discardBuffer(long positionUs, boolean toKeyframe) { - // Do nothing. + for (FakeSampleStream sampleStream : sampleStreams) { + sampleStream.discardTo(positionUs, toKeyframe); + } } @Override @@ -278,22 +320,30 @@ public class FakeMediaPeriod implements MediaPeriod { @Override public long getBufferedPositionUs() { assertThat(prepared).isTrue(); - return bufferedPositionUs; - } - - public void setBufferedPositionUs(long bufferedPositionUs) { - this.bufferedPositionUs = bufferedPositionUs; + if (isLoadingFinished()) { + return C.TIME_END_OF_SOURCE; + } + long minBufferedPositionUs = Long.MAX_VALUE; + for (FakeSampleStream sampleStream : sampleStreams) { + minBufferedPositionUs = + min(minBufferedPositionUs, sampleStream.getLargestQueuedTimestampUs()); + } + return minBufferedPositionUs == Long.MIN_VALUE ? lastSeekPositionUs : minBufferedPositionUs; } @Override public long seekToUs(long positionUs) { assertThat(prepared).isTrue(); long seekPositionUs = positionUs + seekOffsetUs; - for (SampleStream sampleStream : sampleStreams) { - seekSampleStream(sampleStream, seekPositionUs); + lastSeekPositionUs = seekPositionUs; + boolean seekedInsideStreams = true; + for (FakeSampleStream sampleStream : sampleStreams) { + seekedInsideStreams &= sampleStream.seekToUs(seekPositionUs); } - if (bufferedPositionUs != C.TIME_END_OF_SOURCE && seekPositionUs > bufferedPositionUs) { - bufferedPositionUs = seekPositionUs; + if (!seekedInsideStreams) { + for (FakeSampleStream sampleStream : sampleStreams) { + sampleStream.reset(); + } } return seekPositionUs; } @@ -306,12 +356,15 @@ public class FakeMediaPeriod implements MediaPeriod { @Override public long getNextLoadPositionUs() { assertThat(prepared).isTrue(); - return C.TIME_END_OF_SOURCE; + return getBufferedPositionUs(); } @Override public boolean continueLoading(long positionUs) { - return false; + for (FakeSampleStream sampleStream : sampleStreams) { + sampleStream.writeData(positionUs); + } + return true; } @Override @@ -320,58 +373,31 @@ public class FakeMediaPeriod implements MediaPeriod { } /** - * Creates a sample stream for the provided selection. + * Creates a new {@link FakeSampleStream}. * - * @param positionUs The position at which the tracks were selected, in microseconds. - * @param selection A selection of tracks. - * @param mediaSourceEventDispatcher A dispatcher for {@link MediaSourceEventListener} events that - * should be used by the sample stream. - * @param drmSessionManager The DRM session manager. - * @param drmEventDispatcher A dispatcher for {@link DrmSessionEventListener} events that should - * be used by the sample stream. - * @return A {@link SampleStream} for this selection. + * @param mediaSourceEventDispatcher A {@link MediaSourceEventListener.EventDispatcher} to notify + * of media events. + * @param drmSessionManager A {@link DrmSessionManager} for DRM interactions. + * @param drmEventDispatcher A {@link DrmSessionEventListener.EventDispatcher} to notify of DRM + * events. + * @param initialFormat The first {@link Format} to output. + * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to output. + * @return A new {@link FakeSampleStream}. */ - protected SampleStream createSampleStream( - long positionUs, - TrackSelection selection, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, + protected FakeSampleStream createSampleStream( + Allocator allocator, + @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher) { - FakeSampleStream sampleStream = - new FakeSampleStream( - mediaSourceEventDispatcher, - drmSessionManager, - drmEventDispatcher, - selection.getSelectedFormat(), - trackDataFactory.create( - selection.getSelectedFormat(), - Assertions.checkNotNull(mediaSourceEventDispatcher.mediaPeriodId))); - sampleStream.seekTo(positionUs); - return sampleStream; - } - - /** - * Seeks inside the given sample stream. - * - * @param sampleStream A sample stream that was created by a call to {@link - * #createSampleStream(long, TrackSelection, MediaSourceEventListener.EventDispatcher, - * DrmSessionManager, DrmSessionEventListener.EventDispatcher)}. - * @param positionUs The position to seek to, in microseconds. - */ - protected void seekSampleStream(SampleStream sampleStream, long positionUs) { - // Queue a single sample from the seek position again. - ((FakeSampleStream) sampleStream).seekTo(positionUs); - } - - /** - * Releases the given sample stream. - * - * @param sampleStream A sample stream that was created by a call to {@link - * #createSampleStream(long, TrackSelection, MediaSourceEventListener.EventDispatcher, - * DrmSessionManager, DrmSessionEventListener.EventDispatcher)}. - */ - protected void releaseSampleStream(SampleStream sampleStream) { - ((FakeSampleStream) sampleStream).release(); + DrmSessionEventListener.EventDispatcher drmEventDispatcher, + Format initialFormat, + List fakeSampleStreamItems) { + return new FakeSampleStream( + allocator, + mediaSourceEventDispatcher, + drmSessionManager, + drmEventDispatcher, + initialFormat, + fakeSampleStreamItems); } private void finishPreparation() { @@ -395,27 +421,12 @@ public class FakeMediaPeriod implements MediaPeriod { /* mediaEndTimeUs = */ C.TIME_UNSET); } - /** A factory to create the test data for a particular track. */ - public interface TrackDataFactory { - - /** - * Returns the list of {@link FakeSampleStreamItem}s that will be passed to {@link - * FakeSampleStream#FakeSampleStream(MediaSourceEventListener.EventDispatcher, - * DrmSessionManager, DrmSessionEventListener.EventDispatcher, Format, List)}. - * - * @param format The format of the track to provide data for. - * @param mediaPeriodId The {@link MediaPeriodId} to provide data for. - * @return The track data in the form of {@link FakeSampleStreamItem}s. - */ - List create(Format format, MediaPeriodId mediaPeriodId); - - /** - * Returns a factory that always provides a single sample with {@code time=sampleTimeUs} and - * then end-of-stream. - */ - static TrackDataFactory singleSampleWithTimeUs(long sampleTimeUs) { - return (unusedFormat, unusedMediaPeriodId) -> - ImmutableList.of(oneByteSample(sampleTimeUs), END_OF_STREAM_ITEM); + private boolean isLoadingFinished() { + for (FakeSampleStream sampleStream : sampleStreams) { + if (!sampleStream.isLoadingFinished()) { + return false; + } } + return true; } } 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 b0afa7cce2..77d543a36e 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 @@ -343,6 +343,7 @@ public class FakeMediaSource extends BaseMediaSource { long defaultFirstSampleTimeUs = positionInWindowUs >= 0 || id.isAd() ? 0 : -positionInWindowUs; return new FakeMediaPeriod( trackGroupArray, + allocator, trackDataFactory != null ? trackDataFactory : TrackDataFactory.singleSampleWithTimeUs(defaultFirstSampleTimeUs), 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 eaa2fb52bb..52f0f29193 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,55 +15,38 @@ */ package com.google.android.exoplayer2.testutil; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.C.BufferFlags; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.drm.DrmInitData; -import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.MediaSourceEventListener; +import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; -import com.google.common.collect.Iterables; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * Fake {@link SampleStream} that outputs a given {@link Format}, any amount of {@link - * FakeSampleStreamItem items}, then end of stream. + * Fake {@link SampleStream} that outputs a given {@link Format} and any amount of {@link + * FakeSampleStreamItem items}. */ public class FakeSampleStream implements SampleStream { - private static class SampleInfo { - private final byte[] data; - @C.BufferFlags private final int flags; - private final long timeUs; - - private SampleInfo(byte[] data, @C.BufferFlags int flags, long timeUs) { - this.data = Arrays.copyOf(data, data.length); - this.flags = flags; - this.timeUs = timeUs; - } - } - /** Item to customize a return value of {@link FakeSampleStream#readData}. */ public static final class FakeSampleStreamItem { - /** - * Item that designates the end of stream has been reached. - * - *

When this item is read, readData will repeatedly return end of stream. - */ + /** Item that designates the end of stream has been reached. */ public static final FakeSampleStreamItem END_OF_STREAM_ITEM = sample( /* timeUs= */ Long.MAX_VALUE, @@ -92,10 +75,9 @@ public class FakeSampleStream implements SampleStream { *

The sample will contain a single byte of data. * * @param timeUs The timestamp of the sample. - * @param flags The buffer flags that will be set when reading this sample through {@link - * FakeSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)}. + * @param flags The sample {@link C.BufferFlags}. */ - public static FakeSampleStreamItem oneByteSample(long timeUs, @BufferFlags int flags) { + public static FakeSampleStreamItem oneByteSample(long timeUs, @C.BufferFlags int flags) { return sample(timeUs, flags, new byte[] {0}); } @@ -103,12 +85,11 @@ public class FakeSampleStream implements SampleStream { * Creates an item representing a sample with the provided timestamp, flags and data. * * @param timeUs The timestamp of the sample. - * @param flags The buffer flags that will be set when reading this sample through {@link - * FakeSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)}. + * @param flags The sample {@link C.BufferFlags}. * @param sampleData The sample data. */ public static FakeSampleStreamItem sample( - long timeUs, @BufferFlags int flags, byte[] sampleData) { + long timeUs, @C.BufferFlags int flags, byte[] sampleData) { return new FakeSampleStreamItem( /* format= */ null, new SampleInfo(sampleData.clone(), flags, timeUs)); } @@ -126,218 +107,205 @@ public class FakeSampleStream implements SampleStream { } } + private final SampleQueue sampleQueue; @Nullable private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; - private final Format initialFormat; - private final List fakeSampleStreamItems; - private final DrmSessionManager drmSessionManager; - private final DrmSessionEventListener.EventDispatcher drmEventDispatcher; + private final List sampleStreamItems; - private int sampleItemIndex; - private @MonotonicNonNull Format downstreamFormat; - private boolean readEOSBuffer; - @Nullable private DrmSession currentDrmSession; + private int sampleStreamItemsWritePosition; + private boolean loadingFinished; + @Nullable private Format downstreamFormat; + @Nullable private Format notifiedDownstreamFormat; /** * Creates a fake sample stream which outputs the given {@link Format} followed by the provided * {@link FakeSampleStreamItem items}. * + * @param allocator An {@link Allocator}. * @param mediaSourceEventDispatcher A {@link MediaSourceEventListener.EventDispatcher} to notify * of media events. * @param drmSessionManager A {@link DrmSessionManager} for DRM interactions. * @param drmEventDispatcher A {@link DrmSessionEventListener.EventDispatcher} to notify of DRM * events. * @param initialFormat The first {@link Format} to output. - * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to customize the return - * values of {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. This is assumed to - * be in ascending order of sampleTime. Note that once an EOS buffer has been read, that will - * return every time readData is called. This should usually end with {@link - * FakeSampleStreamItem#END_OF_STREAM_ITEM}. + * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to output. */ public FakeSampleStream( + Allocator allocator, @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, Format initialFormat, List fakeSampleStreamItems) { + this.sampleQueue = + SampleQueue.createWithDrm( + allocator, + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + drmSessionManager, + drmEventDispatcher); this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; - this.drmSessionManager = drmSessionManager; - this.drmEventDispatcher = drmEventDispatcher; - this.initialFormat = initialFormat; - this.fakeSampleStreamItems = new ArrayList<>(fakeSampleStreamItems); + this.sampleStreamItems = new ArrayList<>(); + sampleStreamItems.add(FakeSampleStreamItem.format(initialFormat)); + sampleStreamItems.addAll(fakeSampleStreamItems); } /** - * Seeks inside this sample stream. + * Appends {@link FakeSampleStreamItem FakeSampleStreamItems} to the list of items that should be + * written to the queue. * - *

Seeks to just before the first sample with {@code sampleTime >= timeUs}, or to the end of - * the stream otherwise. + *

Note that this data is only written to the queue once {@link #writeData(long)} is called. + * + * @param items The items to append. */ - public void seekTo(long timeUs) { - Format applicableFormat = initialFormat; - for (int i = 0; i < fakeSampleStreamItems.size(); i++) { - @Nullable SampleInfo sampleInfo = fakeSampleStreamItems.get(i).sampleInfo; + public void append(List items) { + sampleStreamItems.addAll(items); + } + + /** + * Writes all not yet written {@link FakeSampleStreamItem sample stream items} to the sample queue + * starting at the given position. + * + * @param startPositionUs The start position, in microseconds. + */ + public void writeData(long startPositionUs) { + if (sampleStreamItemsWritePosition == 0) { + sampleQueue.setStartTimeUs(startPositionUs); + } + boolean writtenFirstFormat = false; + @Nullable Format pendingFirstFormat = null; + for (int i = 0; i < sampleStreamItems.size(); i++) { + FakeSampleStreamItem fakeSampleStreamItem = sampleStreamItems.get(i); + @Nullable FakeSampleStream.SampleInfo sampleInfo = fakeSampleStreamItem.sampleInfo; if (sampleInfo == null) { - applicableFormat = Assertions.checkNotNull(fakeSampleStreamItems.get(i).format); + if (writtenFirstFormat) { + sampleQueue.format(checkNotNull(fakeSampleStreamItem.format)); + } else { + pendingFirstFormat = checkNotNull(fakeSampleStreamItem.format); + } continue; } - if (sampleInfo.timeUs >= timeUs) { - sampleItemIndex = i; - readEOSBuffer = false; - if (downstreamFormat != null && !applicableFormat.equals(downstreamFormat)) { - notifyEventDispatcher(applicableFormat); + if ((sampleInfo.flags & C.BUFFER_FLAG_END_OF_STREAM) != 0) { + loadingFinished = true; + break; + } + if (sampleInfo.timeUs >= startPositionUs && i >= sampleStreamItemsWritePosition) { + if (!writtenFirstFormat) { + sampleQueue.format(checkNotNull(pendingFirstFormat)); + writtenFirstFormat = true; } - return; + sampleQueue.sampleData(new ParsableByteArray(sampleInfo.data), sampleInfo.data.length); + sampleQueue.sampleMetadata( + sampleInfo.timeUs, + sampleInfo.flags, + sampleInfo.data.length, + /* offset= */ 0, + /* cryptoData= */ null); } } - sampleItemIndex = fakeSampleStreamItems.size(); - @Nullable - FakeSampleStreamItem lastItem = - Iterables.getLast(fakeSampleStreamItems, /* defaultValue= */ null); - readEOSBuffer = - lastItem != null - && lastItem.sampleInfo != null - && ((lastItem.sampleInfo.flags & C.BUFFER_FLAG_END_OF_STREAM) != 0); + sampleStreamItemsWritePosition = sampleStreamItems.size(); } /** - * Adds an item to the end of the queue of {@link FakeSampleStreamItem items}. + * Seeks the stream to a new position using already available data in the queue. * - * @param item The item to add. + * @param positionUs The new position, in microseconds. + * @return Whether seeking inside the available data was possible. */ - public void addFakeSampleStreamItem(FakeSampleStreamItem item) { - this.fakeSampleStreamItems.add(item); + public boolean seekToUs(long positionUs) { + return sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false); + } + + /** + * Resets the sample queue. + * + *

A new call to {@link #writeData(long)} is required to fill the queue again. + */ + public void reset() { + sampleQueue.reset(); + sampleStreamItemsWritePosition = 0; + loadingFinished = false; + } + + /** Returns whether data has been written to the sample queue until the end of stream signal. */ + public boolean isLoadingFinished() { + return loadingFinished; + } + + /** + * Returns the timestamp of the largest queued sample in the queue, or {@link Long#MIN_VALUE} if + * no samples are queued. + */ + public long getLargestQueuedTimestampUs() { + return sampleQueue.getLargestQueuedTimestampUs(); + } + + /** + * Discards data from the queue. + * + * @param positionUs The position to discard to, in microseconds. + * @param toKeyframe Whether to discard to keyframes only. + */ + public void discardTo(long positionUs, boolean toKeyframe) { + sampleQueue.discardTo(positionUs, toKeyframe, /* stopAtReadPosition= */ true); + } + + /** Release the stream and its underlying sample queue. */ + public void release() { + sampleQueue.release(); } @Override public boolean isReady() { - if (sampleItemIndex == fakeSampleStreamItems.size()) { - return readEOSBuffer || downstreamFormat == null; - } - if (fakeSampleStreamItems.get(sampleItemIndex).format != null) { - // A format can be read. - return true; - } - return mayReadSample(); + return sampleQueue.isReady(loadingFinished); + } + + @Override + public void maybeThrowError() throws IOException { + sampleQueue.maybeThrowError(); } @Override public int readData( FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - if (downstreamFormat == null || formatRequired) { - onFormatResult(downstreamFormat == null ? initialFormat : downstreamFormat, formatHolder); - return C.RESULT_FORMAT_READ; + int result = sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished); + if (result == C.RESULT_FORMAT_READ) { + downstreamFormat = checkNotNull(formatHolder.format); } - // Once an EOS buffer has been read, send EOS every time. - if (readEOSBuffer) { - buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); - return C.RESULT_BUFFER_READ; - } - - if (sampleItemIndex < fakeSampleStreamItems.size()) { - FakeSampleStreamItem fakeSampleStreamItem = fakeSampleStreamItems.get(sampleItemIndex); - sampleItemIndex++; - if (fakeSampleStreamItem.format != null) { - onFormatResult(fakeSampleStreamItem.format, formatHolder); - return C.RESULT_FORMAT_READ; - } else { - SampleInfo sampleInfo = Assertions.checkNotNull(fakeSampleStreamItem.sampleInfo); - if (sampleInfo.flags != 0) { - buffer.setFlags(sampleInfo.flags); - if (buffer.isEndOfStream()) { - readEOSBuffer = true; - return C.RESULT_BUFFER_READ; - } - } - if (!mayReadSample()) { - sampleItemIndex--; - return C.RESULT_NOTHING_READ; - } - buffer.timeUs = sampleInfo.timeUs; - buffer.ensureSpaceForWrite(sampleInfo.data.length); - buffer.data.put(sampleInfo.data); - return C.RESULT_BUFFER_READ; - } - } - return C.RESULT_NOTHING_READ; - } - - private void onFormatResult(Format newFormat, FormatHolder outputFormatHolder) { - outputFormatHolder.format = newFormat; - @Nullable - DrmInitData oldDrmInitData = downstreamFormat == null ? null : downstreamFormat.drmInitData; - boolean isFirstFormat = downstreamFormat == null; - downstreamFormat = newFormat; - @Nullable DrmInitData newDrmInitData = newFormat.drmInitData; - outputFormatHolder.drmSession = currentDrmSession; - notifyEventDispatcher(newFormat); - if (!isFirstFormat && Util.areEqual(oldDrmInitData, newDrmInitData)) { - // Nothing to do. - return; - } - // Ensure we acquire the new session before releasing the previous one in case the same session - // is being used for both DrmInitData. - @Nullable DrmSession previousSession = currentDrmSession; - Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper()); - currentDrmSession = - drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat); - outputFormatHolder.drmSession = currentDrmSession; - - if (previousSession != null) { - previousSession.release(drmEventDispatcher); - } - } - - private boolean mayReadSample() { - @Nullable DrmSession drmSession = this.currentDrmSession; - @Nullable - FakeSampleStreamItem nextSample = - Iterables.get(fakeSampleStreamItems, sampleItemIndex, /* defaultValue= */ null); - boolean nextSampleIsClear = - nextSample != null - && nextSample.sampleInfo != null - && (nextSample.sampleInfo.flags & C.BUFFER_FLAG_ENCRYPTED) == 0; - return drmSession == null - || drmSession.getState() == DrmSession.STATE_OPENED_WITH_KEYS - || (nextSampleIsClear && drmSession.playClearSamplesWithoutKeys()); - } - - @Override - public void maybeThrowError() throws IOException { - if (currentDrmSession != null && currentDrmSession.getState() == DrmSession.STATE_ERROR) { - throw Assertions.checkNotNull(currentDrmSession.getError()); + if (result == C.RESULT_BUFFER_READ && !buffer.isFlagsOnly()) { + maybeNotifyDownstreamFormat(buffer.timeUs); } + return result; } @Override public int skipData(long positionUs) { - // TODO: Implement this. - return 0; + int skipCount = sampleQueue.getSkipCount(positionUs, loadingFinished); + sampleQueue.skip(skipCount); + return skipCount; } - /** Release this SampleStream and all underlying resources. */ - public void release() { - if (currentDrmSession != null) { - currentDrmSession.release(drmEventDispatcher); - currentDrmSession = null; + private void maybeNotifyDownstreamFormat(long timeUs) { + if (mediaSourceEventDispatcher != null + && downstreamFormat != null + && !downstreamFormat.equals(notifiedDownstreamFormat)) { + mediaSourceEventDispatcher.downstreamFormatChanged( + MimeTypes.getTrackType(downstreamFormat.sampleMimeType), + downstreamFormat, + C.SELECTION_REASON_UNKNOWN, + /* trackSelectionData= */ null, + timeUs); + notifiedDownstreamFormat = downstreamFormat; } } - private void notifyEventDispatcher(Format format) { - if (mediaSourceEventDispatcher != null) { - @Nullable SampleInfo sampleInfo = null; - for (int i = sampleItemIndex; i < fakeSampleStreamItems.size(); i++) { - sampleInfo = fakeSampleStreamItems.get(i).sampleInfo; - if (sampleInfo != null) { - break; - } - } - long nextSampleTimeUs = sampleInfo != null ? sampleInfo.timeUs : C.TIME_END_OF_SOURCE; - mediaSourceEventDispatcher.downstreamFormatChanged( - C.TRACK_TYPE_UNKNOWN, - format, - C.SELECTION_REASON_UNKNOWN, - /* trackSelectionData= */ null, - /* mediaTimeUs= */ nextSampleTimeUs); + private static class SampleInfo { + public final byte[] data; + @C.BufferFlags public final int flags; + public final long timeUs; + + public SampleInfo(byte[] data, @C.BufferFlags int flags, long timeUs) { + this.data = Arrays.copyOf(data, data.length); + this.flags = flags; + this.timeUs = timeUs; } } }