From ff1bb2f70287d8ddf1d9d1ddd3ccd92724db7ba1 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 9 Jan 2018 06:52:54 -0800 Subject: [PATCH] Apply SeekParameters to DASH + SmoothStreaming playbacks Issue: #2882 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=181314086 --- RELEASENOTES.md | 3 +- .../android/exoplayer2/SeekParameters.java | 18 +++++++++ .../source/ExtractorMediaPeriod.java | 24 +----------- .../source/chunk/ChunkSampleStream.java | 19 ++++++++- .../exoplayer2/source/chunk/ChunkSource.java | 11 ++++++ .../google/android/exoplayer2/util/Util.java | 39 +++++++++++++++++++ .../source/dash/DashChunkSource.java | 38 +++++++++++++++--- .../source/dash/DashMediaPeriod.java | 5 +++ .../source/dash/DefaultDashChunkSource.java | 19 +++++++++ .../smoothstreaming/DefaultSsChunkSource.java | 39 +++++++++++++------ .../source/smoothstreaming/SsChunkSource.java | 25 ++++++++++-- .../source/smoothstreaming/SsMediaPeriod.java | 5 +++ .../exoplayer2/testutil/FakeChunkSource.java | 14 +++++++ 13 files changed, 213 insertions(+), 46 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2d14d00a49..80381075b8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -27,8 +27,7 @@ performed. The `SeekParameters` class contains defaults for exact seeking and seeking to the closest sync points before, either side or after specified seek positions. - * Note: `SeekParameters` are only currently effective when playing - `ExtractorMediaSource`s (i.e. progressive streams). + * Note: `SeekParameters` are not currently supported when playing HLS streams. * DASH: Support DASH manifest EventStream elements. * HLS: Add opt-in support for chunkless preparation in HLS. This allows an HLS source to finish preparation without downloading any chunks, which can diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SeekParameters.java b/library/core/src/main/java/com/google/android/exoplayer2/SeekParameters.java index 8643b3999e..2df9840cf8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SeekParameters.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SeekParameters.java @@ -69,4 +69,22 @@ public final class SeekParameters { this.toleranceBeforeUs = toleranceBeforeUs; this.toleranceAfterUs = toleranceAfterUs; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SeekParameters other = (SeekParameters) obj; + return toleranceBeforeUs == other.toleranceBeforeUs + && toleranceAfterUs == other.toleranceAfterUs; + } + + @Override + public int hashCode() { + return (31 * (int) toleranceBeforeUs) + (int) toleranceAfterUs; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index e5d1fae7bd..76d9d22648 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -378,28 +378,8 @@ import java.util.Arrays; return 0; } SeekPoints seekPoints = seekMap.getSeekPoints(positionUs); - long minPositionUs = - Util.subtractWithOverflowDefault( - positionUs, seekParameters.toleranceBeforeUs, Long.MIN_VALUE); - long maxPositionUs = - Util.addWithOverflowDefault(positionUs, seekParameters.toleranceAfterUs, Long.MAX_VALUE); - long firstPointUs = seekPoints.first.timeUs; - boolean firstPointValid = minPositionUs <= firstPointUs && firstPointUs <= maxPositionUs; - long secondPointUs = seekPoints.second.timeUs; - boolean secondPointValid = minPositionUs <= secondPointUs && secondPointUs <= maxPositionUs; - if (firstPointValid && secondPointValid) { - if (Math.abs(firstPointUs - positionUs) <= Math.abs(secondPointUs - positionUs)) { - return firstPointUs; - } else { - return secondPointUs; - } - } else if (firstPointValid) { - return firstPointUs; - } else if (secondPointValid) { - return secondPointUs; - } else { - return minPositionUs; - } + return Util.resolveSeekPositionUs( + positionUs, seekParameters, seekPoints.first.timeUs, seekPoints.second.timeUs); } // SampleStream methods. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index a96bc2dcd0..947664720b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -19,6 +19,7 @@ import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.SampleQueue; @@ -42,7 +43,8 @@ public class ChunkSampleStream implements SampleStream, S private static final String TAG = "ChunkSampleStream"; - private final int primaryTrackType; + public final int primaryTrackType; + private final int[] embeddedTrackTypes; private final boolean[] embeddedTracksSelected; private final T chunkSource; @@ -180,6 +182,21 @@ public class ChunkSampleStream implements SampleStream, S } } + /** + * Adjusts a seek position given the specified {@link SeekParameters}. Chunk boundaries are used + * as sync points. + * + * @param positionUs The seek position in microseconds. + * @param seekParameters Parameters that control how the seek is performed. + * @return The adjusted seek position, in microseconds. + */ + public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + // TODO: Using this method to adjust a seek position and then passing the adjusted position to + // seekToUs does not handle small discrepancies between the chunk boundary timestamps obtained + // from the chunk source and the timestamps of the samples in the chunks. + return chunkSource.getAdjustedSeekPositionUs(positionUs, seekParameters); + } + /** * Seeks to the specified position in microseconds. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java index b04dc7cbdb..568461c206 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.chunk; +import com.google.android.exoplayer2.SeekParameters; import java.io.IOException; import java.util.List; @@ -23,6 +24,16 @@ import java.util.List; */ public interface ChunkSource { + /** + * Adjusts a seek position given the specified {@link SeekParameters}. Chunk boundaries are used + * as sync points. + * + * @param positionUs The seek position in microseconds. + * @param seekParameters Parameters that control how the seek is performed. + * @return The adjusted seek position, in microseconds. + */ + long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters); + /** * If the source is currently having difficulty providing chunks, then this method throws the * underlying error. Otherwise does nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index a5f5222820..b3cc282717 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.upstream.DataSource; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -762,6 +763,44 @@ public final class Util { return Math.round((double) mediaDuration / speed); } + /** + * Resolves a seek given the requested seek position, a {@link SeekParameters} and two candidate + * sync points. + * + * @param positionUs The requested seek position, in microseocnds. + * @param seekParameters The {@link SeekParameters}. + * @param firstSyncUs The first candidate seek point, in micrseconds. + * @param secondSyncUs The second candidate seek point, in microseconds. May equal {@code + * firstSyncUs} if there's only one candidate. + * @return The resolved seek position, in microseconds. + */ + public static long resolveSeekPositionUs( + long positionUs, SeekParameters seekParameters, long firstSyncUs, long secondSyncUs) { + if (SeekParameters.EXACT.equals(seekParameters)) { + return positionUs; + } + long minPositionUs = + subtractWithOverflowDefault(positionUs, seekParameters.toleranceBeforeUs, Long.MIN_VALUE); + long maxPositionUs = + addWithOverflowDefault(positionUs, seekParameters.toleranceAfterUs, Long.MAX_VALUE); + boolean firstSyncPositionValid = minPositionUs <= firstSyncUs && firstSyncUs <= maxPositionUs; + boolean secondSyncPositionValid = + minPositionUs <= secondSyncUs && secondSyncUs <= maxPositionUs; + if (firstSyncPositionValid && secondSyncPositionValid) { + if (Math.abs(firstSyncUs - positionUs) <= Math.abs(secondSyncUs - positionUs)) { + return firstSyncUs; + } else { + return secondSyncUs; + } + } else if (firstSyncPositionValid) { + return firstSyncUs; + } else if (secondSyncPositionValid) { + return secondSyncUs; + } else { + return minPositionUs; + } + } + /** * Converts a list of integers to a primitive array. * diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java index 4e25c0e333..167a8d486c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash; +import android.os.SystemClock; import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -25,15 +26,40 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; */ public interface DashChunkSource extends ChunkSource { + /** Factory for {@link DashChunkSource}s. */ interface Factory { - DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, - DashManifest manifest, int periodIndex, int[] adaptationSetIndices, - TrackSelection trackSelection, int type, long elapsedRealtimeOffsetMs, - boolean enableEventMessageTrack, boolean enableCea608Track); - + /** + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param periodIndex The index of the corresponding period in the manifest. + * @param adaptationSetIndices The indices of the corresponding adaptation sets in the period. + * @param trackSelection The track selection. + * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between + * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, + * specified as the server's unix time minus the local elapsed time. If unknown, set to 0. + * @param enableEventMessageTrack Whether the chunks generated by the source may output an event + * message track. + * @param enableCea608Track Whether the chunks generated by the source may output a CEA-608 + * track. + * @return The created {@link DashChunkSource}. + */ + DashChunkSource createDashChunkSource( + LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, + int periodIndex, + int[] adaptationSetIndices, + TrackSelection trackSelection, + int type, + long elapsedRealtimeOffsetMs, + boolean enableEventMessageTrack, + boolean enableCea608Track); } + /** + * Updates the manifest. + * + * @param newManifest The new manifest. + */ void updateManifest(DashManifest newManifest, int periodIndex); - } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index a8f9203cbf..8a69f98653 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -309,6 +309,11 @@ import java.util.Map; @Override public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + for (ChunkSampleStream sampleStream : sampleStreams) { + if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) { + return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters); + } + } return positionUs; } 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 b254c4f09a..1162762f7c 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 @@ -19,6 +19,7 @@ import android.net.Uri; import android.os.SystemClock; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.SeekMap; @@ -142,6 +143,24 @@ public class DefaultDashChunkSource implements DashChunkSource { } } + @Override + public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + // Segments are aligned across representations, so any segment index will do. + for (RepresentationHolder representationHolder : representationHolders) { + if (representationHolder.segmentIndex != null) { + int segmentNum = representationHolder.getSegmentNum(positionUs); + long firstSyncUs = representationHolder.getSegmentStartTimeUs(segmentNum); + long secondSyncUs = + firstSyncUs < positionUs && segmentNum < representationHolder.getSegmentCount() - 1 + ? representationHolder.getSegmentStartTimeUs(segmentNum + 1) + : firstSyncUs; + return Util.resolveSeekPositionUs(positionUs, seekParameters, firstSyncUs, secondSyncUs); + } + } + // We don't have a segment index to adjust the seek position with yet. + return positionUs; + } + @Override public void updateManifest(DashManifest newManifest, int newPeriodIndex) { try { diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index 5a6493b702..79014d6f4a 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.smoothstreaming; import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.mp4.Track; import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; @@ -34,6 +35,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.List; @@ -62,7 +64,7 @@ public class DefaultSsChunkSource implements SsChunkSource { } private final LoaderErrorThrower manifestLoaderErrorThrower; - private final int elementIndex; + private final int streamElementIndex; private final TrackSelection trackSelection; private final ChunkExtractorWrapper[] extractorWrappers; private final DataSource dataSource; @@ -75,22 +77,25 @@ public class DefaultSsChunkSource implements SsChunkSource { /** * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifest The initial manifest. - * @param elementIndex The index of the stream element in the manifest. + * @param streamElementIndex The index of the stream element in the manifest. * @param trackSelection The track selection. * @param dataSource A {@link DataSource} suitable for loading the media data. * @param trackEncryptionBoxes Track encryption boxes for the stream. */ - public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest, - int elementIndex, TrackSelection trackSelection, DataSource dataSource, + public DefaultSsChunkSource( + LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, + int streamElementIndex, + TrackSelection trackSelection, + DataSource dataSource, TrackEncryptionBox[] trackEncryptionBoxes) { this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifest = manifest; - this.elementIndex = elementIndex; + this.streamElementIndex = streamElementIndex; this.trackSelection = trackSelection; this.dataSource = dataSource; - StreamElement streamElement = manifest.streamElements[elementIndex]; - + StreamElement streamElement = manifest.streamElements[streamElementIndex]; extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()]; for (int i = 0; i < extractorWrappers.length; i++) { int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i); @@ -106,11 +111,23 @@ public class DefaultSsChunkSource implements SsChunkSource { } } + @Override + public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + StreamElement streamElement = manifest.streamElements[streamElementIndex]; + int chunkIndex = streamElement.getChunkIndex(positionUs); + long firstSyncUs = streamElement.getStartTimeUs(chunkIndex); + long secondSyncUs = + firstSyncUs < positionUs && chunkIndex < streamElement.chunkCount - 1 + ? streamElement.getStartTimeUs(chunkIndex + 1) + : firstSyncUs; + return Util.resolveSeekPositionUs(positionUs, seekParameters, firstSyncUs, secondSyncUs); + } + @Override public void updateManifest(SsManifest newManifest) { - StreamElement currentElement = manifest.streamElements[elementIndex]; + StreamElement currentElement = manifest.streamElements[streamElementIndex]; int currentElementChunkCount = currentElement.chunkCount; - StreamElement newElement = newManifest.streamElements[elementIndex]; + StreamElement newElement = newManifest.streamElements[streamElementIndex]; if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { // There's no overlap between the old and new elements because at least one is empty. currentManifestChunkOffset += currentElementChunkCount; @@ -155,7 +172,7 @@ public class DefaultSsChunkSource implements SsChunkSource { return; } - StreamElement streamElement = manifest.streamElements[elementIndex]; + StreamElement streamElement = manifest.streamElements[streamElementIndex]; if (streamElement.chunkCount == 0) { // There aren't any chunks for us to load. out.endOfStream = !manifest.isLive; @@ -229,7 +246,7 @@ public class DefaultSsChunkSource implements SsChunkSource { return C.TIME_UNSET; } - StreamElement currentElement = manifest.streamElements[elementIndex]; + StreamElement currentElement = manifest.streamElements[streamElementIndex]; int lastChunkIndex = currentElement.chunkCount - 1; long lastChunkEndTimeUs = currentElement.getStartTimeUs(lastChunkIndex) + currentElement.getChunkDurationUs(lastChunkIndex); diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java index e8815ff424..48491cd0bd 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java @@ -26,14 +26,31 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; */ public interface SsChunkSource extends ChunkSource { + /** Factory for {@link SsChunkSource}s. */ interface Factory { - SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, - SsManifest manifest, int elementIndex, TrackSelection trackSelection, + /** + * Creates a new {@link SsChunkSource}. + * + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param streamElementIndex The index of the corresponding stream element in the manifest. + * @param trackSelection The track selection. + * @param trackEncryptionBoxes Track encryption boxes for the stream. + * @return The created {@link SsChunkSource}. + */ + SsChunkSource createChunkSource( + LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, + int streamElementIndex, + TrackSelection trackSelection, TrackEncryptionBox[] trackEncryptionBoxes); - } + /** + * Updates the manifest. + * + * @param newManifest The new manifest. + */ void updateManifest(SsManifest newManifest); - } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index 5ee60bdeed..99804ca809 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -185,6 +185,11 @@ import java.util.ArrayList; @Override public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + for (ChunkSampleStream sampleStream : sampleStreams) { + if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) { + return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters); + } + } return positionUs; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java index 28f5926bfa..6ff18e0b3d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java @@ -18,6 +18,7 @@ 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.SeekParameters; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ChunkSource; @@ -28,6 +29,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; + import java.io.IOException; import java.util.List; @@ -71,6 +74,17 @@ public final class FakeChunkSource implements ChunkSource { this.dataSet = dataSet; } + @Override + public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { + int chunkIndex = dataSet.getChunkIndexByPosition(positionUs); + long firstSyncUs = dataSet.getStartTime(chunkIndex); + long secondSyncUs = + firstSyncUs < positionUs && chunkIndex < dataSet.getChunkCount() - 1 + ? dataSet.getStartTime(chunkIndex + 1) + : firstSyncUs; + return Util.resolveSeekPositionUs(positionUs, seekParameters, firstSyncUs, secondSyncUs); + } + @Override public void maybeThrowError() throws IOException { // Do nothing.