diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/IndexSeekMap.java b/libraries/extractor/src/main/java/androidx/media3/extractor/IndexSeekMap.java index 5068052d85..c21f73d8f1 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/IndexSeekMap.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/IndexSeekMap.java @@ -32,7 +32,8 @@ public final class IndexSeekMap implements SeekMap { private final LongArray positions; private final LongArray timesUs; - private final long durationUs; + + private long durationUs; /** * Creates an instance. @@ -103,4 +104,38 @@ public final class IndexSeekMap implements SeekMap { positions.add(position); timesUs.add(timeUs); } + + /** + * Maps a position (byte offset) to a corresponding sample timestamp. + * + * @param position A seek position (byte offset) relative to the start of the stream. + * @return The corresponding timestamp of the sample at or before the given position, in + * microseconds, or the timestamp of the last available sample in the seek map if the position + * is past the last seek point added, or {@link C#TIME_UNSET} if no seek points exist. + */ + public long getTimeUs(long position) { + if (timesUs.size() == 0) { + return C.TIME_UNSET; + } + int targetIndex = + Util.binarySearchFloor( + positions, position, /* inclusive= */ true, /* stayInBounds= */ true); + return timesUs.get(targetIndex); + } + + /** + * Returns the timestamp of the last seek point added, in microseconds, or {@link C#TIME_UNSET} if + * no seek points exist. + */ + public long getLastSeekPointTimeUs() { + if (timesUs.size() == 0) { + return C.TIME_UNSET; + } + return timesUs.get(timesUs.size() - 1); + } + + /** Sets the duration of the input stream, or {@link C#TIME_UNSET} if it is unknown. */ + public void setDurationUs(long durationUs) { + this.durationUs = durationUs; + } } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/IndexSeeker.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/IndexSeeker.java index e6207d0732..f699e0011e 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/IndexSeeker.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/IndexSeeker.java @@ -15,11 +15,12 @@ */ package androidx.media3.extractor.mp3; +import static androidx.media3.common.util.Assertions.checkState; + import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; -import androidx.media3.common.util.LongArray; import androidx.media3.common.util.Util; -import androidx.media3.extractor.SeekPoint; +import androidx.media3.extractor.IndexSeekMap; import java.math.RoundingMode; /** MP3 seeker that builds a time-to-byte mapping as the stream is read. */ @@ -29,19 +30,16 @@ import java.math.RoundingMode; /* package */ static final long MIN_TIME_BETWEEN_POINTS_US = C.MICROS_PER_SECOND / 10; private final long dataEndPosition; - private final LongArray timesUs; - private final LongArray positions; private final int averageBitrate; - - private long durationUs; + private final IndexSeekMap indexSeekMap; public IndexSeeker(long durationUs, long dataStartPosition, long dataEndPosition) { - this.durationUs = durationUs; + this.indexSeekMap = + new IndexSeekMap( + /* positions= */ new long[] {dataStartPosition}, + /* timesUs= */ new long[] {0L}, + durationUs); this.dataEndPosition = dataEndPosition; - timesUs = new LongArray(); - positions = new LongArray(); - timesUs.add(0L); - positions.add(dataStartPosition); if (durationUs != C.TIME_UNSET) { long bitrate = Util.scaleLargeValue( @@ -55,10 +53,7 @@ import java.math.RoundingMode; @Override public long getTimeUs(long position) { - int targetIndex = - Util.binarySearchFloor( - positions, position, /* inclusive= */ true, /* stayInBounds= */ true); - return timesUs.get(targetIndex); + return indexSeekMap.getTimeUs(position); } @Override @@ -68,26 +63,17 @@ import java.math.RoundingMode; @Override public boolean isSeekable() { - return true; + return indexSeekMap.isSeekable(); } @Override public long getDurationUs() { - return durationUs; + return indexSeekMap.getDurationUs(); } @Override public SeekPoints getSeekPoints(long timeUs) { - int targetIndex = - Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true); - SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex)); - if (seekPoint.timeUs == timeUs || targetIndex == timesUs.size() - 1) { - return new SeekPoints(seekPoint); - } else { - SeekPoint nextSeekPoint = - new SeekPoint(timesUs.get(targetIndex + 1), positions.get(targetIndex + 1)); - return new SeekPoints(seekPoint, nextSeekPoint); - } + return indexSeekMap.getSeekPoints(timeUs); } @Override @@ -107,8 +93,7 @@ import java.math.RoundingMode; if (isTimeUsInIndex(timeUs)) { return; } - timesUs.add(timeUs); - positions.add(position); + indexSeekMap.addSeekPoint(timeUs, position); } /** @@ -118,11 +103,12 @@ import java.math.RoundingMode; * sufficiently close to the last point. */ public boolean isTimeUsInIndex(long timeUs) { - long lastIndexedTimeUs = timesUs.get(timesUs.size() - 1); + long lastIndexedTimeUs = indexSeekMap.getLastSeekPointTimeUs(); + checkState(lastIndexedTimeUs != C.TIME_UNSET); return timeUs - lastIndexedTimeUs < MIN_TIME_BETWEEN_POINTS_US; } /* package */ void setDurationUs(long durationUs) { - this.durationUs = durationUs; + indexSeekMap.setDurationUs(durationUs); } }