From b05fa731d8d4ec4d97e248dade78ed94b5d16a03 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 7 Jan 2021 13:30:10 +0000 Subject: [PATCH] Propagate segment properties when merging HLS delta updates Issue: #5011 PiperOrigin-RevId: 350540152 --- .../DefaultHlsPlaylistParserFactory.java | 5 +- .../playlist/DefaultHlsPlaylistTracker.java | 17 +-- .../FilteringHlsPlaylistParserFactory.java | 6 +- .../source/hls/playlist/HlsMediaPlaylist.java | 102 +++++++--------- .../hls/playlist/HlsPlaylistParser.java | 60 ++++++++-- .../playlist/HlsPlaylistParserFactory.java | 6 +- .../DefaultHlsPlaylistTrackerTest.java | 40 +++---- .../playlist/HlsMediaPlaylistParserTest.java | 110 ++++++++++++++++-- 8 files changed, 232 insertions(+), 114 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java index d185e2a3e8..3bb34a0268 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistParserFactory.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.hls.playlist; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.ParsingLoadable; /** Default implementation for {@link HlsPlaylistParserFactory}. */ @@ -27,7 +28,7 @@ public final class DefaultHlsPlaylistParserFactory implements HlsPlaylistParserF @Override public ParsingLoadable.Parser createPlaylistParser( - HlsMasterPlaylist masterPlaylist) { - return new HlsPlaylistParser(masterPlaylist); + HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { + return new HlsPlaylistParser(masterPlaylist, previousMediaPlaylist); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index f41ddce037..afc55a2bfa 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -68,7 +68,6 @@ public final class DefaultHlsPlaylistTracker private final List listeners; private final double playlistStuckTargetDurationCoefficient; - @Nullable private ParsingLoadable.Parser mediaPlaylistParser; @Nullable private EventDispatcher eventDispatcher; @Nullable private Loader initialPlaylistLoader; @Nullable private Handler playlistRefreshHandler; @@ -243,7 +242,6 @@ public final class DefaultHlsPlaylistTracker masterPlaylist = (HlsMasterPlaylist) result; } this.masterPlaylist = masterPlaylist; - mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist); primaryMediaPlaylistUrl = masterPlaylist.variants.get(0).url; createBundles(masterPlaylist.mediaPlaylistUrls); LoadEventInfo loadEventInfo = @@ -671,6 +669,8 @@ public final class DefaultHlsPlaylistTracker } private void loadPlaylistImmediately(Uri playlistRequestUri) { + ParsingLoadable.Parser mediaPlaylistParser = + playlistParserFactory.createPlaylistParser(masterPlaylist, playlistSnapshot); ParsingLoadable mediaPlaylistLoadable = new ParsingLoadable<>( mediaPlaylistDataSource, @@ -691,10 +691,6 @@ public final class DefaultHlsPlaylistTracker private void processLoadedPlaylist( HlsMediaPlaylist loadedPlaylist, LoadEventInfo loadEventInfo) { @Nullable HlsMediaPlaylist oldPlaylist = playlistSnapshot; - loadedPlaylist = - loadedPlaylist.skippedSegmentCount > 0 - ? loadedPlaylist.expandSkippedSegments(checkNotNull(playlistSnapshot)) - : loadedPlaylist; long currentTimeMs = SystemClock.elapsedRealtime(); lastSnapshotLoadMs = currentTimeMs; playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist); @@ -759,9 +755,7 @@ public final class DefaultHlsPlaylistTracker Uri.Builder uriBuilder = playlistUrl.buildUpon(); if (playlistSnapshot.serverControl.canBlockReload) { long targetMediaSequence = - playlistSnapshot.mediaSequence - + playlistSnapshot.segments.size() - + playlistSnapshot.skippedSegmentCount; + playlistSnapshot.mediaSequence + playlistSnapshot.segments.size(); uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(targetMediaSequence)); if (playlistSnapshot.partTargetDurationUs != C.TIME_UNSET) { List trailingParts = playlistSnapshot.trailingParts; @@ -774,9 +768,8 @@ public final class DefaultHlsPlaylistTracker } } if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) { - // TODO: Fix skipped segment merging before re-enabling. - // uriBuilder.appendQueryParameter( - // SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES"); + uriBuilder.appendQueryParameter( + SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES"); } return uriBuilder.build(); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/FilteringHlsPlaylistParserFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/FilteringHlsPlaylistParserFactory.java index 2d7ad5a78a..cab93a1dc3 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/FilteringHlsPlaylistParserFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/FilteringHlsPlaylistParserFactory.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.hls.playlist; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.offline.FilteringManifestParser; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.upstream.ParsingLoadable; @@ -48,8 +49,9 @@ public final class FilteringHlsPlaylistParserFactory implements HlsPlaylistParse @Override public ParsingLoadable.Parser createPlaylistParser( - HlsMasterPlaylist masterPlaylist) { + HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { return new FilteringManifestParser<>( - hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist), streamKeys); + hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist, previousMediaPlaylist), + streamKeys); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 9b9082af9d..c4ab3fc662 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.source.hls.playlist; -import static com.google.android.exoplayer2.util.Assertions.checkArgument; import android.net.Uri; import androidx.annotation.IntDef; @@ -170,6 +169,30 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this.title = title; this.parts = ImmutableList.copyOf(parts); } + + public Segment copyWith(long relativeStartTimeUs, int relativeDiscontinuitySequence) { + List updatedParts = new ArrayList<>(); + long relativePartStartTimeUs = relativeStartTimeUs; + for (int i = 0; i < parts.size(); i++) { + Part part = parts.get(i); + updatedParts.add(part.copyWith(relativePartStartTimeUs, relativeDiscontinuitySequence)); + relativePartStartTimeUs += part.durationUs; + } + return new Segment( + url, + initializationSegment, + title, + durationUs, + relativeDiscontinuitySequence, + relativeStartTimeUs, + drmInitData, + fullSegmentEncryptionKeyUri, + encryptionIV, + byteRangeOffset, + byteRangeLength, + hasGapTag, + updatedParts); + } } /** A media part. */ @@ -226,6 +249,23 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this.isIndependent = isIndependent; this.isPreload = isPreload; } + + public Part copyWith(long relativeStartTimeUs, int relativeDiscontinuitySequence) { + return new Part( + url, + initializationSegment, + durationUs, + relativeDiscontinuitySequence, + relativeStartTimeUs, + drmInitData, + fullSegmentEncryptionKeyUri, + encryptionIV, + byteRangeOffset, + byteRangeLength, + hasGapTag, + isIndependent, + isPreload); + } } /** The base for a {@link Segment} or a {@link Part} required for playback. */ @@ -405,8 +445,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * The list of segments in the playlist. */ public final List segments; - /** The number of skipped segments. */ - public int skippedSegmentCount; /** * The list of parts at the end of the playlist for which the segment is not in the playlist yet. */ @@ -434,7 +472,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * @param hasProgramDateTime See {@link #hasProgramDateTime}. * @param protectionSchemes See {@link #protectionSchemes}. * @param segments See {@link #segments}. - * @param skippedSegmentCount See {@link #skippedSegmentCount}. * @param trailingParts See {@link #trailingParts}. * @param serverControl See {@link #serverControl} * @param renditionReports See {@link #renditionReports}. @@ -456,7 +493,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { boolean hasProgramDateTime, @Nullable DrmInitData protectionSchemes, List segments, - int skippedSegmentCount, List trailingParts, ServerControl serverControl, Map renditionReports) { @@ -473,7 +509,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this.hasProgramDateTime = hasProgramDateTime; this.protectionSchemes = protectionSchemes; this.segments = ImmutableList.copyOf(segments); - this.skippedSegmentCount = skippedSegmentCount; this.trailingParts = ImmutableList.copyOf(trailingParts); this.renditionReports = ImmutableMap.copyOf(renditionReports); if (!trailingParts.isEmpty()) { @@ -509,10 +544,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist { return false; } // The media sequences are equal. - int segmentCount = segments.size() + skippedSegmentCount; - int otherSegmentCount = other.segments.size() + other.skippedSegmentCount; - if (segmentCount != otherSegmentCount) { - return segmentCount > otherSegmentCount; + int segmentCountDifference = segments.size() - other.segments.size(); + if (segmentCountDifference != 0) { + return segmentCountDifference > 0; } int partCount = trailingParts.size(); int otherPartCount = other.trailingParts.size(); @@ -527,52 +561,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { return startTimeUs + durationUs; } - /** - * Merges the skipped segments of the previous playlist and returns a copy with a {@link - * #skippedSegmentCount} of 0. - * - * @param previousPlaylist The previous playlist with a {@link #skippedSegmentCount} of zero. - * @return A new playlist with a complete list of segments. - */ - public HlsMediaPlaylist expandSkippedSegments(HlsMediaPlaylist previousPlaylist) { - if (skippedSegmentCount == 0) { - return this; - } - checkArgument(previousPlaylist.skippedSegmentCount == 0); - List mergedSegments = new ArrayList<>(); - long mediaSequence = this.mediaSequence; - int startIndex = (int) (mediaSequence - previousPlaylist.mediaSequence); - int endIndex = startIndex + skippedSegmentCount; - if (startIndex >= 0 && endIndex <= previousPlaylist.segments.size()) { - mergedSegments.addAll(previousPlaylist.segments.subList(startIndex, endIndex)); - } else { - // Adjust the media sequence if the old playlist doesn't contain all of the skipped segments. - mediaSequence += skippedSegmentCount; - } - mergedSegments.addAll(segments); - return new HlsMediaPlaylist( - playlistType, - baseUri, - tags, - startOffsetUs, - startTimeUs, - hasDiscontinuitySequence, - discontinuitySequence, - mediaSequence, - version, - targetDurationUs, - partTargetDurationUs, - hasIndependentSegments, - hasEndTag, - hasProgramDateTime, - protectionSchemes, - mergedSegments, - /* skippedSegmentCount= */ 0, - trailingParts, - serverControl, - renditionReports); - } - /** * Returns a playlist identical to this one except for the start time, the discontinuity sequence * and {@code hasDiscontinuitySequence} values. The first two are set to the specified values, @@ -600,7 +588,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { hasProgramDateTime, protectionSchemes, segments, - skippedSegmentCount, trailingParts, serverControl, renditionReports); @@ -631,7 +618,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist { hasProgramDateTime, protectionSchemes, segments, - skippedSegmentCount, trailingParts, serverControl, renditionReports); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 5453d752b7..1b4fd34565 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.source.hls.playlist; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Util.castNonNull; import android.net.Uri; import android.text.TextUtils; @@ -211,13 +213,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser= 0 && endIndex <= previousMediaPlaylist.segments.size()) { + // Merge only if all skipped segments are available in the previous playlist. + for (int i = startIndex; i < endIndex; i++) { + Segment segment = previousMediaPlaylist.segments.get(i); + if (mediaSequence != previousMediaPlaylist.mediaSequence) { + // If the media sequences of the playlists are not the same, we need to recreate the + // object with the updated relative start time and the relative discontinuity + // sequence. With identical playlist media sequences these values do not change. + int newRelativeDiscontinuitySequence = + previousMediaPlaylist.discontinuitySequence + - playlistDiscontinuitySequence + + segment.relativeDiscontinuitySequence; + segment = segment.copyWith(segmentStartTimeUs, newRelativeDiscontinuitySequence); + } + segments.add(segment); + segmentStartTimeUs += segment.durationUs; + partStartTimeUs = segmentStartTimeUs; + if (segment.byteRangeLength != C.LENGTH_UNSET) { + segmentByteRangeOffset = segment.byteRangeOffset + segment.byteRangeLength; + } + relativeDiscontinuitySequence = segment.relativeDiscontinuitySequence; + initializationSegment = segment.initializationSegment; + cachedDrmInitData = segment.drmInitData; + fullSegmentEncryptionKeyUri = segment.fullSegmentEncryptionKeyUri; + if (segment.encryptionIV == null + || !segment.encryptionIV.equals(Long.toHexString(segmentMediaSequence))) { + fullSegmentEncryptionIV = segment.encryptionIV; + } + segmentMediaSequence++; + } + } } else if (line.startsWith(TAG_KEY)) { String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions); String keyFormat = @@ -972,7 +1019,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser createPlaylistParser(HlsMasterPlaylist masterPlaylist); + ParsingLoadable.Parser createPlaylistParser( + HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist); } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java index b1ef0fd58c..7fd12abb4f 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.robolectric.RobolectricUtil; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import java.io.IOException; import java.util.ArrayList; @@ -116,7 +116,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -128,14 +128,12 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(firstFullPlaylist.segments).hasSize(6); HlsMediaPlaylist secondFullPlaylist = mediaPlaylists.get(1); assertThat(secondFullPlaylist.mediaSequence).isEqualTo(11); - assertThat(secondFullPlaylist.skippedSegmentCount).isEqualTo(0); assertThat(secondFullPlaylist.segments.get(0).url).isEqualTo("fileSequence11.ts"); assertThat(secondFullPlaylist.segments.get(5).url).isEqualTo("fileSequence16.ts"); assertThat(secondFullPlaylist.segments).hasSize(6); assertThat(secondFullPlaylist.segments).containsNoneIn(firstFullPlaylist.segments); } - @Ignore // Test disabled because playlist delta updates are temporarily disabled. @Test public void start_playlistCanSkip_requestsDeltaUpdateAndExpandsSkippedSegments() throws IOException, TimeoutException, InterruptedException { @@ -150,7 +148,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -160,14 +158,14 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(initialPlaylistWithAllSegments.segments).hasSize(6); HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1); assertThat(mergedPlaylist.mediaSequence).isEqualTo(11); - assertThat(mergedPlaylist.skippedSegmentCount).isEqualTo(0); assertThat(mergedPlaylist.segments).hasSize(6); // First 2 segments of the merged playlist need to be copied from the previous playlist. - assertThat(mergedPlaylist.segments.subList(0, 2)) - .containsExactlyElementsIn(initialPlaylistWithAllSegments.segments.subList(1, 3)) - .inOrder(); - assertThat(mergedPlaylist.segments.get(2).url) - .isEqualTo(initialPlaylistWithAllSegments.segments.get(3).url); + assertThat(mergedPlaylist.segments.get(0).url) + .isEqualTo(initialPlaylistWithAllSegments.segments.get(1).url); + assertThat(mergedPlaylist.segments.get(0).relativeStartTimeUs).isEqualTo(0); + assertThat(mergedPlaylist.segments.get(1).url) + .isEqualTo(initialPlaylistWithAllSegments.segments.get(2).url); + assertThat(mergedPlaylist.segments.get(1).relativeStartTimeUs).isEqualTo(4000000); } @Ignore // Test disabled because playlist delta updates are temporarily disabled. @@ -185,7 +183,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -195,11 +193,9 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(initialPlaylistWithAllSegments.segments).hasSize(6); HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1); assertThat(mergedPlaylist.mediaSequence).isEqualTo(22); - assertThat(mergedPlaylist.skippedSegmentCount).isEqualTo(0); assertThat(mergedPlaylist.segments).hasSize(4); } - @Ignore // Test disabled because playlist delta updates are temporarily disabled. @Test public void start_playlistCanSkipDataRanges_requestsDeltaUpdateV2() throws IOException, TimeoutException, InterruptedException { @@ -214,7 +210,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -224,7 +220,6 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(11); } - @Ignore // Test disabled because playlist delta updates are temporarily disabled. @Test public void start_playlistCanSkipAndUriWithParams_preservesOriginalParams() throws IOException, TimeoutException, InterruptedException { @@ -241,7 +236,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -266,7 +261,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -292,7 +287,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -321,7 +316,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -352,7 +347,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - new DefaultHttpDataSourceFactory(), + new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 2); @@ -365,7 +360,6 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2); } - @Ignore // Test disabled because playlist delta updates are temporarily disabled. @Test public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest() throws IOException, TimeoutException, InterruptedException { @@ -386,7 +380,7 @@ public class DefaultHlsPlaylistTrackerTest { List mediaPlaylists = runPlaylistTrackerAndCollectMediaPlaylists( - /* dataSourceFactory= */ new DefaultHttpDataSourceFactory(), + /* dataSourceFactory= */ new DefaultHttpDataSource.Factory(), Uri.parse(mockWebServer.url("/master.m3u8").toString()), /* awaitedMediaPlaylistCount= */ 3); diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 3246dbb16c..b97c940c95 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -303,24 +303,112 @@ public class HlsMediaPlaylistParserTest { } @Test - public void parseMediaPlaylist_withSkippedSegments_parsesNumberOfSkippedSegments() - throws IOException { + public void parseMediaPlaylist_withSkippedSegments_correctlyMergedSegments() throws IOException { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); + String previousPlaylistString = + "#EXTM3U\n" + + "#EXT-X-TARGETDURATION:4\n" + + "#EXT-X-VERSION:6\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:1234\n" + + "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n" + + "#EXT-X-MEDIA-SEQUENCE:263\n" + + "#EXTINF:4.00008,\n" + + "fileSequence264.mp4\n" + + "#EXT-X-DISCONTINUITY\n" + + "#EXTINF:4.00008,\n" + + "fileSequence265.mp4\n" + + "#EXTINF:4.00008,\n" + + "fileSequence266.mp4"; String playlistString = "#EXTM3U\n" + "#EXT-X-TARGETDURATION:4\n" + "#EXT-X-VERSION:6\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:1234\n" + "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n" - + "#EXT-X-MEDIA-SEQUENCE:266\n" - + "#EXT-X-SKIP:SKIPPED-SEGMENTS=1234\n" + + "#EXT-X-MEDIA-SEQUENCE:265\n" + + "#EXT-X-SKIP:SKIPPED-SEGMENTS=1\n" + "#EXTINF:4.00008,\n" - + "fileSequence266.mp4"; + + "fileSequence266.mp4" + + "#EXTINF:4.00008,\n" + + "fileSequence267.mp4\n"; + InputStream previousInputStream = + new ByteArrayInputStream(Util.getUtf8Bytes(previousPlaylistString)); + HlsMediaPlaylist previousPlaylist = + (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, previousInputStream); InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); HlsMediaPlaylist playlist = - (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); + (HlsMediaPlaylist) + new HlsPlaylistParser(HlsMasterPlaylist.EMPTY, previousPlaylist) + .parse(playlistUri, inputStream); - assertThat(playlist.skippedSegmentCount).isEqualTo(1234); + assertThat(playlist.segments).hasSize(3); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); + assertThat(previousPlaylist.segments.get(0).relativeDiscontinuitySequence).isEqualTo(0); + assertThat(previousPlaylist.segments.get(1).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(previousPlaylist.segments.get(2).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(0).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(1).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(2).relativeDiscontinuitySequence).isEqualTo(1); + } + + @Test + public void parseMediaPlaylist_withSkippedSegments_correctlyMergedParts() throws IOException { + Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); + String previousPlaylistString = + "#EXTM3U\n" + + "#EXT-X-TARGETDURATION:4\n" + + "#EXT-X-VERSION:6\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:1234\n" + + "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n" + + "#EXT-X-MEDIA-SEQUENCE:264\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part264.1.ts\"\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part264.2.ts\"\n" + + "#EXTINF:4.00008,\n" + + "fileSequence264.mp4\n" + + "#EXT-X-DISCONTINUITY\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part265.1.ts\"\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part265.2.ts\"\n" + + "#EXTINF:4.00008,\n" + + "fileSequence265.mp4\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part266.1.ts\"\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part266.2.ts\"\n" + + "#EXTINF:4.00008,\n" + + "fileSequence266.mp4\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\""; + String playlistString = + "#EXTM3U\n" + + "#EXT-X-TARGETDURATION:4\n" + + "#EXT-X-VERSION:6\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:1234\n" + + "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n" + + "#EXT-X-MEDIA-SEQUENCE:265\n" + + "#EXT-X-SKIP:SKIPPED-SEGMENTS=2\n" + + "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\""; + InputStream previousInputStream = + new ByteArrayInputStream(Util.getUtf8Bytes(previousPlaylistString)); + HlsMediaPlaylist previousPlaylist = + (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, previousInputStream); + InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); + + HlsMediaPlaylist playlist = + (HlsMediaPlaylist) + new HlsPlaylistParser(HlsMasterPlaylist.EMPTY, previousPlaylist) + .parse(playlistUri, inputStream); + + assertThat(playlist.segments).hasSize(2); + assertThat(playlist.segments.get(0).relativeStartTimeUs).isEqualTo(0); + assertThat(playlist.segments.get(0).parts.get(0).relativeStartTimeUs).isEqualTo(0); + assertThat(playlist.segments.get(0).parts.get(0).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(0).parts.get(1).relativeStartTimeUs).isEqualTo(2000000); + assertThat(playlist.segments.get(0).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).parts.get(0).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.segments.get(1).parts.get(1).relativeStartTimeUs).isEqualTo(6000079); + assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); + assertThat(playlist.trailingParts.get(0).relativeStartTimeUs).isEqualTo(8000158); + assertThat(playlist.trailingParts.get(0).relativeDiscontinuitySequence).isEqualTo(1); } @Test @@ -1125,7 +1213,9 @@ public class HlsMediaPlaylistParserTest { /* variableDefinitions= */ Collections.emptyMap(), /* sessionKeyDrmInitData= */ Collections.emptyList()); HlsMediaPlaylist playlistWithInheritance = - (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); + (HlsMediaPlaylist) + new HlsPlaylistParser(masterPlaylist, /* previousMediaPlaylist= */ null) + .parse(playlistUri, inputStream); assertThat(playlistWithInheritance.hasIndependentSegments).isTrue(); } @@ -1187,7 +1277,9 @@ public class HlsMediaPlaylistParserTest { variableDefinitions, /* sessionKeyDrmInitData= */ Collections.emptyList()); HlsMediaPlaylist playlist = - (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); + (HlsMediaPlaylist) + new HlsPlaylistParser(masterPlaylist, /* previousMediaPlaylist= */ null) + .parse(playlistUri, inputStream); for (int i = 1; i <= 4; i++) { assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts"); }