From a99ef01d3a01dc9619fe2b6751cb44a107c58c33 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 29 Nov 2017 09:09:17 -0800 Subject: [PATCH] Optimize seeking for unseekable SeekMaps - Avoid re-downloading data prior to the first mdat box when seeking back to the start of an unseekable FMP4. - Avoid re-downloading data prior to the first frame for constant bitrate MP3. - Update SeekMap.getPosition documentation to allow a non-zero position for the unseekable case. Note that XingSeeker was already returning a non-zero position if unseekable. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177317256 --- .../android/exoplayer2/extractor/SeekMap.java | 16 ++++++++++++++-- .../extractor/mp3/ConstantBitrateSeeker.java | 2 +- .../extractor/mp4/FragmentedMp4Extractor.java | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java index 778aa4d715..964c43a45a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java @@ -28,13 +28,24 @@ public interface SeekMap { final class Unseekable implements SeekMap { private final long durationUs; + private final long startPosition; /** * @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if * the duration is unknown. */ public Unseekable(long durationUs) { + this(durationUs, 0); + } + + /** + * @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if + * the duration is unknown. + * @param startPosition The position (byte offset) of the start of the media. + */ + public Unseekable(long durationUs, long startPosition) { this.durationUs = durationUs; + this.startPosition = startPosition; } @Override @@ -49,7 +60,7 @@ public interface SeekMap { @Override public long getPosition(long timeUs) { - return 0; + return startPosition; } } @@ -78,7 +89,8 @@ public interface SeekMap { * * @param timeUs A seek position in microseconds. * @return The corresponding position (byte offset) in the stream from which data can be provided - * to the extractor, or 0 if {@code #isSeekable()} returns false. + * to the extractor. If {@link #isSeekable()} returns false then the returned value will be + * independent of {@code timeUs}, and will indicate the start of the media in the stream. */ long getPosition(long timeUs); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index df7748a910..47e12161a8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -43,7 +43,7 @@ import com.google.android.exoplayer2.util.Util; @Override public long getPosition(long timeUs) { if (durationUs == C.TIME_UNSET) { - return 0; + return firstFramePosition; } timeUs = Util.constrainValue(timeUs, 0, durationUs); return firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 4bc1b04418..28a1ffaa7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -345,7 +345,8 @@ public final class FragmentedMp4Extractor implements Extractor { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { - extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); + // This must be the first mdat in the stream. + extractorOutput.seekMap(new SeekMap.Unseekable(durationUs, atomPosition)); haveOutputSeekMap = true; } parserState = STATE_READING_ENCRYPTION_DATA;