diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
index 7fc6b11af1..75a5b95e7f 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
@@ -17,17 +17,20 @@ 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;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/** Represents an HLS media playlist. */
public final class HlsMediaPlaylist extends HlsPlaylist {
@@ -297,6 +300,36 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
}
+ /**
+ * A rendition report for an alternative rendition defined in another media playlist.
+ *
+ *
See RFC 8216, section 4.4.5.1.4.
+ */
+ public static final class RenditionReport {
+ /** The URI of the media playlist of the reported rendition. */
+ public final Uri playlistUri;
+ /** The last media sequence that is in the playlist of the reported rendition. */
+ public final long lastMediaSequence;
+ /**
+ * The last part index that is in the playlist of the reported rendition, or {@link
+ * C#INDEX_UNSET} if the rendition does not contain partial segments.
+ */
+ public final int lastPartIndex;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param playlistUri See {@link #playlistUri}.
+ * @param lastMediaSequence See {@link #lastMediaSequence}.
+ * @param lastPartIndex See {@link #lastPartIndex}.
+ */
+ public RenditionReport(Uri playlistUri, long lastMediaSequence, int lastPartIndex) {
+ this.playlistUri = playlistUri;
+ this.lastMediaSequence = lastMediaSequence;
+ this.lastPartIndex = lastPartIndex;
+ }
+ }
+
/**
* Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link
* #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}.
@@ -372,6 +405,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* The list of parts at the end of the playlist for which the segment is not in the playlist yet.
*/
public final List trailingParts;
+ /** The rendition reports of alternative rendition playlists. */
+ public final Map renditionReports;
/** The total duration of the playlist in microseconds. */
public final long durationUs;
/** The attributes of the #EXT-X-SERVER-CONTROL header. */
@@ -396,6 +431,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param skippedSegmentCount See {@link #skippedSegmentCount}.
* @param trailingParts See {@link #trailingParts}.
* @param serverControl See {@link #serverControl}
+ * @param renditionReports See {@link #renditionReports}.
*/
public HlsMediaPlaylist(
@PlaylistType int playlistType,
@@ -416,7 +452,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
List segments,
int skippedSegmentCount,
List trailingParts,
- ServerControl serverControl) {
+ ServerControl serverControl,
+ Map renditionReports) {
super(baseUri, tags, hasIndependentSegments);
this.playlistType = playlistType;
this.startTimeUs = startTimeUs;
@@ -432,6 +469,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this.segments = ImmutableList.copyOf(segments);
this.skippedSegmentCount = skippedSegmentCount;
this.trailingParts = ImmutableList.copyOf(trailingParts);
+ this.renditionReports = ImmutableMap.copyOf(renditionReports);
if (!segments.isEmpty()) {
Segment last = segments.get(segments.size() - 1);
durationUs = last.relativeStartTimeUs + last.durationUs;
@@ -517,7 +555,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
mergedSegments,
/* skippedSegmentCount= */ 0,
trailingParts,
- serverControl);
+ serverControl,
+ renditionReports);
}
/**
@@ -549,7 +588,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
segments,
skippedSegmentCount,
trailingParts,
- serverControl);
+ serverControl,
+ renditionReports);
}
/**
@@ -579,7 +619,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
segments,
skippedSegmentCount,
trailingParts,
- serverControl);
+ serverControl,
+ renditionReports);
}
}
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
index 03408776b3..9586244afc 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
@@ -34,12 +34,14 @@ import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInf
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part;
+import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.RenditionReport;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
+import com.google.common.collect.Iterables;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -94,6 +96,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions = new HashMap<>();
HashMap urlToInferredInitSegment = new HashMap<>();
List segments = new ArrayList<>();
- List parts = new ArrayList<>();
+ List trailingParts = new ArrayList<>();
+ @Nullable Part preloadPart = null;
+ Map renditionReports = new HashMap<>();
List tags = new ArrayList<>();
long segmentDurationUs = 0;
@@ -769,7 +775,22 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser lastParts =
+ trailingParts.isEmpty() ? Iterables.getLast(segments).parts : trailingParts;
+ int defaultPartIndex =
+ partTargetDurationUs != C.TIME_UNSET ? lastParts.size() - 1 : C.INDEX_UNSET;
+ int lastPartIndex = parseOptionalIntAttr(line, REGEX_LAST_PART, defaultPartIndex);
+ String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
+ Uri playlistUri = Uri.parse(UriUtil.resolve(baseUri, uri));
+ renditionReports.put(
+ playlistUri, new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex));
+ } else if (line.startsWith(TAG_PRELOAD_HINT)) {
+ if (preloadPart != null) {
+ continue;
+ }
String type = parseStringAttr(line, REGEX_PRELOAD_HINT_TYPE, variableDefinitions);
if (!TYPE_PART.equals(type)) {
continue;
@@ -790,7 +811,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser();
+ trailingParts = new ArrayList<>();
if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset += segmentByteRangeLength;
}
@@ -917,6 +937,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser