diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java new file mode 100644 index 0000000000..68dd322449 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.chunk; + +import java.util.NoSuchElementException; + +/** + * Base class for {@link MediaChunkIterator}s. Handles {@link #next()} and {@link #isEnded()}, and + * provides a bounds check for child classes. + */ +public abstract class BaseMediaChunkIterator implements MediaChunkIterator { + + private final long fromIndex; + private final long toIndex; + + private long currentIndex; + + /** + * Creates base iterator. + * + * @param fromIndex The index at which the iterator will start. + * @param toIndex The last available index. + */ + public BaseMediaChunkIterator(long fromIndex, long toIndex) { + this.fromIndex = fromIndex; + this.toIndex = toIndex; + currentIndex = fromIndex - 1; + } + + @Override + public boolean isEnded() { + return currentIndex > toIndex; + } + + @Override + public boolean next() { + currentIndex++; + return !isEnded(); + } + + /** + * Verifies that the iterator points to a valid element. + * + * @throws NoSuchElementException If the iterator does not point to a valid element. + */ + protected void checkInBounds() { + if (currentIndex < fromIndex || currentIndex > toIndex) { + throw new NoSuchElementException(); + } + } + + /** Returns the current index this iterator is pointing to. */ + protected long getCurrentIndex() { + return currentIndex; + } +} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 41be4eb2f1..d226b78410 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.rawcc.RawCcExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.ChunkHolder; @@ -55,7 +56,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.NoSuchElementException; /** * A default {@link DashChunkSource} implementation. @@ -509,13 +509,9 @@ public class DefaultDashChunkSource implements DashChunkSource { // Protected classes. /** {@link MediaChunkIterator} wrapping a {@link RepresentationHolder}. */ - protected static final class RepresentationSegmentIterator implements MediaChunkIterator { + protected static final class RepresentationSegmentIterator extends BaseMediaChunkIterator { private final RepresentationHolder representationHolder; - private final long firstSegmentNum; - private final long lastAvailableSegmentNum; - - private long segmentNum; /** * Creates iterator. @@ -526,28 +522,15 @@ public class DefaultDashChunkSource implements DashChunkSource { */ public RepresentationSegmentIterator( RepresentationHolder representation, long segmentNum, long lastAvailableSegmentNum) { + super(/* fromIndex= */ segmentNum, /* toIndex= */ lastAvailableSegmentNum); this.representationHolder = representation; - this.firstSegmentNum = segmentNum; - this.segmentNum = segmentNum - 1; - this.lastAvailableSegmentNum = lastAvailableSegmentNum; } @Override - public boolean isEnded() { - return segmentNum > lastAvailableSegmentNum; - } - - @Override - public boolean next() { - segmentNum++; - return segmentNum <= lastAvailableSegmentNum; - } - - @Override - public @Nullable DataSpec getDataSpec() { + public DataSpec getDataSpec() { checkInBounds(); Representation representation = representationHolder.representation; - RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); + RangedUri segmentUri = representationHolder.getSegmentUrl(getCurrentIndex()); Uri resolvedUri = segmentUri.resolveUri(representation.baseUrl); String cacheKey = representation.getCacheKey(); return new DataSpec(resolvedUri, segmentUri.start, segmentUri.length, cacheKey); @@ -556,19 +539,13 @@ public class DefaultDashChunkSource implements DashChunkSource { @Override public long getChunkStartTimeUs() { checkInBounds(); - return representationHolder.getSegmentStartTimeUs(segmentNum); + return representationHolder.getSegmentStartTimeUs(getCurrentIndex()); } @Override public long getChunkEndTimeUs() { checkInBounds(); - return representationHolder.getSegmentEndTimeUs(segmentNum); - } - - private void checkInBounds() { - if (segmentNum < firstSegmentNum || segmentNum > lastAvailableSegmentNum) { - throw new NoSuchElementException(); - } + return representationHolder.getSegmentEndTimeUs(getCurrentIndex()); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java index 82c14a5b32..0fef8db78e 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java @@ -15,9 +15,14 @@ */ package com.google.android.exoplayer2.testutil; +import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; +import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; +import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; +import com.google.android.exoplayer2.upstream.DataSpec; import java.util.Random; /** @@ -63,6 +68,49 @@ public final class FakeAdaptiveDataSet extends FakeDataSet { } + /** {@link MediaChunkIterator} for the chunks defined by a fake adaptive data set. */ + public static final class Iterator extends BaseMediaChunkIterator { + + private final FakeAdaptiveDataSet dataSet; + private final int trackGroupIndex; + + /** + * Create iterator. + * + * @param dataSet The data set to iterate over. + * @param trackGroupIndex The index of the track group to iterate over. + * @param chunkIndex The chunk index to which the iterator points initially. + */ + public Iterator(FakeAdaptiveDataSet dataSet, int trackGroupIndex, int chunkIndex) { + super(/* fromIndex= */ chunkIndex, /* toIndex= */ dataSet.getChunkCount() - 1); + this.dataSet = dataSet; + this.trackGroupIndex = trackGroupIndex; + } + + @Override + public DataSpec getDataSpec() { + checkInBounds(); + String uri = dataSet.getUri(trackGroupIndex); + int chunkIndex = (int) getCurrentIndex(); + Segment fakeDataChunk = dataSet.getData(uri).getSegments().get(chunkIndex); + return new DataSpec( + Uri.parse(uri), fakeDataChunk.byteOffset, fakeDataChunk.length, /* key= */ null); + } + + @Override + public long getChunkStartTimeUs() { + checkInBounds(); + return dataSet.getStartTime((int) getCurrentIndex()); + } + + @Override + public long getChunkEndTimeUs() { + checkInBounds(); + int chunkIndex = (int) getCurrentIndex(); + return dataSet.getStartTime(chunkIndex) + dataSet.getChunkDuration(chunkIndex); + } + } + private final int chunkCount; private final long chunkDurationUs; private final long lastChunkDurationUs; @@ -124,5 +172,4 @@ public final class FakeAdaptiveDataSet extends FakeDataSet { public int getChunkIndexByPosition(long positionUs) { return (int) (positionUs / chunkDurationUs); } - }