mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +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;
|
package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
|
|
||||||
/** Default implementation for {@link HlsPlaylistParserFactory}. */
|
/** Default implementation for {@link HlsPlaylistParserFactory}. */
|
||||||
@ -27,7 +28,7 @@ public final class DefaultHlsPlaylistParserFactory implements HlsPlaylistParserF
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
|
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
|
||||||
HlsMasterPlaylist masterPlaylist) {
|
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) {
|
||||||
return new HlsPlaylistParser(masterPlaylist);
|
return new HlsPlaylistParser(masterPlaylist, previousMediaPlaylist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
private final List<PlaylistEventListener> listeners;
|
private final List<PlaylistEventListener> listeners;
|
||||||
private final double playlistStuckTargetDurationCoefficient;
|
private final double playlistStuckTargetDurationCoefficient;
|
||||||
|
|
||||||
@Nullable private ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser;
|
|
||||||
@Nullable private EventDispatcher eventDispatcher;
|
@Nullable private EventDispatcher eventDispatcher;
|
||||||
@Nullable private Loader initialPlaylistLoader;
|
@Nullable private Loader initialPlaylistLoader;
|
||||||
@Nullable private Handler playlistRefreshHandler;
|
@Nullable private Handler playlistRefreshHandler;
|
||||||
@ -243,7 +242,6 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
masterPlaylist = (HlsMasterPlaylist) result;
|
masterPlaylist = (HlsMasterPlaylist) result;
|
||||||
}
|
}
|
||||||
this.masterPlaylist = masterPlaylist;
|
this.masterPlaylist = masterPlaylist;
|
||||||
mediaPlaylistParser = playlistParserFactory.createPlaylistParser(masterPlaylist);
|
|
||||||
primaryMediaPlaylistUrl = masterPlaylist.variants.get(0).url;
|
primaryMediaPlaylistUrl = masterPlaylist.variants.get(0).url;
|
||||||
createBundles(masterPlaylist.mediaPlaylistUrls);
|
createBundles(masterPlaylist.mediaPlaylistUrls);
|
||||||
LoadEventInfo loadEventInfo =
|
LoadEventInfo loadEventInfo =
|
||||||
@ -671,6 +669,8 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadPlaylistImmediately(Uri playlistRequestUri) {
|
private void loadPlaylistImmediately(Uri playlistRequestUri) {
|
||||||
|
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
|
||||||
|
playlistParserFactory.createPlaylistParser(masterPlaylist, playlistSnapshot);
|
||||||
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
|
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
|
||||||
new ParsingLoadable<>(
|
new ParsingLoadable<>(
|
||||||
mediaPlaylistDataSource,
|
mediaPlaylistDataSource,
|
||||||
@ -691,10 +691,6 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
private void processLoadedPlaylist(
|
private void processLoadedPlaylist(
|
||||||
HlsMediaPlaylist loadedPlaylist, LoadEventInfo loadEventInfo) {
|
HlsMediaPlaylist loadedPlaylist, LoadEventInfo loadEventInfo) {
|
||||||
@Nullable HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
@Nullable HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
||||||
loadedPlaylist =
|
|
||||||
loadedPlaylist.skippedSegmentCount > 0
|
|
||||||
? loadedPlaylist.expandSkippedSegments(checkNotNull(playlistSnapshot))
|
|
||||||
: loadedPlaylist;
|
|
||||||
long currentTimeMs = SystemClock.elapsedRealtime();
|
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||||
lastSnapshotLoadMs = currentTimeMs;
|
lastSnapshotLoadMs = currentTimeMs;
|
||||||
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
|
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
|
||||||
@ -759,9 +755,7 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
Uri.Builder uriBuilder = playlistUrl.buildUpon();
|
Uri.Builder uriBuilder = playlistUrl.buildUpon();
|
||||||
if (playlistSnapshot.serverControl.canBlockReload) {
|
if (playlistSnapshot.serverControl.canBlockReload) {
|
||||||
long targetMediaSequence =
|
long targetMediaSequence =
|
||||||
playlistSnapshot.mediaSequence
|
playlistSnapshot.mediaSequence + playlistSnapshot.segments.size();
|
||||||
+ playlistSnapshot.segments.size()
|
|
||||||
+ playlistSnapshot.skippedSegmentCount;
|
|
||||||
uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(targetMediaSequence));
|
uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(targetMediaSequence));
|
||||||
if (playlistSnapshot.partTargetDurationUs != C.TIME_UNSET) {
|
if (playlistSnapshot.partTargetDurationUs != C.TIME_UNSET) {
|
||||||
List<Part> trailingParts = playlistSnapshot.trailingParts;
|
List<Part> trailingParts = playlistSnapshot.trailingParts;
|
||||||
@ -774,9 +768,8 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) {
|
if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) {
|
||||||
// TODO: Fix skipped segment merging before re-enabling.
|
uriBuilder.appendQueryParameter(
|
||||||
// uriBuilder.appendQueryParameter(
|
SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES");
|
||||||
// SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES");
|
|
||||||
}
|
}
|
||||||
return uriBuilder.build();
|
return uriBuilder.build();
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
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.FilteringManifestParser;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
@ -48,8 +49,9 @@ public final class FilteringHlsPlaylistParserFactory implements HlsPlaylistParse
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
|
public ParsingLoadable.Parser<HlsPlaylist> createPlaylistParser(
|
||||||
HlsMasterPlaylist masterPlaylist) {
|
HlsMasterPlaylist masterPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) {
|
||||||
return new FilteringManifestParser<>(
|
return new FilteringManifestParser<>(
|
||||||
hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist), streamKeys);
|
hlsPlaylistParserFactory.createPlaylistParser(masterPlaylist, previousMediaPlaylist),
|
||||||
|
streamKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
@ -170,6 +169,30 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
this.title = title;
|
this.title = title;
|
||||||
this.parts = ImmutableList.copyOf(parts);
|
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. */
|
/** A media part. */
|
||||||
@ -226,6 +249,23 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
this.isIndependent = isIndependent;
|
this.isIndependent = isIndependent;
|
||||||
this.isPreload = isPreload;
|
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. */
|
/** 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.
|
* The list of segments in the playlist.
|
||||||
*/
|
*/
|
||||||
public final List<Segment> segments;
|
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.
|
* 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 hasProgramDateTime See {@link #hasProgramDateTime}.
|
||||||
* @param protectionSchemes See {@link #protectionSchemes}.
|
* @param protectionSchemes See {@link #protectionSchemes}.
|
||||||
* @param segments See {@link #segments}.
|
* @param segments See {@link #segments}.
|
||||||
* @param skippedSegmentCount See {@link #skippedSegmentCount}.
|
|
||||||
* @param trailingParts See {@link #trailingParts}.
|
* @param trailingParts See {@link #trailingParts}.
|
||||||
* @param serverControl See {@link #serverControl}
|
* @param serverControl See {@link #serverControl}
|
||||||
* @param renditionReports See {@link #renditionReports}.
|
* @param renditionReports See {@link #renditionReports}.
|
||||||
@ -456,7 +493,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
boolean hasProgramDateTime,
|
boolean hasProgramDateTime,
|
||||||
@Nullable DrmInitData protectionSchemes,
|
@Nullable DrmInitData protectionSchemes,
|
||||||
List<Segment> segments,
|
List<Segment> segments,
|
||||||
int skippedSegmentCount,
|
|
||||||
List<Part> trailingParts,
|
List<Part> trailingParts,
|
||||||
ServerControl serverControl,
|
ServerControl serverControl,
|
||||||
Map<Uri, RenditionReport> renditionReports) {
|
Map<Uri, RenditionReport> renditionReports) {
|
||||||
@ -473,7 +509,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
this.hasProgramDateTime = hasProgramDateTime;
|
this.hasProgramDateTime = hasProgramDateTime;
|
||||||
this.protectionSchemes = protectionSchemes;
|
this.protectionSchemes = protectionSchemes;
|
||||||
this.segments = ImmutableList.copyOf(segments);
|
this.segments = ImmutableList.copyOf(segments);
|
||||||
this.skippedSegmentCount = skippedSegmentCount;
|
|
||||||
this.trailingParts = ImmutableList.copyOf(trailingParts);
|
this.trailingParts = ImmutableList.copyOf(trailingParts);
|
||||||
this.renditionReports = ImmutableMap.copyOf(renditionReports);
|
this.renditionReports = ImmutableMap.copyOf(renditionReports);
|
||||||
if (!trailingParts.isEmpty()) {
|
if (!trailingParts.isEmpty()) {
|
||||||
@ -509,10 +544,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// The media sequences are equal.
|
// The media sequences are equal.
|
||||||
int segmentCount = segments.size() + skippedSegmentCount;
|
int segmentCountDifference = segments.size() - other.segments.size();
|
||||||
int otherSegmentCount = other.segments.size() + other.skippedSegmentCount;
|
if (segmentCountDifference != 0) {
|
||||||
if (segmentCount != otherSegmentCount) {
|
return segmentCountDifference > 0;
|
||||||
return segmentCount > otherSegmentCount;
|
|
||||||
}
|
}
|
||||||
int partCount = trailingParts.size();
|
int partCount = trailingParts.size();
|
||||||
int otherPartCount = other.trailingParts.size();
|
int otherPartCount = other.trailingParts.size();
|
||||||
@ -527,52 +561,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
return startTimeUs + durationUs;
|
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
|
* 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,
|
* and {@code hasDiscontinuitySequence} values. The first two are set to the specified values,
|
||||||
@ -600,7 +588,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
hasProgramDateTime,
|
hasProgramDateTime,
|
||||||
protectionSchemes,
|
protectionSchemes,
|
||||||
segments,
|
segments,
|
||||||
skippedSegmentCount,
|
|
||||||
trailingParts,
|
trailingParts,
|
||||||
serverControl,
|
serverControl,
|
||||||
renditionReports);
|
renditionReports);
|
||||||
@ -631,7 +618,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
hasProgramDateTime,
|
hasProgramDateTime,
|
||||||
protectionSchemes,
|
protectionSchemes,
|
||||||
segments,
|
segments,
|
||||||
skippedSegmentCount,
|
|
||||||
trailingParts,
|
trailingParts,
|
||||||
serverControl,
|
serverControl,
|
||||||
renditionReports);
|
renditionReports);
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
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.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.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@ -211,13 +213,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}");
|
Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}");
|
||||||
|
|
||||||
private final HlsMasterPlaylist masterPlaylist;
|
private final HlsMasterPlaylist masterPlaylist;
|
||||||
|
@Nullable private final HlsMediaPlaylist previousMediaPlaylist;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance where media playlists are parsed without inheriting attributes from a
|
* Creates an instance where media playlists are parsed without inheriting attributes from a
|
||||||
* master playlist.
|
* master playlist.
|
||||||
*/
|
*/
|
||||||
public HlsPlaylistParser() {
|
public HlsPlaylistParser() {
|
||||||
this(HlsMasterPlaylist.EMPTY);
|
this(HlsMasterPlaylist.EMPTY, /* previousMediaPlaylist= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,9 +228,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
* playlist.
|
* playlist.
|
||||||
*
|
*
|
||||||
* @param masterPlaylist The master playlist from which media playlists will inherit attributes.
|
* @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.masterPlaylist = masterPlaylist;
|
||||||
|
this.previousMediaPlaylist = previousMediaPlaylist;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -257,7 +264,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
|| line.equals(TAG_ENDLIST)) {
|
|| line.equals(TAG_ENDLIST)) {
|
||||||
extraLines.add(line);
|
extraLines.add(line);
|
||||||
return parseMediaPlaylist(
|
return parseMediaPlaylist(
|
||||||
masterPlaylist, new LineIterator(extraLines, reader), uri.toString());
|
masterPlaylist,
|
||||||
|
previousMediaPlaylist,
|
||||||
|
new LineIterator(extraLines, reader),
|
||||||
|
uri.toString());
|
||||||
} else {
|
} else {
|
||||||
extraLines.add(line);
|
extraLines.add(line);
|
||||||
}
|
}
|
||||||
@ -603,7 +613,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static HlsMediaPlaylist parseMediaPlaylist(
|
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;
|
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
|
||||||
long startOffsetUs = C.TIME_UNSET;
|
long startOffsetUs = C.TIME_UNSET;
|
||||||
long mediaSequence = 0;
|
long mediaSequence = 0;
|
||||||
@ -642,7 +656,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
/* holdBackUs= */ C.TIME_UNSET,
|
/* holdBackUs= */ C.TIME_UNSET,
|
||||||
/* partHoldBackUs= */ C.TIME_UNSET,
|
/* partHoldBackUs= */ C.TIME_UNSET,
|
||||||
/* canBlockReload= */ false);
|
/* canBlockReload= */ false);
|
||||||
int skippedSegmentCount = 0;
|
|
||||||
|
|
||||||
@Nullable DrmInitData playlistProtectionSchemes = null;
|
@Nullable DrmInitData playlistProtectionSchemes = null;
|
||||||
@Nullable String fullSegmentEncryptionKeyUri = 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);
|
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
|
||||||
segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions);
|
segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions);
|
||||||
} else if (line.startsWith(TAG_SKIP)) {
|
} 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)) {
|
} else if (line.startsWith(TAG_KEY)) {
|
||||||
String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
|
String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
|
||||||
String keyFormat =
|
String keyFormat =
|
||||||
@ -972,7 +1019,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
/* hasProgramDateTime= */ playlistStartTimeUs != 0,
|
/* hasProgramDateTime= */ playlistStartTimeUs != 0,
|
||||||
playlistProtectionSchemes,
|
playlistProtectionSchemes,
|
||||||
segments,
|
segments,
|
||||||
skippedSegmentCount,
|
|
||||||
trailingParts,
|
trailingParts,
|
||||||
serverControl,
|
serverControl,
|
||||||
renditionReports);
|
renditionReports);
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
|
|
||||||
/** Factory for {@link HlsPlaylist} parsers. */
|
/** Factory for {@link HlsPlaylist} parsers. */
|
||||||
@ -32,7 +33,10 @@ public interface HlsPlaylistParserFactory {
|
|||||||
* {@code masterPlaylist}.
|
* {@code masterPlaylist}.
|
||||||
*
|
*
|
||||||
* @param masterPlaylist The master playlist that referenced any parsed media playlists.
|
* @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.
|
* @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.source.MediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
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 com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -116,7 +116,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -128,14 +128,12 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
assertThat(firstFullPlaylist.segments).hasSize(6);
|
assertThat(firstFullPlaylist.segments).hasSize(6);
|
||||||
HlsMediaPlaylist secondFullPlaylist = mediaPlaylists.get(1);
|
HlsMediaPlaylist secondFullPlaylist = mediaPlaylists.get(1);
|
||||||
assertThat(secondFullPlaylist.mediaSequence).isEqualTo(11);
|
assertThat(secondFullPlaylist.mediaSequence).isEqualTo(11);
|
||||||
assertThat(secondFullPlaylist.skippedSegmentCount).isEqualTo(0);
|
|
||||||
assertThat(secondFullPlaylist.segments.get(0).url).isEqualTo("fileSequence11.ts");
|
assertThat(secondFullPlaylist.segments.get(0).url).isEqualTo("fileSequence11.ts");
|
||||||
assertThat(secondFullPlaylist.segments.get(5).url).isEqualTo("fileSequence16.ts");
|
assertThat(secondFullPlaylist.segments.get(5).url).isEqualTo("fileSequence16.ts");
|
||||||
assertThat(secondFullPlaylist.segments).hasSize(6);
|
assertThat(secondFullPlaylist.segments).hasSize(6);
|
||||||
assertThat(secondFullPlaylist.segments).containsNoneIn(firstFullPlaylist.segments);
|
assertThat(secondFullPlaylist.segments).containsNoneIn(firstFullPlaylist.segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
|
|
||||||
@Test
|
@Test
|
||||||
public void start_playlistCanSkip_requestsDeltaUpdateAndExpandsSkippedSegments()
|
public void start_playlistCanSkip_requestsDeltaUpdateAndExpandsSkippedSegments()
|
||||||
throws IOException, TimeoutException, InterruptedException {
|
throws IOException, TimeoutException, InterruptedException {
|
||||||
@ -150,7 +148,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -160,14 +158,14 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
assertThat(initialPlaylistWithAllSegments.segments).hasSize(6);
|
assertThat(initialPlaylistWithAllSegments.segments).hasSize(6);
|
||||||
HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1);
|
HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1);
|
||||||
assertThat(mergedPlaylist.mediaSequence).isEqualTo(11);
|
assertThat(mergedPlaylist.mediaSequence).isEqualTo(11);
|
||||||
assertThat(mergedPlaylist.skippedSegmentCount).isEqualTo(0);
|
|
||||||
assertThat(mergedPlaylist.segments).hasSize(6);
|
assertThat(mergedPlaylist.segments).hasSize(6);
|
||||||
// First 2 segments of the merged playlist need to be copied from the previous playlist.
|
// First 2 segments of the merged playlist need to be copied from the previous playlist.
|
||||||
assertThat(mergedPlaylist.segments.subList(0, 2))
|
assertThat(mergedPlaylist.segments.get(0).url)
|
||||||
.containsExactlyElementsIn(initialPlaylistWithAllSegments.segments.subList(1, 3))
|
.isEqualTo(initialPlaylistWithAllSegments.segments.get(1).url);
|
||||||
.inOrder();
|
assertThat(mergedPlaylist.segments.get(0).relativeStartTimeUs).isEqualTo(0);
|
||||||
assertThat(mergedPlaylist.segments.get(2).url)
|
assertThat(mergedPlaylist.segments.get(1).url)
|
||||||
.isEqualTo(initialPlaylistWithAllSegments.segments.get(3).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.
|
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
|
||||||
@ -185,7 +183,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -195,11 +193,9 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
assertThat(initialPlaylistWithAllSegments.segments).hasSize(6);
|
assertThat(initialPlaylistWithAllSegments.segments).hasSize(6);
|
||||||
HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1);
|
HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1);
|
||||||
assertThat(mergedPlaylist.mediaSequence).isEqualTo(22);
|
assertThat(mergedPlaylist.mediaSequence).isEqualTo(22);
|
||||||
assertThat(mergedPlaylist.skippedSegmentCount).isEqualTo(0);
|
|
||||||
assertThat(mergedPlaylist.segments).hasSize(4);
|
assertThat(mergedPlaylist.segments).hasSize(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
|
|
||||||
@Test
|
@Test
|
||||||
public void start_playlistCanSkipDataRanges_requestsDeltaUpdateV2()
|
public void start_playlistCanSkipDataRanges_requestsDeltaUpdateV2()
|
||||||
throws IOException, TimeoutException, InterruptedException {
|
throws IOException, TimeoutException, InterruptedException {
|
||||||
@ -214,7 +210,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -224,7 +220,6 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(11);
|
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(11);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
|
|
||||||
@Test
|
@Test
|
||||||
public void start_playlistCanSkipAndUriWithParams_preservesOriginalParams()
|
public void start_playlistCanSkipAndUriWithParams_preservesOriginalParams()
|
||||||
throws IOException, TimeoutException, InterruptedException {
|
throws IOException, TimeoutException, InterruptedException {
|
||||||
@ -241,7 +236,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -266,7 +261,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -292,7 +287,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -321,7 +316,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -352,7 +347,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
new DefaultHttpDataSourceFactory(),
|
new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 2);
|
/* awaitedMediaPlaylistCount= */ 2);
|
||||||
|
|
||||||
@ -365,7 +360,6 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2);
|
assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
|
|
||||||
@Test
|
@Test
|
||||||
public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest()
|
public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest()
|
||||||
throws IOException, TimeoutException, InterruptedException {
|
throws IOException, TimeoutException, InterruptedException {
|
||||||
@ -386,7 +380,7 @@ public class DefaultHlsPlaylistTrackerTest {
|
|||||||
|
|
||||||
List<HlsMediaPlaylist> mediaPlaylists =
|
List<HlsMediaPlaylist> mediaPlaylists =
|
||||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||||
/* dataSourceFactory= */ new DefaultHttpDataSourceFactory(),
|
/* dataSourceFactory= */ new DefaultHttpDataSource.Factory(),
|
||||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||||
/* awaitedMediaPlaylistCount= */ 3);
|
/* awaitedMediaPlaylistCount= */ 3);
|
||||||
|
|
||||||
|
@ -303,24 +303,112 @@ public class HlsMediaPlaylistParserTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseMediaPlaylist_withSkippedSegments_parsesNumberOfSkippedSegments()
|
public void parseMediaPlaylist_withSkippedSegments_correctlyMergedSegments() throws IOException {
|
||||||
throws IOException {
|
|
||||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
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 =
|
String playlistString =
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
+ "#EXT-X-TARGETDURATION:4\n"
|
+ "#EXT-X-TARGETDURATION:4\n"
|
||||||
+ "#EXT-X-VERSION:6\n"
|
+ "#EXT-X-VERSION:6\n"
|
||||||
|
+ "#EXT-X-DISCONTINUITY-SEQUENCE:1234\n"
|
||||||
+ "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n"
|
+ "#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n"
|
||||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
+ "#EXT-X-MEDIA-SEQUENCE:265\n"
|
||||||
+ "#EXT-X-SKIP:SKIPPED-SEGMENTS=1234\n"
|
+ "#EXT-X-SKIP:SKIPPED-SEGMENTS=1\n"
|
||||||
+ "#EXTINF:4.00008,\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));
|
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||||
|
|
||||||
HlsMediaPlaylist playlist =
|
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
|
@Test
|
||||||
@ -1125,7 +1213,9 @@ public class HlsMediaPlaylistParserTest {
|
|||||||
/* variableDefinitions= */ Collections.emptyMap(),
|
/* variableDefinitions= */ Collections.emptyMap(),
|
||||||
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
HlsMediaPlaylist playlistWithInheritance =
|
HlsMediaPlaylist playlistWithInheritance =
|
||||||
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
|
(HlsMediaPlaylist)
|
||||||
|
new HlsPlaylistParser(masterPlaylist, /* previousMediaPlaylist= */ null)
|
||||||
|
.parse(playlistUri, inputStream);
|
||||||
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
|
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1187,7 +1277,9 @@ public class HlsMediaPlaylistParserTest {
|
|||||||
variableDefinitions,
|
variableDefinitions,
|
||||||
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
HlsMediaPlaylist playlist =
|
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++) {
|
for (int i = 1; i <= 4; i++) {
|
||||||
assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts");
|
assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user