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 3cbc3e4f6c..8b989bc5a1 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 @@ -652,7 +652,7 @@ public final class ExoPlayerTest { FakeMediaSource mediaSource = new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -696,7 +696,7 @@ public final class ExoPlayerTest { FakeMediaSource mediaSource = new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -728,7 +728,7 @@ public final class ExoPlayerTest { FakeMediaSource mediaSource = new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -974,7 +974,7 @@ public final class ExoPlayerTest { MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -1027,7 +1027,7 @@ public final class ExoPlayerTest { FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -4284,7 +4284,7 @@ public final class ExoPlayerTest { AdPlaybackState.NONE)); return new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -4659,7 +4659,7 @@ public final class ExoPlayerTest { MediaSource mediaSourceWithLoadInProgress = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -7210,7 +7210,7 @@ public final class ExoPlayerTest { MediaSource continuouslyAllocatingMediaSource = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -7287,7 +7287,7 @@ public final class ExoPlayerTest { MediaSource largeBufferAllocatingMediaSource = new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -8023,7 +8023,7 @@ public final class ExoPlayerTest { player.addMediaSource( new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -8068,7 +8068,7 @@ public final class ExoPlayerTest { player.addMediaSource( new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index dda55e4f47..5e39105359 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -32,7 +32,6 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.source.MaskingMediaSource.PlaceholderTimeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; -import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; @@ -562,7 +561,7 @@ public final class ClippingMediaSourceTest { FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline) { @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -579,7 +578,7 @@ public final class ClippingMediaSourceTest { /* trackSelectionData= */ null, C.usToMs(eventStartUs), C.usToMs(eventEndUs))); - return super.createFakeMediaPeriod( + return super.createMediaPeriod( id, trackGroupArray, allocator, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index 4a3b9e923e..6f39dec7cd 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -15,42 +15,58 @@ */ package com.google.android.exoplayer2.testutil; +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.os.SystemClock; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.source.CompositeSequenceableLoader; +import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SequenceableLoader; +import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; 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.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; -import java.util.ArrayList; -import java.util.List; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import java.io.IOException; +import java.util.Set; import org.checkerframework.checker.nullness.compatqual.NullableType; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting a * track will give the player a {@link ChunkSampleStream}. */ -public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod - implements SequenceableLoader.Callback> { +public class FakeAdaptiveMediaPeriod + implements MediaPeriod, SequenceableLoader.Callback> { - private final Allocator allocator; + private static final DataSpec FAKE_DATA_SPEC = new DataSpec(Uri.parse("http://fake.test")); + + private final TrackGroupArray trackGroupArray; + private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; + private final long fakePreparationLoadTaskId; private final FakeChunkSource.Factory chunkSourceFactory; - @Nullable private final TransferListener transferListener; + private final Allocator allocator; private final long durationUs; + @Nullable private final TransferListener transferListener; + private final Set> sampleStreams; - private @MonotonicNonNull Callback callback; - private ChunkSampleStream[] sampleStreams; + @Nullable private Callback callback; + private boolean prepared; private SequenceableLoader sequenceableLoader; public FakeAdaptiveMediaPeriod( @@ -60,54 +76,121 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod FakeChunkSource.Factory chunkSourceFactory, long durationUs, @Nullable TransferListener transferListener) { - super( - trackGroupArray, - /* trackDataFactory= */ (unusedFormat, unusedMediaPeriodId) -> { - throw new RuntimeException("unused track data"); - }, - mediaSourceEventDispatcher, - DrmSessionManager.DUMMY, - new DrmSessionEventListener.EventDispatcher(), - /* deferOnPrepared= */ false); - this.allocator = allocator; + this.trackGroupArray = trackGroupArray; + this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; this.chunkSourceFactory = chunkSourceFactory; - this.transferListener = transferListener; + this.allocator = allocator; this.durationUs = durationUs; - this.sampleStreams = newSampleStreamArray(0); - this.sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); + this.transferListener = transferListener; + sampleStreams = Sets.newIdentityHashSet(); + sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); + fakePreparationLoadTaskId = LoadEventInfo.getNewId(); + } + + /** Releases the media period. */ + public void release() { + prepared = false; + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.release(); + } + sampleStreams.clear(); + sequenceableLoader = new CompositeSequenceableLoader(new SequenceableLoader[0]); } @Override - public synchronized void prepare(Callback callback, long positionUs) { - super.prepare(callback, positionUs); + public void prepare(Callback callback, long positionUs) { + mediaSourceEventDispatcher.loadStarted( + new LoadEventInfo(fakePreparationLoadTaskId, FAKE_DATA_SPEC, SystemClock.elapsedRealtime()), + C.DATA_TYPE_MEDIA, + C.TRACK_TYPE_UNKNOWN, + /* trackFormat= */ null, + C.SELECTION_REASON_UNKNOWN, + /* trackSelectionData= */ null, + /* mediaStartTimeUs= */ 0, + /* mediaEndTimeUs = */ C.TIME_UNSET); this.callback = callback; + prepared = true; + Util.castNonNull(this.callback).onPrepared(this); + mediaSourceEventDispatcher.loadCompleted( + new LoadEventInfo( + fakePreparationLoadTaskId, + FAKE_DATA_SPEC, + FAKE_DATA_SPEC.uri, + /* responseHeaders= */ ImmutableMap.of(), + SystemClock.elapsedRealtime(), + /* loadDurationMs= */ 0, + /* bytesLoaded= */ 100), + C.DATA_TYPE_MEDIA, + C.TRACK_TYPE_UNKNOWN, + /* trackFormat= */ null, + C.SELECTION_REASON_UNKNOWN, + /* trackSelectionData= */ null, + /* mediaStartTimeUs= */ 0, + /* mediaEndTimeUs = */ C.TIME_UNSET); } @Override - @SuppressWarnings("unchecked") + public void maybeThrowPrepareError() throws IOException { + // Do nothing. + } + + @Override + public TrackGroupArray getTrackGroups() { + assertThat(prepared).isTrue(); + return trackGroupArray; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) // Casting sample streams created by this class. + @Override public long selectTracks( @NullableType TrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - long returnPositionUs = super.selectTracks(selections, mayRetainStreamFlags, streams, - streamResetFlags, positionUs); - List> validStreams = new ArrayList<>(); - for (SampleStream stream : streams) { - if (stream != null) { - validStreams.add((ChunkSampleStream) stream); + assertThat(prepared).isTrue(); + int rendererCount = selections.length; + for (int i = 0; i < rendererCount; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + ((ChunkSampleStream) streams[i]).release(); + sampleStreams.remove(streams[i]); + streams[i] = null; + } + if (streams[i] == null && selections[i] != null) { + TrackSelection selection = selections[i]; + assertThat(selection.length()).isAtLeast(1); + TrackGroup trackGroup = selection.getTrackGroup(); + assertThat(trackGroupArray.indexOf(trackGroup)).isNotEqualTo(C.INDEX_UNSET); + int indexInTrackGroup = selection.getIndexInTrackGroup(selection.getSelectedIndex()); + assertThat(indexInTrackGroup).isAtLeast(0); + assertThat(indexInTrackGroup).isLessThan(trackGroup.length); + FakeChunkSource chunkSource = + chunkSourceFactory.createChunkSource(selection, durationUs, transferListener); + ChunkSampleStream sampleStream = + new ChunkSampleStream<>( + MimeTypes.getTrackType(selection.getSelectedFormat().sampleMimeType), + /* embeddedTrackTypes= */ null, + /* embeddedTrackFormats= */ null, + chunkSource, + /* callback= */ this, + allocator, + positionUs, + DrmSessionManager.DUMMY, + new DrmSessionEventListener.EventDispatcher(), + new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3), + mediaSourceEventDispatcher); + streams[i] = sampleStream; + sampleStreams.add(sampleStream); + streamResetFlags[i] = true; } } - sampleStreams = newSampleStreamArray(validStreams.size()); - Util.nullSafeListToArray(validStreams, sampleStreams); - this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); - return returnPositionUs; + sequenceableLoader = + new CompositeSequenceableLoader(sampleStreams.toArray(new ChunkSampleStream[0])); + return seekToUs(positionUs); } @Override public void discardBuffer(long positionUs, boolean toKeyframe) { - super.discardBuffer(positionUs, toKeyframe); for (ChunkSampleStream sampleStream : sampleStreams) { sampleStream.discardBuffer(positionUs, toKeyframe); } @@ -115,26 +198,45 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod @Override public void reevaluateBuffer(long positionUs) { - super.reevaluateBuffer(positionUs); sequenceableLoader.reevaluateBuffer(positionUs); } + @Override + public long readDiscontinuity() { + assertThat(prepared).isTrue(); + return C.TIME_UNSET; + } + @Override public long getBufferedPositionUs() { - super.getBufferedPositionUs(); + assertThat(prepared).isTrue(); return sequenceableLoader.getBufferedPositionUs(); } + @Override + public long seekToUs(long positionUs) { + assertThat(prepared).isTrue(); + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.seekToUs(positionUs); + } + return positionUs; + } + + @Override + public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + return positionUs; + } + @Override public long getNextLoadPositionUs() { - super.getNextLoadPositionUs(); + assertThat(prepared).isTrue(); return sequenceableLoader.getNextLoadPositionUs(); } @Override public boolean continueLoading(long positionUs) { - super.continueLoading(positionUs); - return sequenceableLoader.continueLoading(positionUs); + sequenceableLoader.continueLoading(positionUs); + return true; } @Override @@ -142,51 +244,8 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod return sequenceableLoader.isLoading(); } - @Override - protected final SampleStream createSampleStream( - long positionUs, - TrackSelection trackSelection, - MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, - DrmSessionManager drmSessionManager, - DrmSessionEventListener.EventDispatcher drmEventDispatcher) { - FakeChunkSource chunkSource = - chunkSourceFactory.createChunkSource(trackSelection, durationUs, transferListener); - return new ChunkSampleStream<>( - MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType), - /* embeddedTrackTypes= */ null, - /* embeddedTrackFormats= */ null, - chunkSource, - /* callback= */ this, - allocator, - positionUs, - /* drmSessionManager= */ DrmSessionManager.getDummyDrmSessionManager(), - drmEventDispatcher, - new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3), - mediaSourceEventDispatcher); - } - - @Override - // sampleStream is created by createSampleStream() above. - @SuppressWarnings("unchecked") - protected void seekSampleStream(SampleStream sampleStream, long positionUs) { - ((ChunkSampleStream) sampleStream).seekToUs(positionUs); - } - - @Override - // sampleStream is created by createSampleStream() above. - @SuppressWarnings("unchecked") - protected void releaseSampleStream(SampleStream sampleStream) { - ((ChunkSampleStream) sampleStream).release(); - } - @Override public void onContinueLoadingRequested(ChunkSampleStream source) { Assertions.checkStateNotNull(callback).onContinueLoadingRequested(this); } - - // We won't assign the array to a variable that erases the generic type, and then write into it. - @SuppressWarnings({"unchecked", "rawtypes"}) - private static ChunkSampleStream[] newSampleStreamArray(int length) { - return new ChunkSampleStream[length]; - } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java index d331b33ff4..17759eece1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -50,7 +51,7 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { } @Override - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -68,4 +69,8 @@ public class FakeAdaptiveMediaSource extends FakeMediaSource { transferListener); } + @Override + public void releaseMediaPeriod(MediaPeriod mediaPeriod) { + ((FakeAdaptiveMediaPeriod) mediaPeriod).release(); + } } 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 7485f27cb8..b0afa7cce2 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 @@ -82,7 +82,7 @@ public class FakeMediaSource extends BaseMediaSource { private final TrackGroupArray trackGroupArray; @Nullable private final FakeMediaPeriod.TrackDataFactory trackDataFactory; - private final ArrayList activeMediaPeriods; + private final ArrayList activeMediaPeriods; private final ArrayList createdMediaPeriods; private final DrmSessionManager drmSessionManager; @@ -225,8 +225,8 @@ public class FakeMediaSource extends BaseMediaSource { createEventDispatcher(period.windowIndex, id, period.getPositionInWindowMs()); DrmSessionEventListener.EventDispatcher drmEventDispatcher = createDrmEventDispatcher(period.windowIndex, id); - FakeMediaPeriod mediaPeriod = - createFakeMediaPeriod( + MediaPeriod mediaPeriod = + createMediaPeriod( id, trackGroupArray, allocator, @@ -243,9 +243,8 @@ public class FakeMediaSource extends BaseMediaSource { public void releasePeriod(MediaPeriod mediaPeriod) { assertThat(preparedSource).isTrue(); assertThat(releasedSource).isFalse(); - FakeMediaPeriod fakeMediaPeriod = (FakeMediaPeriod) mediaPeriod; - assertThat(activeMediaPeriods.remove(fakeMediaPeriod)).isTrue(); - fakeMediaPeriod.release(); + assertThat(activeMediaPeriods.remove(mediaPeriod)).isTrue(); + releaseMediaPeriod(mediaPeriod); } @Override @@ -317,7 +316,7 @@ public class FakeMediaSource extends BaseMediaSource { } /** - * Creates a {@link FakeMediaPeriod} for this media source. + * Creates a {@link MediaPeriod} for this media source. * * @param id The identifier of the period. * @param trackGroupArray The {@link TrackGroupArray} supported by the media period. @@ -331,7 +330,7 @@ public class FakeMediaSource extends BaseMediaSource { * @return A new {@link FakeMediaPeriod}. */ @RequiresNonNull("this.timeline") - protected FakeMediaPeriod createFakeMediaPeriod( + protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, @@ -353,6 +352,15 @@ public class FakeMediaSource extends BaseMediaSource { /* deferOnPrepared= */ false); } + /** + * Releases a media period created by {@link #createMediaPeriod(MediaPeriodId, TrackGroupArray, + * Allocator, MediaSourceEventListener.EventDispatcher, DrmSessionManager, + * DrmSessionEventListener.EventDispatcher, TransferListener)}. + */ + protected void releaseMediaPeriod(MediaPeriod mediaPeriod) { + ((FakeMediaPeriod) mediaPeriod).release(); + } + private void finishSourcePreparation(boolean sendManifestLoadEvents) { refreshSourceInfo(Assertions.checkStateNotNull(timeline)); if (!timeline.isEmpty() && sendManifestLoadEvents) {