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:
parent
0841d043b8
commit
23cb9532c5
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentManifest == null) {
|
|
||||||
currentManifest = manifestFetcher.getManifest();
|
currentManifest = manifestFetcher.getManifest();
|
||||||
if (currentManifest == null) {
|
if (currentManifest == null) {
|
||||||
manifestFetcher.maybeThrowError();
|
manifestFetcher.maybeThrowError();
|
||||||
manifestFetcher.requestRefresh();
|
manifestFetcher.requestRefresh();
|
||||||
return false;
|
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];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user