mirror of
https://github.com/androidx/media.git
synced 2025-05-06 23:20:42 +08:00
Propagate segment properties when merging HLS delta updates
Issue: #5011 PiperOrigin-RevId: 350540152
This commit is contained in:
parent
fc3b91c96c
commit
b05fa731d8
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user