From 4332dc2304ff65a5e6117ac8e4470abc0a174611 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 4 Nov 2020 12:09:29 +0000 Subject: [PATCH] Parse HLS #EXT-X-RENDITION-REPORT tag Issue: #5011 PiperOrigin-RevId: 340621758 --- .../source/hls/playlist/HlsMediaPlaylist.java | 49 ++++- .../hls/playlist/HlsPlaylistParser.java | 47 ++++- .../playlist/HlsMediaPlaylistParserTest.java | 193 ++++++++++++++++++ 3 files changed, 274 insertions(+), 15 deletions(-) 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