Refactor #6.5: Restore DASH UTC timing element support.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121002218
This commit is contained in:
olly 2016-04-28 02:52:51 -07:00 committed by Oliver Woodman
parent 0841d043b8
commit 23cb9532c5
3 changed files with 75 additions and 38 deletions

View File

@ -46,6 +46,8 @@ import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.os.SystemClock;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -62,6 +64,7 @@ public class DashChunkSource implements ChunkSource {
private final boolean[] adaptiveFormatBlacklistFlags; private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private final long elapsedRealtimeOffsetUs;
private final Evaluation evaluation; private final Evaluation evaluation;
private MediaPresentationDescription manifest; private MediaPresentationDescription manifest;
@ -77,15 +80,19 @@ public class DashChunkSource implements ChunkSource {
* @param tracks The indices of the selected tracks within the adaptation set. * @param tracks The indices of the selected tracks within the adaptation set.
* @param dataSource A {@link DataSource} suitable for loading the media data. * @param dataSource A {@link DataSource} suitable for loading the media data.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
* @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. It unknown, set to 0.
*/ */
public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex, public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex,
TrackGroup trackGroup, int[] tracks, DataSource dataSource, TrackGroup trackGroup, int[] tracks, DataSource dataSource,
FormatEvaluator adaptiveFormatEvaluator) { FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs) {
this.manifest = manifest; this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex; this.adaptationSetIndex = adaptationSetIndex;
this.trackGroup = trackGroup; this.trackGroup = trackGroup;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
Period period = manifest.getPeriod(0); Period period = manifest.getPeriod(0);
@ -197,9 +204,7 @@ public class DashChunkSource implements ChunkSource {
return; return;
} }
// TODO[REFACTOR]: Bring back UTC timing element support. long nowUs = getNowUnixTimeUs();
long nowUs = System.currentTimeMillis() * 1000;
int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum(); int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
@ -278,6 +283,14 @@ public class DashChunkSource implements ChunkSource {
// Private methods. // Private methods.
private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetUs != 0) {
return (SystemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
} else {
return System.currentTimeMillis() * 1000;
}
}
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
int trigger) { int trigger) {

View File

@ -33,17 +33,20 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.dash.mpd.Period; import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory; import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import java.io.IOException; import java.io.IOException;
@ -53,7 +56,9 @@ import java.util.List;
/** /**
* A {@link SampleSource} for DASH media. * A {@link SampleSource} for DASH media.
*/ */
public final class DashSampleSource implements SampleSource { public final class DashSampleSource implements SampleSource, UtcTimingCallback {
private static final String TAG = "DashSampleSource";
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher; private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private final DataSourceFactory dataSourceFactory; private final DataSourceFactory dataSourceFactory;
@ -63,8 +68,10 @@ public final class DashSampleSource implements SampleSource {
private final LoadControl loadControl; private final LoadControl loadControl;
private boolean prepared; private boolean prepared;
private boolean released;
private long durationUs; private long durationUs;
private MediaPresentationDescription currentManifest; private long elapsedRealtimeOffset;
private MediaPresentationDescription manifest;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private int[] trackGroupAdaptationSetIndices; private int[] trackGroupAdaptationSetIndices;
private boolean pendingReset; private boolean pendingReset;
@ -96,20 +103,24 @@ public final class DashSampleSource implements SampleSource {
return true; return true;
} }
if (currentManifest == null) { if (manifest == null) {
currentManifest = manifestFetcher.getManifest(); manifest = manifestFetcher.getManifest();
if (currentManifest == null) { if (manifest == null) {
manifestFetcher.maybeThrowError(); manifestFetcher.maybeThrowError();
manifestFetcher.requestRefresh(); manifestFetcher.requestRefresh();
return false; return false;
} }
durationUs = manifest.dynamic ? C.UNSET_TIME_US : manifest.duration * 1000;
buildTrackGroups(manifest);
if (manifest.utcTiming != null) {
UtcTimingElementResolver.resolveTimingElement(dataSourceFactory.createDataSource(),
manifest.utcTiming, manifestFetcher.getManifestLoadCompleteTimestamp(), this);
} else {
prepared = true;
}
} }
durationUs = currentManifest.dynamic ? C.UNSET_TIME_US : currentManifest.duration * 1000; return prepared;
buildTrackGroups(currentManifest);
prepared = true;
return true;
} }
@Override @Override
@ -125,8 +136,6 @@ public final class DashSampleSource implements SampleSource {
@Override @Override
public TrackStream[] selectTracks(List<TrackStream> oldStreams, public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) { List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared);
int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size();
DashChunkSource[] newChunkSources = new DashChunkSource[newEnabledSourceCount]; DashChunkSource[] newChunkSources = new DashChunkSource[newEnabledSourceCount];
ChunkTrackStream[] newTrackStreams = new ChunkTrackStream[newEnabledSourceCount]; ChunkTrackStream[] newTrackStreams = new ChunkTrackStream[newEnabledSourceCount];
@ -161,16 +170,16 @@ public final class DashSampleSource implements SampleSource {
@Override @Override
public void continueBuffering(long positionUs) { public void continueBuffering(long positionUs) {
if (currentManifest.dynamic) { if (manifest.dynamic) {
MediaPresentationDescription newManifest = manifestFetcher.getManifest(); MediaPresentationDescription newManifest = manifestFetcher.getManifest();
if (newManifest != currentManifest) { if (newManifest != manifest) {
currentManifest = newManifest; manifest = newManifest;
for (DashChunkSource chunkSource : chunkSources) { for (DashChunkSource chunkSource : chunkSources) {
chunkSource.updateManifest(newManifest); chunkSource.updateManifest(newManifest);
} }
} }
long minUpdatePeriod = currentManifest.minUpdatePeriod; long minUpdatePeriod = manifest.minUpdatePeriod;
if (minUpdatePeriod == 0) { if (minUpdatePeriod == 0) {
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
// minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit // minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit
@ -233,6 +242,28 @@ public final class DashSampleSource implements SampleSource {
for (ChunkTrackStream trackStream : trackStreams) { for (ChunkTrackStream trackStream : trackStreams) {
trackStream.release(); trackStream.release();
} }
released = true;
}
// UtcTimingCallback implementation.
@Override
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
if (released) {
return;
}
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
prepared = true;
}
@Override
public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
if (released) {
return;
}
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
// Be optimistic and continue in the hope that the device clock is correct.
prepared = true;
} }
// Internal methods. // Internal methods.
@ -271,13 +302,14 @@ public final class DashSampleSource implements SampleSource {
FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1 FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1
? new AdaptiveEvaluator(bandwidthMeter) : null; ? new AdaptiveEvaluator(bandwidthMeter) : null;
int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group]; int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group];
AdaptationSet adaptationSet = currentManifest.getPeriod(0).adaptationSets.get( AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(
adaptationSetIndex); adaptationSetIndex);
int adaptationSetType = adaptationSet.type; int adaptationSetType = adaptationSet.type;
int bufferSize = Util.getDefaultBufferSize(adaptationSetType); int bufferSize = Util.getDefaultBufferSize(adaptationSetType);
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
DashChunkSource chunkSource = new DashChunkSource(currentManifest, adaptationSetIndex, DashChunkSource chunkSource = new DashChunkSource(manifest, adaptationSetIndex,
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator); trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
elapsedRealtimeOffset);
ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize, ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize,
positionUs, eventHandler, eventListener, adaptationSetType); positionUs, eventHandler, eventListener, adaptationSetType);
return Pair.create(chunkSource, trackStream); return Pair.create(chunkSource, trackStream);

View File

@ -37,7 +37,6 @@ import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory; import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
@ -67,7 +66,6 @@ public final class SmoothStreamingSampleSource implements SampleSource {
private final LoadControl loadControl; private final LoadControl loadControl;
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher; private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
private boolean prepared;
private long durationUs; private long durationUs;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
private TrackEncryptionBox[] trackEncryptionBoxes; private TrackEncryptionBox[] trackEncryptionBoxes;
@ -102,22 +100,20 @@ public final class SmoothStreamingSampleSource implements SampleSource {
@Override @Override
public boolean prepare(long positionUs) throws IOException { public boolean prepare(long positionUs) throws IOException {
if (prepared) { if (currentManifest != null) {
// Already prepared.
return true; return true;
} }
currentManifest = manifestFetcher.getManifest();
if (currentManifest == null) { if (currentManifest == null) {
currentManifest = manifestFetcher.getManifest(); manifestFetcher.maybeThrowError();
if (currentManifest == null) { manifestFetcher.requestRefresh();
manifestFetcher.maybeThrowError(); return false;
manifestFetcher.requestRefresh();
return false;
}
} }
durationUs = currentManifest.durationUs; durationUs = currentManifest.durationUs;
buildTrackGroups(currentManifest); buildTrackGroups(currentManifest);
ProtectionElement protectionElement = currentManifest.protectionElement; ProtectionElement protectionElement = currentManifest.protectionElement;
if (protectionElement != null) { if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data); byte[] keyId = getProtectionElementKeyId(protectionElement.data);
@ -127,8 +123,6 @@ public final class SmoothStreamingSampleSource implements SampleSource {
drmInitData.put(protectionElement.uuid, drmInitData.put(protectionElement.uuid,
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data));
} }
prepared = true;
return true; return true;
} }
@ -145,8 +139,6 @@ public final class SmoothStreamingSampleSource implements SampleSource {
@Override @Override
public TrackStream[] selectTracks(List<TrackStream> oldStreams, public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) { List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared);
int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size();
SmoothStreamingChunkSource[] newChunkSources = SmoothStreamingChunkSource[] newChunkSources =
new SmoothStreamingChunkSource[newEnabledSourceCount]; new SmoothStreamingChunkSource[newEnabledSourceCount];