mirror of
https://github.com/samsonjs/media.git
synced 2026-04-09 11:55:46 +00:00
Parse HLS #EXT-X-RENDITION-REPORT tag
Issue: #5011 PiperOrigin-RevId: 340621758
This commit is contained in:
parent
ae17e6d6f8
commit
4332dc2304
3 changed files with 274 additions and 15 deletions
|
|
@ -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.
|
||||
*
|
||||
* <p>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<Part> trailingParts;
|
||||
/** The rendition reports of alternative rendition playlists. */
|
||||
public final Map<Uri, RenditionReport> 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<Segment> segments,
|
||||
int skippedSegmentCount,
|
||||
List<Part> trailingParts,
|
||||
ServerControl serverControl) {
|
||||
ServerControl serverControl,
|
||||
Map<Uri, RenditionReport> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HlsPlayli
|
|||
private static final String TAG_GAP = "#EXT-X-GAP";
|
||||
private static final String TAG_SKIP = "#EXT-X-SKIP";
|
||||
private static final String TAG_PRELOAD_HINT = "#EXT-X-PRELOAD-HINT";
|
||||
private static final String TAG_RENDITION_REPORT = "#EXT-X-RENDITION-REPORT";
|
||||
|
||||
private static final String TYPE_AUDIO = "AUDIO";
|
||||
private static final String TYPE_VIDEO = "VIDEO";
|
||||
|
|
@ -155,6 +158,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
+ ":([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_MEDIA_TITLE =
|
||||
Pattern.compile(TAG_MEDIA_DURATION + ":[\\d\\.]+\\b,(.+)");
|
||||
private static final Pattern REGEX_LAST_MSN = Pattern.compile("LAST-MSN" + "=(\\d+)\\b");
|
||||
private static final Pattern REGEX_LAST_PART = Pattern.compile("LAST-PART" + "=(\\d+)\\b");
|
||||
private static final Pattern REGEX_TIME_OFFSET = Pattern.compile("TIME-OFFSET=(-?[\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE
|
||||
+ ":(\\d+(?:@\\d+)?)\\b");
|
||||
|
|
@ -600,12 +605,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
long partTargetDurationUs = C.TIME_UNSET;
|
||||
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
|
||||
boolean hasEndTag = false;
|
||||
boolean seenPreloadPart = false;
|
||||
@Nullable Segment initializationSegment = null;
|
||||
HashMap<String, String> variableDefinitions = new HashMap<>();
|
||||
HashMap<String, Segment> urlToInferredInitSegment = new HashMap<>();
|
||||
List<Segment> segments = new ArrayList<>();
|
||||
List<Part> parts = new ArrayList<>();
|
||||
List<Part> trailingParts = new ArrayList<>();
|
||||
@Nullable Part preloadPart = null;
|
||||
Map<Uri, RenditionReport> renditionReports = new HashMap<>();
|
||||
List<String> tags = new ArrayList<>();
|
||||
|
||||
long segmentDurationUs = 0;
|
||||
|
|
@ -769,7 +775,22 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
hasIndependentSegmentsTag = true;
|
||||
} else if (line.equals(TAG_ENDLIST)) {
|
||||
hasEndTag = true;
|
||||
} else if (line.startsWith(TAG_PRELOAD_HINT) && !seenPreloadPart) {
|
||||
} else if (line.startsWith(TAG_RENDITION_REPORT)) {
|
||||
long defaultValue = mediaSequence + segments.size() - (trailingParts.isEmpty() ? 1 : 0);
|
||||
long lastMediaSequence = parseOptionalLongAttr(line, REGEX_LAST_MSN, defaultValue);
|
||||
List<Part> 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<HlsPlayli
|
|||
playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
|
||||
}
|
||||
}
|
||||
parts.add(
|
||||
preloadPart =
|
||||
new Part(
|
||||
url,
|
||||
initializationSegment,
|
||||
|
|
@ -803,8 +824,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
byteRangeStart,
|
||||
byteRangeLength,
|
||||
/* hasGapTag= */ false,
|
||||
/* isIndependent= */ false));
|
||||
seenPreloadPart = true;
|
||||
/* isIndependent= */ false);
|
||||
} else if (line.startsWith(TAG_PART)) {
|
||||
@Nullable
|
||||
String segmentEncryptionIV =
|
||||
|
|
@ -836,7 +856,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
|
||||
}
|
||||
}
|
||||
parts.add(
|
||||
trailingParts.add(
|
||||
new Part(
|
||||
url,
|
||||
initializationSegment,
|
||||
|
|
@ -903,12 +923,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
segmentByteRangeOffset,
|
||||
segmentByteRangeLength,
|
||||
hasGapTag,
|
||||
parts));
|
||||
trailingParts));
|
||||
segmentStartTimeUs += segmentDurationUs;
|
||||
partStartTimeUs = segmentStartTimeUs;
|
||||
segmentDurationUs = 0;
|
||||
segmentTitle = "";
|
||||
parts = new ArrayList<>();
|
||||
trailingParts = new ArrayList<>();
|
||||
if (segmentByteRangeLength != C.LENGTH_UNSET) {
|
||||
segmentByteRangeOffset += segmentByteRangeLength;
|
||||
}
|
||||
|
|
@ -917,6 +937,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
}
|
||||
}
|
||||
|
||||
if (preloadPart != null) {
|
||||
trailingParts.add(preloadPart);
|
||||
}
|
||||
|
||||
return new HlsMediaPlaylist(
|
||||
playlistType,
|
||||
baseUri,
|
||||
|
|
@ -935,8 +959,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
playlistProtectionSchemes,
|
||||
segments,
|
||||
skippedSegmentCount,
|
||||
parts,
|
||||
serverControl);
|
||||
trailingParts,
|
||||
serverControl,
|
||||
renditionReports);
|
||||
}
|
||||
|
||||
private static DrmInitData getPlaylistProtectionSchemes(
|
||||
|
|
|
|||
|
|
@ -636,6 +636,199 @@ public class HlsMediaPlaylistParserTest {
|
|||
assertThat(preloadPart.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withRenditionReportWithoutPartTargetDuration_lastPartIndexUnset()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=100\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(100);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
parseMediaPlaylist_withRenditionReportWithoutPartTargetDurationWithoutLastMsn_sameLastMsnAsCurrentPlaylist()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(266);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=1\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=100,LAST-PART=2\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:"
|
||||
+ "URI=\"http://foo.bar/rendition2.m3u8\",LAST-MSN=1000,LAST-PART=3\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(2);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(100);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(2);
|
||||
HlsMediaPlaylist.RenditionReport report2 =
|
||||
playlist.renditionReports.get(Uri.parse("http://foo.bar/rendition2.m3u8"));
|
||||
assertThat(report2.lastMediaSequence).isEqualTo(1000);
|
||||
assertThat(report2.lastPartIndex).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
parseMediaPlaylist_withRenditionReportLowLatencyWithoutLastMsn_sameMsnAsCurrentPlaylist()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=1\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-PART=2\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(267);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
parseMediaPlaylist_withRenditionReportLowLatencyWithoutLastPartIndex_sameLastPartIndexAsCurrentPlaylist()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=1\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=100\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(100);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
parseMediaPlaylist_withRenditionReportLowLatencyWithoutLastPartIndex_ignoredPreloadPart()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=1\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.1.ts\"\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=100\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.trailingParts).hasSize(2);
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(100);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withRenditionReportLowLatencyFullSegment_rollingPartIndexUriParam()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=1\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part266.0.ts\"\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part266.1.ts\"\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part266.2.ts\"\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part266.3.ts\"\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.0.ts\"\n"
|
||||
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.renditionReports).hasSize(1);
|
||||
HlsMediaPlaylist.RenditionReport report0 =
|
||||
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
|
||||
assertThat(report0.lastMediaSequence).isEqualTo(266);
|
||||
assertThat(report0.lastPartIndex).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleExtXKeysForSingleSegment() throws Exception {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
|
|
|
|||
Loading…
Reference in a new issue