From 23cb9532c58b01035f2bc8c346bfa2b0b503703c Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 28 Apr 2016 02:52:51 -0700 Subject: [PATCH] Refactor #6.5: Restore DASH UTC timing element support. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121002218 --- .../exoplayer/dash/DashChunkSource.java | 21 ++++-- .../exoplayer/dash/DashSampleSource.java | 72 +++++++++++++------ .../SmoothStreamingSampleSource.java | 20 ++---- 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 3130c0a3cfd..8843ecaa0ad 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -46,6 +46,8 @@ import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; +import android.os.SystemClock; + import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -62,6 +64,7 @@ public class DashChunkSource implements ChunkSource { private final boolean[] adaptiveFormatBlacklistFlags; private final DataSource dataSource; private final FormatEvaluator adaptiveFormatEvaluator; + private final long elapsedRealtimeOffsetUs; private final Evaluation evaluation; 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 dataSource A {@link DataSource} suitable for loading the media data. * @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, TrackGroup trackGroup, int[] tracks, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator) { + FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs) { this.manifest = manifest; this.adaptationSetIndex = adaptationSetIndex; this.trackGroup = trackGroup; this.dataSource = dataSource; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; + this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000; this.evaluation = new Evaluation(); Period period = manifest.getPeriod(0); @@ -197,9 +204,7 @@ public final void getNextChunk(MediaChunk previous, long playbackPositionUs, Chu return; } - // TODO[REFACTOR]: Bring back UTC timing element support. - long nowUs = System.currentTimeMillis() * 1000; - + long nowUs = getNowUnixTimeUs(); int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum(); int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; @@ -278,6 +283,14 @@ public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { // 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, Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, int trigger) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java index f16bc356592..d6aa32e1a50 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java @@ -33,17 +33,20 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; import com.google.android.exoplayer.dash.mpd.Period; 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.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; 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.Util; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; +import android.util.Log; import android.util.Pair; import java.io.IOException; @@ -53,7 +56,9 @@ /** * 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 manifestFetcher; private final DataSourceFactory dataSourceFactory; @@ -63,8 +68,10 @@ public final class DashSampleSource implements SampleSource { private final LoadControl loadControl; private boolean prepared; + private boolean released; private long durationUs; - private MediaPresentationDescription currentManifest; + private long elapsedRealtimeOffset; + private MediaPresentationDescription manifest; private TrackGroupArray trackGroups; private int[] trackGroupAdaptationSetIndices; private boolean pendingReset; @@ -96,20 +103,24 @@ public boolean prepare(long positionUs) throws IOException { return true; } - if (currentManifest == null) { - currentManifest = manifestFetcher.getManifest(); - if (currentManifest == null) { + if (manifest == null) { + manifest = manifestFetcher.getManifest(); + if (manifest == null) { manifestFetcher.maybeThrowError(); manifestFetcher.requestRefresh(); 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; - buildTrackGroups(currentManifest); - - prepared = true; - return true; + return prepared; } @Override @@ -125,8 +136,6 @@ public TrackGroupArray getTrackGroups() { @Override public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { - Assertions.checkState(prepared); - int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); DashChunkSource[] newChunkSources = new DashChunkSource[newEnabledSourceCount]; ChunkTrackStream[] newTrackStreams = new ChunkTrackStream[newEnabledSourceCount]; @@ -161,16 +170,16 @@ public TrackStream[] selectTracks(List oldStreams, @Override public void continueBuffering(long positionUs) { - if (currentManifest.dynamic) { + if (manifest.dynamic) { MediaPresentationDescription newManifest = manifestFetcher.getManifest(); - if (newManifest != currentManifest) { - currentManifest = newManifest; + if (newManifest != manifest) { + manifest = newManifest; for (DashChunkSource chunkSource : chunkSources) { chunkSource.updateManifest(newManifest); } } - long minUpdatePeriod = currentManifest.minUpdatePeriod; + long minUpdatePeriod = manifest.minUpdatePeriod; if (minUpdatePeriod == 0) { // 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 @@ -233,6 +242,28 @@ public void release() { for (ChunkTrackStream trackStream : trackStreams) { 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. @@ -271,13 +302,14 @@ private Pair buildTrackStream(TrackSelection FormatEvaluator adaptiveEvaluator = selectedTracks.length > 1 ? new AdaptiveEvaluator(bandwidthMeter) : null; int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group]; - AdaptationSet adaptationSet = currentManifest.getPeriod(0).adaptationSets.get( + AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get( adaptationSetIndex); int adaptationSetType = adaptationSet.type; int bufferSize = Util.getDefaultBufferSize(adaptationSetType); DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); - DashChunkSource chunkSource = new DashChunkSource(currentManifest, adaptationSetIndex, - trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator); + DashChunkSource chunkSource = new DashChunkSource(manifest, adaptationSetIndex, + trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator, + elapsedRealtimeOffset); ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize, positionUs, eventHandler, eventListener, adaptationSetType); return Pair.create(chunkSource, trackStream); diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java index cfe4e4d50e4..e2ea582e933 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java @@ -37,7 +37,6 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; 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.MimeTypes; import com.google.android.exoplayer.util.Util; @@ -67,7 +66,6 @@ public final class SmoothStreamingSampleSource implements SampleSource { private final LoadControl loadControl; private final ManifestFetcher manifestFetcher; - private boolean prepared; private long durationUs; private SmoothStreamingManifest currentManifest; private TrackEncryptionBox[] trackEncryptionBoxes; @@ -102,22 +100,20 @@ public SmoothStreamingSampleSource(Uri uri, DataSourceFactory dataSourceFactory, @Override public boolean prepare(long positionUs) throws IOException { - if (prepared) { + if (currentManifest != null) { + // Already prepared. return true; } + currentManifest = manifestFetcher.getManifest(); if (currentManifest == null) { - currentManifest = manifestFetcher.getManifest(); - if (currentManifest == null) { - manifestFetcher.maybeThrowError(); - manifestFetcher.requestRefresh(); - return false; - } + manifestFetcher.maybeThrowError(); + manifestFetcher.requestRefresh(); + return false; } durationUs = currentManifest.durationUs; buildTrackGroups(currentManifest); - ProtectionElement protectionElement = currentManifest.protectionElement; if (protectionElement != null) { byte[] keyId = getProtectionElementKeyId(protectionElement.data); @@ -127,8 +123,6 @@ public boolean prepare(long positionUs) throws IOException { drmInitData.put(protectionElement.uuid, new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); } - - prepared = true; return true; } @@ -145,8 +139,6 @@ public TrackGroupArray getTrackGroups() { @Override public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { - Assertions.checkState(prepared); - int newEnabledSourceCount = trackStreams.length + newSelections.size() - oldStreams.size(); SmoothStreamingChunkSource[] newChunkSources = new SmoothStreamingChunkSource[newEnabledSourceCount];