Propagate segment properties when merging HLS delta updates

Issue: #5011
PiperOrigin-RevId: 350540152
This commit is contained in:
bachinger 2021-01-07 13:30:10 +00:00 committed by Ian Baker
parent fc3b91c96c
commit b05fa731d8
8 changed files with 232 additions and 114 deletions

View File

@ -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<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist) {
return new HlsPlaylistParser(masterPlaylist);
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) {
return new HlsPlaylistParser(masterPlaylist, previousMediaPlaylist);
}
}

View File

@ -68,7 +68,6 @@ public final class DefaultHlsPlaylistTracker
private final List<PlaylistEventListener> listeners;
private final double playlistStuckTargetDurationCoefficient;
@Nullable private ParsingLoadable.Parser<HlsPlaylist> 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<HlsPlaylist> mediaPlaylistParser =
playlistParserFactory.createPlaylistParser(masterPlaylist, playlistSnapshot);
ParsingLoadable<HlsPlaylist> 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<Part> 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();
}

View File

@ -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<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist) {
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) {
return new FilteringManifestParser<>(
hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist), streamKeys);
hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist, previousMediaPlaylist),
streamKeys);
}
}

View File

@ -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<Part> 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<Segment> 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<Segment> segments,
int skippedSegmentCount,
List<Part> trailingParts,
ServerControl serverControl,
Map<Uri, RenditionReport> 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<Segment> 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);

View File

@ -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<HlsPlayli
Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}");
private final HlsMasterPlaylist masterPlaylist;
@Nullable private final HlsMediaPlaylist previousMediaPlaylist;
/**
* Creates an instance where media playlists are parsed without inheriting attributes from a
* master playlist.
*/
public HlsPlaylistParser() {
this(HlsMasterPlaylist.EMPTY);
this(HlsMasterPlaylist.EMPTY, /* previousMediaPlaylist= */ null);
}
/**
@ -225,9 +228,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
* playlist.
*
* @param masterPlaylist The master playlist from which media playlists will inherit attributes.
* @param previousMediaPlaylist The previous media playlist from which the new media playlist may
* inherit skipped segments.
*/
public HlsPlaylistParser(HlsMasterPlaylist masterPlaylist) {
public HlsPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) {
this.masterPlaylist = masterPlaylist;
this.previousMediaPlaylist = previousMediaPlaylist;
}
@Override
@ -257,7 +264,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|| line.equals(TAG_ENDLIST)) {
extraLines.add(line);
return parseMediaPlaylist(
masterPlaylist, new LineIterator(extraLines, reader), uri.toString());
masterPlaylist,
previousMediaPlaylist,
new LineIterator(extraLines, reader),
uri.toString());
} else {
extraLines.add(line);
}
@ -603,7 +613,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
HlsMasterPlaylist masterPlaylist,
@Nullable HlsMediaPlaylist previousMediaPlaylist,
LineIterator iterator,
String baseUri)
throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
long startOffsetUs = C.TIME_UNSET;
long mediaSequence = 0;
@ -642,7 +656,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
/* holdBackUs= */ C.TIME_UNSET,
/* partHoldBackUs= */ C.TIME_UNSET,
/* canBlockReload= */ false);
int skippedSegmentCount = 0;
@Nullable DrmInitData playlistProtectionSchemes = null;
@Nullable String fullSegmentEncryptionKeyUri = null;
@ -727,7 +740,41 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions);
} else if (line.startsWith(TAG_SKIP)) {
skippedSegmentCount = parseIntAttr(line, REGEX_SKIPPED_SEGMENTS);
int skippedSegmentCount = parseIntAttr(line, REGEX_SKIPPED_SEGMENTS);
checkState(previousMediaPlaylist != null && segments.isEmpty());
int startIndex = (int) (mediaSequence - castNonNull(previousMediaPlaylist).mediaSequence);
int endIndex = startIndex + skippedSegmentCount;
if (startIndex >= 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<HlsPlayli
/* hasProgramDateTime= */ playlistStartTimeUs != 0,
playlistProtectionSchemes,
segments,
skippedSegmentCount,
trailingParts,
serverControl,
renditionReports);

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls.playlist;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
/** Factory for {@link HlsPlaylist} parsers. */
@ -32,7 +33,10 @@ public interface HlsPlaylistParserFactory {
* {@code masterPlaylist}.
*
* @param masterPlaylist The master playlist that referenced any parsed media playlists.
* @param previousMediaPlaylist The previous media playlist or null if there is no previous media
* playlist.
* @return A parser for HLS playlists.
*/
ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(HlsMasterPlaylist masterPlaylist);
ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist);
}

View File

@ -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<HlsMediaPlaylist> 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<HlsMediaPlaylist> 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<HlsMediaPlaylist> 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<HlsMediaPlaylist> 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<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSourceFactory(),
new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2);
@ -266,7 +261,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSourceFactory(),
new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2);
@ -292,7 +287,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSourceFactory(),
new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2);
@ -321,7 +316,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
new DefaultHttpDataSourceFactory(),
new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 2);
@ -352,7 +347,7 @@ public class DefaultHlsPlaylistTrackerTest {
List<HlsMediaPlaylist> 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<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
/* dataSourceFactory= */ new DefaultHttpDataSourceFactory(),
/* dataSourceFactory= */ new DefaultHttpDataSource.Factory(),
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
/* awaitedMediaPlaylistCount= */ 3);

View File

@ -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");
}