Apply SeekParameters to DASH + SmoothStreaming playbacks
Issue: #2882 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=181314086
This commit is contained in:
parent
4ee971052b
commit
ff1bb2f702
@ -27,8 +27,7 @@
|
|||||||
performed. The `SeekParameters` class contains defaults for exact seeking and
|
performed. The `SeekParameters` class contains defaults for exact seeking and
|
||||||
seeking to the closest sync points before, either side or after specified seek
|
seeking to the closest sync points before, either side or after specified seek
|
||||||
positions.
|
positions.
|
||||||
* Note: `SeekParameters` are only currently effective when playing
|
* Note: `SeekParameters` are not currently supported when playing HLS streams.
|
||||||
`ExtractorMediaSource`s (i.e. progressive streams).
|
|
||||||
* DASH: Support DASH manifest EventStream elements.
|
* DASH: Support DASH manifest EventStream elements.
|
||||||
* HLS: Add opt-in support for chunkless preparation in HLS. This allows an
|
* HLS: Add opt-in support for chunkless preparation in HLS. This allows an
|
||||||
HLS source to finish preparation without downloading any chunks, which can
|
HLS source to finish preparation without downloading any chunks, which can
|
||||||
|
@ -69,4 +69,22 @@ public final class SeekParameters {
|
|||||||
this.toleranceBeforeUs = toleranceBeforeUs;
|
this.toleranceBeforeUs = toleranceBeforeUs;
|
||||||
this.toleranceAfterUs = toleranceAfterUs;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,28 +378,8 @@ import java.util.Arrays;
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
SeekPoints seekPoints = seekMap.getSeekPoints(positionUs);
|
SeekPoints seekPoints = seekMap.getSeekPoints(positionUs);
|
||||||
long minPositionUs =
|
return Util.resolveSeekPositionUs(
|
||||||
Util.subtractWithOverflowDefault(
|
positionUs, seekParameters, seekPoints.first.timeUs, seekPoints.second.timeUs);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SampleStream methods.
|
// SampleStream methods.
|
||||||
|
@ -19,6 +19,7 @@ import android.util.Log;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
|
import com.google.android.exoplayer2.SeekParameters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.SampleQueue;
|
import com.google.android.exoplayer2.source.SampleQueue;
|
||||||
@ -42,7 +43,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||||||
|
|
||||||
private static final String TAG = "ChunkSampleStream";
|
private static final String TAG = "ChunkSampleStream";
|
||||||
|
|
||||||
private final int primaryTrackType;
|
public final int primaryTrackType;
|
||||||
|
|
||||||
private final int[] embeddedTrackTypes;
|
private final int[] embeddedTrackTypes;
|
||||||
private final boolean[] embeddedTracksSelected;
|
private final boolean[] embeddedTracksSelected;
|
||||||
private final T chunkSource;
|
private final T chunkSource;
|
||||||
@ -180,6 +182,21 @@ public class ChunkSampleStream<T extends ChunkSource> 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.
|
* Seeks to the specified position in microseconds.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.chunk;
|
package com.google.android.exoplayer2.source.chunk;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.SeekParameters;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -23,6 +24,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface ChunkSource {
|
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
|
* If the source is currently having difficulty providing chunks, then this method throws the
|
||||||
* underlying error. Otherwise does nothing.
|
* underlying error. Otherwise does nothing.
|
||||||
|
@ -34,6 +34,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.SeekParameters;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@ -762,6 +763,44 @@ public final class Util {
|
|||||||
return Math.round((double) mediaDuration / speed);
|
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.
|
* Converts a list of integers to a primitive array.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash;
|
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.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
@ -25,15 +26,40 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
|||||||
*/
|
*/
|
||||||
public interface DashChunkSource extends ChunkSource {
|
public interface DashChunkSource extends ChunkSource {
|
||||||
|
|
||||||
|
/** Factory for {@link DashChunkSource}s. */
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
|
/**
|
||||||
DashManifest manifest, int periodIndex, int[] adaptationSetIndices,
|
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
|
||||||
TrackSelection trackSelection, int type, long elapsedRealtimeOffsetMs,
|
* @param manifest The initial manifest.
|
||||||
boolean enableEventMessageTrack, boolean enableCea608Track);
|
* @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);
|
void updateManifest(DashManifest newManifest, int periodIndex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -309,6 +309,11 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
|
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
|
||||||
|
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||||
|
if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) {
|
||||||
|
return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
return positionUs;
|
return positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import android.net.Uri;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.ChunkIndex;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
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
|
@Override
|
||||||
public void updateManifest(DashManifest newManifest, int newPeriodIndex) {
|
public void updateManifest(DashManifest newManifest, int newPeriodIndex) {
|
||||||
try {
|
try {
|
||||||
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.smoothstreaming;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.FragmentedMp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.Track;
|
import com.google.android.exoplayer2.extractor.mp4.Track;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
private final LoaderErrorThrower manifestLoaderErrorThrower;
|
||||||
private final int elementIndex;
|
private final int streamElementIndex;
|
||||||
private final TrackSelection trackSelection;
|
private final TrackSelection trackSelection;
|
||||||
private final ChunkExtractorWrapper[] extractorWrappers;
|
private final ChunkExtractorWrapper[] extractorWrappers;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
@ -75,22 +77,25 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
/**
|
/**
|
||||||
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
|
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
|
||||||
* @param manifest The initial manifest.
|
* @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 trackSelection The track selection.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param trackEncryptionBoxes Track encryption boxes for the stream.
|
* @param trackEncryptionBoxes Track encryption boxes for the stream.
|
||||||
*/
|
*/
|
||||||
public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest,
|
public DefaultSsChunkSource(
|
||||||
int elementIndex, TrackSelection trackSelection, DataSource dataSource,
|
LoaderErrorThrower manifestLoaderErrorThrower,
|
||||||
|
SsManifest manifest,
|
||||||
|
int streamElementIndex,
|
||||||
|
TrackSelection trackSelection,
|
||||||
|
DataSource dataSource,
|
||||||
TrackEncryptionBox[] trackEncryptionBoxes) {
|
TrackEncryptionBox[] trackEncryptionBoxes) {
|
||||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.elementIndex = elementIndex;
|
this.streamElementIndex = streamElementIndex;
|
||||||
this.trackSelection = trackSelection;
|
this.trackSelection = trackSelection;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
|
|
||||||
StreamElement streamElement = manifest.streamElements[elementIndex];
|
StreamElement streamElement = manifest.streamElements[streamElementIndex];
|
||||||
|
|
||||||
extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()];
|
extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()];
|
||||||
for (int i = 0; i < extractorWrappers.length; i++) {
|
for (int i = 0; i < extractorWrappers.length; i++) {
|
||||||
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(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
|
@Override
|
||||||
public void updateManifest(SsManifest newManifest) {
|
public void updateManifest(SsManifest newManifest) {
|
||||||
StreamElement currentElement = manifest.streamElements[elementIndex];
|
StreamElement currentElement = manifest.streamElements[streamElementIndex];
|
||||||
int currentElementChunkCount = currentElement.chunkCount;
|
int currentElementChunkCount = currentElement.chunkCount;
|
||||||
StreamElement newElement = newManifest.streamElements[elementIndex];
|
StreamElement newElement = newManifest.streamElements[streamElementIndex];
|
||||||
if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
|
if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
|
||||||
// There's no overlap between the old and new elements because at least one is empty.
|
// There's no overlap between the old and new elements because at least one is empty.
|
||||||
currentManifestChunkOffset += currentElementChunkCount;
|
currentManifestChunkOffset += currentElementChunkCount;
|
||||||
@ -155,7 +172,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamElement streamElement = manifest.streamElements[elementIndex];
|
StreamElement streamElement = manifest.streamElements[streamElementIndex];
|
||||||
if (streamElement.chunkCount == 0) {
|
if (streamElement.chunkCount == 0) {
|
||||||
// There aren't any chunks for us to load.
|
// There aren't any chunks for us to load.
|
||||||
out.endOfStream = !manifest.isLive;
|
out.endOfStream = !manifest.isLive;
|
||||||
@ -229,7 +246,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamElement currentElement = manifest.streamElements[elementIndex];
|
StreamElement currentElement = manifest.streamElements[streamElementIndex];
|
||||||
int lastChunkIndex = currentElement.chunkCount - 1;
|
int lastChunkIndex = currentElement.chunkCount - 1;
|
||||||
long lastChunkEndTimeUs = currentElement.getStartTimeUs(lastChunkIndex)
|
long lastChunkEndTimeUs = currentElement.getStartTimeUs(lastChunkIndex)
|
||||||
+ currentElement.getChunkDurationUs(lastChunkIndex);
|
+ currentElement.getChunkDurationUs(lastChunkIndex);
|
||||||
|
@ -26,14 +26,31 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
|||||||
*/
|
*/
|
||||||
public interface SsChunkSource extends ChunkSource {
|
public interface SsChunkSource extends ChunkSource {
|
||||||
|
|
||||||
|
/** Factory for {@link SsChunkSource}s. */
|
||||||
interface Factory {
|
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);
|
TrackEncryptionBox[] trackEncryptionBoxes);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the manifest.
|
||||||
|
*
|
||||||
|
* @param newManifest The new manifest.
|
||||||
|
*/
|
||||||
void updateManifest(SsManifest newManifest);
|
void updateManifest(SsManifest newManifest);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -185,6 +185,11 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
|
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
|
||||||
|
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
|
||||||
|
if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) {
|
||||||
|
return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
return positionUs;
|
return positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.Chunk;
|
||||||
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
|
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
|
||||||
import com.google.android.exoplayer2.source.chunk.ChunkSource;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -71,6 +74,17 @@ public final class FakeChunkSource implements ChunkSource {
|
|||||||
this.dataSet = dataSet;
|
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
|
@Override
|
||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user