From 992cfdecc250d20129516a922536e007fe371a4b Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 4 Nov 2016 10:48:14 -0700 Subject: [PATCH] Feed timestamps from loaded chunks back to the playlist tracker This is the first step towards allowing discontinuities in the playlist tracking. Also changed durationSecs for durationUs in MediaPlaylist.Segment. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=138207732 --- .../playlist/HlsMediaPlaylistParserTest.java | 11 ++--- .../exoplayer2/source/hls/HlsChunkSource.java | 11 +++-- .../exoplayer2/source/hls/HlsMediaChunk.java | 14 ++++-- .../source/hls/playlist/HlsMediaPlaylist.java | 20 +++----- .../hls/playlist/HlsPlaylistParser.java | 13 ++--- .../hls/playlist/HlsPlaylistTracker.java | 49 ++++++++++++++++++- 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index ac99760d87..67ec907d61 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -72,7 +72,6 @@ public class HlsMediaPlaylistParserTest extends TestCase { HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; assertEquals(2679, mediaPlaylist.mediaSequence); - assertEquals(8, mediaPlaylist.targetDurationSecs); assertEquals(3, mediaPlaylist.version); assertEquals(true, mediaPlaylist.hasEndTag); List segments = mediaPlaylist.segments; @@ -80,7 +79,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertEquals(5, segments.size()); assertEquals(4, segments.get(0).discontinuitySequenceNumber); - assertEquals(7.975, segments.get(0).durationSecs); + assertEquals(7975000, segments.get(0).durationUs); assertEquals(false, segments.get(0).isEncrypted); assertEquals(null, segments.get(0).encryptionKeyUri); assertEquals(null, segments.get(0).encryptionIV); @@ -89,7 +88,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url); assertEquals(4, segments.get(1).discontinuitySequenceNumber); - assertEquals(7.975, segments.get(1).durationSecs); + assertEquals(7975000, segments.get(1).durationUs); assertEquals(true, segments.get(1).isEncrypted); assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri); assertEquals("0x1566B", segments.get(1).encryptionIV); @@ -98,7 +97,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url); assertEquals(4, segments.get(2).discontinuitySequenceNumber); - assertEquals(7.941, segments.get(2).durationSecs); + assertEquals(7941000, segments.get(2).durationUs); assertEquals(false, segments.get(2).isEncrypted); assertEquals(null, segments.get(2).encryptionKeyUri); assertEquals(null, segments.get(2).encryptionIV); @@ -107,7 +106,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url); assertEquals(5, segments.get(3).discontinuitySequenceNumber); - assertEquals(7.975, segments.get(3).durationSecs); + assertEquals(7975000, segments.get(3).durationUs); assertEquals(true, segments.get(3).isEncrypted); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri); // 0xA7A == 2682. @@ -118,7 +117,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url); assertEquals(5, segments.get(4).discontinuitySequenceNumber); - assertEquals(7.975, segments.get(4).durationSecs); + assertEquals(7975000, segments.get(4).durationUs); assertEquals(true, segments.get(4).isEncrypted); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri); // 0xA7B == 2683. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 351583a334..cbef07f6fe 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -267,7 +267,7 @@ import java.util.Locale; if (previous != null && !switchingVariant) { startTimeUs = previous.getAdjustedEndTimeUs(); } - long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); + long endTimeUs = startTimeUs + segment.durationUs; Format format = variants[newVariantIndex].format; Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); @@ -322,7 +322,7 @@ import java.util.Locale; // This flag ensures the change of pid between streams does not affect the sample queues. @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = 0; - String codecs = variants[newVariantIndex].format.codecs; + String codecs = format.codecs; if (!TextUtils.isEmpty(codecs)) { // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // exist. If we know from the codec attribute that they don't exist, then we can @@ -353,7 +353,7 @@ import java.util.Locale; // Configure the data source and spec for the chunk. DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null); - out.chunk = new HlsMediaChunk(dataSource, dataSpec, format, + out.chunk = new HlsMediaChunk(dataSource, dataSpec, variants[newVariantIndex], trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, isTimestampMaster, timestampAdjuster, extractor, extractorNeedsInit, switchingVariant, @@ -367,6 +367,11 @@ import java.util.Locale; * @param chunk The chunk whose load has been completed. */ public void onChunkLoadCompleted(Chunk chunk) { + if (chunk instanceof HlsMediaChunk) { + HlsMediaChunk mediaChunk = (HlsMediaChunk) chunk; + playlistTracker.onChunkLoaded(mediaChunk.hlsUrl, mediaChunk.chunkIndex, + mediaChunk.getAdjustedStartTimeUs()); + } if (chunk instanceof HlsInitializationChunk) { lastLoadedInitializationChunk = (HlsInitializationChunk) chunk; } else if (chunk instanceof EncryptionKeyChunk) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 1af0881f1a..25435022c5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -15,12 +15,12 @@ */ package com.google.android.exoplayer2.source.hls; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.source.chunk.MediaChunk; +import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; @@ -49,6 +49,11 @@ import java.util.concurrent.atomic.AtomicInteger; */ public final Extractor extractor; + /** + * The url of the playlist from which this chunk was obtained. + */ + public final HlsUrl hlsUrl; + private final boolean isEncrypted; private final boolean extractorNeedsInit; private final boolean shouldSpliceIn; @@ -64,7 +69,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @param dataSource The source from which the data should be loaded. * @param dataSpec Defines the data to be loaded. - * @param trackFormat See {@link #trackFormat}. + * @param hlsUrl The url of the playlist from which this chunk was obtained. * @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionData See {@link #trackSelectionData}. * @param startTimeUs The start time of the media contained by the chunk, in microseconds. @@ -81,13 +86,14 @@ import java.util.concurrent.atomic.AtomicInteger; * @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionIv For AES encryption chunks, the encryption initialization vector. */ - public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey, byte[] encryptionIv) { - super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trackFormat, + super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); + this.hlsUrl = hlsUrl; this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.isMasterTimestampSource = isMasterTimestampSource; this.timestampAdjuster = timestampAdjuster; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 177546d301..2962d656be 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -31,7 +31,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { public static final class Segment implements Comparable { public final String url; - public final double durationSecs; + public final long durationUs; public final int discontinuitySequenceNumber; public final long startTimeUs; public final boolean isEncrypted; @@ -44,11 +44,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength); } - public Segment(String uri, double durationSecs, int discontinuitySequenceNumber, + public Segment(String uri, long durationUs, int discontinuitySequenceNumber, long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long byterangeOffset, long byterangeLength) { this.url = uri; - this.durationSecs = durationSecs; + this.durationUs = durationUs; this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.startTimeUs = startTimeUs; this.isEncrypted = isEncrypted; @@ -64,28 +64,23 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } public Segment copyWithStartTimeUs(long startTimeUs) { - return new Segment(url, durationSecs, discontinuitySequenceNumber, startTimeUs, isEncrypted, + return new Segment(url, durationUs, discontinuitySequenceNumber, startTimeUs, isEncrypted, encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength); } } - public static final String ENCRYPTION_METHOD_NONE = "NONE"; - public static final String ENCRYPTION_METHOD_AES_128 = "AES-128"; - public final int mediaSequence; - public final int targetDurationSecs; public final int version; public final Segment initializationSegment; public final List segments; public final boolean hasEndTag; public final long durationUs; - public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version, + public HlsMediaPlaylist(String baseUri, int mediaSequence, int version, boolean hasEndTag, Segment initializationSegment, List segments) { super(baseUri, HlsPlaylist.TYPE_MEDIA); this.mediaSequence = mediaSequence; - this.targetDurationSecs = targetDurationSecs; this.version = version; this.hasEndTag = hasEndTag; this.initializationSegment = initializationSegment; @@ -94,8 +89,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { if (!segments.isEmpty()) { Segment first = segments.get(0); Segment last = segments.get(segments.size() - 1); - durationUs = last.startTimeUs + (long) (last.durationSecs * C.MICROS_PER_SECOND) - - first.startTimeUs; + durationUs = last.startTimeUs + last.durationUs - first.startTimeUs; } else { durationUs = 0; } @@ -121,7 +115,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } public HlsMediaPlaylist copyWithSegments(List segments) { - return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, hasEndTag, + return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag, initializationSegment, segments); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 76606fad17..420500615a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -217,7 +217,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(); - double segmentDurationSecs = 0.0; + long segmentDurationUs = 0; int discontinuitySequenceNumber = 0; long segmentStartTimeUs = 0; long segmentByteRangeOffset = 0; @@ -252,7 +252,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(latestPlaylistSnapshot.segments); + int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence; + if (indexOfChunk < 0) { + return; + } + Segment actualSegment = segments.get(indexOfChunk); + long timestampDriftUs = Math.abs(actualSegment.startTimeUs - adjustedStartTimeUs); + if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) { + return; + } + segments.set(indexOfChunk, actualSegment.copyWithStartTimeUs(adjustedStartTimeUs)); + // Propagate the adjustment backwards. + for (int i = indexOfChunk - 1; i >= 0; i--) { + Segment segment = segments.get(i); + segments.set(i, + segment.copyWithStartTimeUs(segments.get(i + 1).startTimeUs - segment.durationUs)); + } + // Propagate the adjustment forward. + int segmentsSize = segments.size(); + for (int i = indexOfChunk + 1; i < segmentsSize; i++) { + Segment segment = segments.get(i); + segments.set(i, + segment.copyWithStartTimeUs(segments.get(i - 1).startTimeUs + segment.durationUs)); + } + latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithSegments(segments); + } + // Loader.Callback implementation. @Override