diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java index c740eac6cd..30cce581af 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java @@ -647,7 +647,7 @@ private static HlsMediaPlaylist parseMediaPlaylist( List segments = new ArrayList<>(); List trailingParts = new ArrayList<>(); @Nullable Part preloadPart = null; - Map renditionReports = new HashMap<>(); + List renditionReports = new ArrayList<>(); List tags = new ArrayList<>(); long segmentDurationUs = 0; @@ -856,17 +856,11 @@ private static HlsMediaPlaylist parseMediaPlaylist( } 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 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 @@ private static HlsMediaPlaylist parseMediaPlaylist( } } + Map 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 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 @@ private static HlsMediaPlaylist parseMediaPlaylist( segments, trailingParts, serverControl, - renditionReports); + renditionReportMap); } private static DrmInitData getPlaylistProtectionSchemes( diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java index 045e29430f..6999617a22 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java @@ -903,6 +903,114 @@ public void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes( 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 void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes( + "#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 void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes( 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 void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes( + "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 void parseMediaPlaylist_withRenditionReportLowLatency_parseAllAttributes( 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); }