Use real SampleQueue in FakeSampleStream.

This replaces all the duplicated logic previously implemented in
FakeSampleStream and more closely follows the pattern of how
SampleStreams are used from real MediaPeriods.

Some tests needed adjustments because using real the SampleQueue
improved behaviour:
 - Waiting for isLoading is only needed once even across period
   boundaries because the real SampleQueue doesn't have the on/off
   pattern.
 - AnalyticsCollectorTest.playlistOperations() was wrongly asserting
   that some pre-buffering events. The new version is more intuitively
   correct we pre-buffer the second item during the initial loading
   phase (thus period1seq1) and keep the buffer in the queue after
   the removal operation.

PiperOrigin-RevId: 348440255
This commit is contained in:
tonihei 2020-12-21 11:11:56 +00:00 committed by Oliver Woodman
parent cf0a4e528e
commit 30ee29df1a
12 changed files with 485 additions and 435 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Metadata> runRenderer(byte[] input) throws ExoPlaybackException {
List<Metadata> 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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<FakeSampleStream.FakeSampleStreamItem> 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<SampleStream> sampleStreams;
private final Set<FakeSampleStream> 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<FakeSampleStreamItem> 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<FakeSampleStream.FakeSampleStreamItem> 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<FakeSampleStreamItem> 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;
}
}

View file

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

View file

@ -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.
*
* <p>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 {
* <p>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<FakeSampleStreamItem> fakeSampleStreamItems;
private final DrmSessionManager drmSessionManager;
private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
private final List<FakeSampleStreamItem> 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<FakeSampleStreamItem> 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.
*
* <p>Seeks to just before the first sample with {@code sampleTime >= timeUs}, or to the end of
* the stream otherwise.
* <p>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<FakeSampleStreamItem> 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.
*
* <p>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;
}
}
}