Skip to content

Commit

Permalink
Apply SeekParameters to DASH + SmoothStreaming playbacks
Browse files Browse the repository at this point in the history
Issue: #2882

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=181314086
  • Loading branch information
ojw28 committed Jan 15, 2018
1 parent 4ee9710 commit ff1bb2f
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 46 deletions.
3 changes: 1 addition & 2 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,22 @@ public SeekParameters(long toleranceBeforeUs, long toleranceAfterUs) {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -378,28 +378,8 @@ public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParame
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -42,7 +43,8 @@ public class ChunkSampleStream<T extends ChunkSource> 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;
Expand Down Expand Up @@ -180,6 +182,21 @@ public long getBufferedPositionUs() {
}
}

/**
* 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,6 +24,16 @@
*/
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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;
Expand Down Expand Up @@ -762,6 +763,44 @@ public static long getPlayoutDurationForMediaDuration(long mediaDuration, float
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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,15 +26,40 @@
*/
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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ public long seekToUs(long positionUs) {

@Override
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -142,6 +143,24 @@ public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
}
}

@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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand All @@ -34,6 +35,7 @@
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;

Expand Down Expand Up @@ -62,7 +64,7 @@ public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThr
}

private final LoaderErrorThrower manifestLoaderErrorThrower;
private final int elementIndex;
private final int streamElementIndex;
private final TrackSelection trackSelection;
private final ChunkExtractorWrapper[] extractorWrappers;
private final DataSource dataSource;
Expand All @@ -75,22 +77,25 @@ public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThr
/**
* @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);
Expand All @@ -106,11 +111,23 @@ public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsMan
}
}

@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;
Expand Down Expand Up @@ -155,7 +172,7 @@ public final void getNextChunk(MediaChunk previous, long playbackPositionUs, lon
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;
Expand Down Expand Up @@ -229,7 +246,7 @@ private long resolveTimeToLiveEdgeUs(long playbackPositionUs) {
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);
Expand Down
Loading

0 comments on commit ff1bb2f

Please sign in to comment.