Defer setting defaults for rendition reports until playlist is parsed

This makes sure that #EXT-X-RENDITION-REPORT tags can be placed before
the list of segments/parts as well. We were previously assuming that
these come at the end, which naturally would make sense and is done like
this in all examples, but it is not explicitly defined by the spec.

Issue: google/ExoPlayer#9592
PiperOrigin-RevId: 406329684
This commit is contained in:
bachinger 2021-10-29 12:13:32 +01:00 committed by Ian Baker
parent 222db48298
commit 80c5e00b18
2 changed files with 135 additions and 15 deletions

View File

@ -647,7 +647,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
List<Segment> segments = new ArrayList<>();
List<Part> trailingParts = new ArrayList<>();
@Nullable Part preloadPart = null;
Map<Uri, RenditionReport> renditionReports = new HashMap<>();
List<RenditionReport> renditionReports = new ArrayList<>();
List<String> tags = new ArrayList<>();
long segmentDurationUs = 0;
@ -856,17 +856,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} else if (line.equals(TAG_ENDLIST)) {
hasEndTag = true;
} 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);
long lastMediaSequence = parseOptionalLongAttr(line, REGEX_LAST_MSN, C.INDEX_UNSET);
int lastPartIndex = parseOptionalIntAttr(line, REGEX_LAST_PART, C.INDEX_UNSET);
String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
Uri playlistUri = Uri.parse(UriUtil.resolve(baseUri, uri));
renditionReports.put(
playlistUri, new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex));
renditionReports.add(new RenditionReport(playlistUri, lastMediaSequence, lastPartIndex));
} else if (line.startsWith(TAG_PRELOAD_HINT)) {
if (preloadPart != null) {
continue;
@ -1024,6 +1018,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
}
Map<Uri, RenditionReport> renditionReportMap = new HashMap<>();
for (int i = 0; i < renditionReports.size(); i++) {
RenditionReport renditionReport = renditionReports.get(i);
long lastMediaSequence = renditionReport.lastMediaSequence;
if (lastMediaSequence == C.INDEX_UNSET) {
lastMediaSequence = mediaSequence + segments.size() - (trailingParts.isEmpty() ? 1 : 0);
}
int lastPartIndex = renditionReport.lastPartIndex;
if (lastPartIndex == C.INDEX_UNSET && partTargetDurationUs != C.TIME_UNSET) {
List<Part> lastParts =
trailingParts.isEmpty() ? Iterables.getLast(segments).parts : trailingParts;
lastPartIndex = lastParts.size() - 1;
}
renditionReportMap.put(
renditionReport.playlistUri,
new RenditionReport(renditionReport.playlistUri, lastMediaSequence, lastPartIndex));
}
if (preloadPart != null) {
trailingParts.add(preloadPart);
}
@ -1048,7 +1060,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segments,
trailingParts,
serverControl,
renditionReports);
renditionReportMap);
}
private static DrmInitData getPlaylistProtectionSchemes(

View File

@ -903,6 +903,114 @@ public class HlsMediaPlaylistParserTest {
assertThat(report0.lastPartIndex).isEqualTo(2);
}
@Test
public void
parseMediaPlaylist_withRenditionReportBeforeSegmentsWithoutPartTargetDurationWithoutLastMsn_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"
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\"\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence266.mp4\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.renditionReports).hasSize(1);
HlsMediaPlaylist.RenditionReport report =
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
assertThat(report.lastMediaSequence).isEqualTo(266);
assertThat(report.lastPartIndex).isEqualTo(C.INDEX_UNSET);
}
@Test
public void
parseMediaPlaylist_withRenditionReportBeforeSegementsDefaultMsn_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"
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-PART=2\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence266.mp4\n"
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.renditionReports).hasSize(1);
HlsMediaPlaylist.RenditionReport report =
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
assertThat(report.lastMediaSequence).isEqualTo(267);
assertThat(report.lastPartIndex).isEqualTo(2);
}
@Test
public void
parseMediaPlaylist_withRenditionReportBeforeSegementsDefaultLastPart_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"
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=267\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence266.mp4\n"
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n"
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.renditionReports).hasSize(1);
HlsMediaPlaylist.RenditionReport report =
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
assertThat(report.lastMediaSequence).isEqualTo(267);
assertThat(report.lastPartIndex).isEqualTo(1);
}
@Test
public void
parseMediaPlaylist_withRenditionReportBeforeSegements_sameMsnAndLastPartIndexAsCurrentPlaylist()
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-RENDITION-REPORT:URI=\"/rendition0.m3u8\"\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence266.mp4\n"
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.0.ts\"\n"
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.renditionReports).hasSize(1);
HlsMediaPlaylist.RenditionReport report =
playlist.renditionReports.get(Uri.parse("https://example.com/rendition0.m3u8"));
assertThat(report.lastMediaSequence).isEqualTo(267);
assertThat(report.lastPartIndex).isEqualTo(1);
}
@Test
public void
parseMediaPlaylist_withRenditionReportLowLatencyWithoutLastPartIndex_sameLastPartIndexAsCurrentPlaylist()
@ -917,7 +1025,7 @@ public class HlsMediaPlaylistParserTest {
+ "#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";
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=267\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
@ -926,7 +1034,7 @@ public class HlsMediaPlaylistParserTest {
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.lastMediaSequence).isEqualTo(267);
assertThat(report0.lastPartIndex).isEqualTo(0);
}
@ -945,7 +1053,7 @@ public class HlsMediaPlaylistParserTest {
+ "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";
+ "#EXT-X-RENDITION-REPORT:URI=\"/rendition0.m3u8\",LAST-MSN=267\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
@ -955,7 +1063,7 @@ public class HlsMediaPlaylistParserTest {
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.lastMediaSequence).isEqualTo(267);
assertThat(report0.lastPartIndex).isEqualTo(0);
}