diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index d492221686..fb39f2f2b4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory; 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.Segment; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; @@ -40,6 +41,7 @@ import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.Iterables; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -465,6 +467,7 @@ public final class DefaultHlsPlaylistTracker private final class MediaPlaylistBundle implements Loader.Callback> { private static final String BLOCK_MSN_PARAM = "_HLS_msn"; + private static final String BLOCK_PART_PARAM = "_HLS_part"; private static final String SKIP_PARAM = "_HLS_skip"; private final Uri playlistUrl; @@ -729,16 +732,25 @@ public final class DefaultHlsPlaylistTracker return playlistUrl; } Uri.Builder uriBuilder = playlistUrl.buildUpon(); - if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) { - uriBuilder.appendQueryParameter( - SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES"); - } if (playlistSnapshot.serverControl.canBlockReload) { - long reloadMediaSequence = + long targetMediaSequence = playlistSnapshot.mediaSequence + playlistSnapshot.segments.size() + playlistSnapshot.skippedSegmentCount; - uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(reloadMediaSequence)); + uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(targetMediaSequence)); + if (playlistSnapshot.partTargetDurationUs != C.TIME_UNSET) { + List trailingParts = playlistSnapshot.trailingParts; + int targetPartIndex = trailingParts.size(); + if (!trailingParts.isEmpty() && Iterables.getLast(trailingParts).isPreload) { + // Ignore the preload part. + targetPartIndex--; + } + uriBuilder.appendQueryParameter(BLOCK_PART_PARAM, String.valueOf(targetPartIndex)); + } + } + if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) { + uriBuilder.appendQueryParameter( + SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES"); } return uriBuilder.build(); } 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 75a5b95e7f..14c369c3e7 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 @@ -176,6 +176,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist { /** Whether the part is independent. */ public final boolean isIndependent; + /** Whether the part is a preloading part. */ + public final boolean isPreload; /** * Creates an instance. @@ -192,6 +194,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * @param byteRangeLength See {@link #byteRangeLength}. * @param hasGapTag See {@link #hasGapTag}. * @param isIndependent See {@link #isIndependent}. + * @param isPreload See {@link #isPreload}. */ public Part( String url, @@ -205,7 +208,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist { long byteRangeOffset, long byteRangeLength, boolean hasGapTag, - boolean isIndependent) { + boolean isIndependent, + boolean isPreload) { super( url, initializationSegment, @@ -219,6 +223,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { byteRangeLength, hasGapTag); this.isIndependent = isIndependent; + this.isPreload = isPreload; } } @@ -502,8 +507,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist { // The media sequences are equal. int segmentCount = segments.size() + skippedSegmentCount; int otherSegmentCount = other.segments.size() + other.skippedSegmentCount; - return segmentCount > otherSegmentCount - || (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag); + if (segmentCount != otherSegmentCount) { + return segmentCount > otherSegmentCount; + } + int partCount = trailingParts.size(); + int otherPartCount = other.trailingParts.size(); + return partCount > otherPartCount + || (partCount == otherPartCount && hasEndTag && !other.hasEndTag); } /** 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 9586244afc..2fa01c43fc 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 @@ -824,7 +824,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser httpUrls = + enqueueWebServerResponses( + new String[] { + "/master.m3u8", + "/media0/playlist.m3u8", + "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=1" + }, + getMockResponse(SAMPLE_M3U8_LIVE_MASTER), + getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY), + getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_NEXT)); + + List mediaPlaylists = + runPlaylistTrackerAndCollectMediaPlaylists( + new DefaultHttpDataSourceFactory(), + Uri.parse(mockWebServer.url("/master.m3u8").toString()), + /* awaitedMediaPlaylistCount= */ 2); + + assertRequestUrlsCalled(httpUrls); + assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(0).segments).hasSize(4); + assertThat(mediaPlaylists.get(0).trailingParts).hasSize(2); + assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(1).segments).hasSize(4); + assertThat(mediaPlaylists.get(1).trailingParts).hasSize(3); + } + + @Test + public void start_playlistCanBlockReloadLowLatencyFullSegment_correctMsnAndPartParams() + throws IOException, TimeoutException, InterruptedException { + List httpUrls = + enqueueWebServerResponses( + new String[] { + "/master.m3u8", + "/media0/playlist.m3u8", + "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0" + }, + getMockResponse(SAMPLE_M3U8_LIVE_MASTER), + getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT), + getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT)); + + List mediaPlaylists = + runPlaylistTrackerAndCollectMediaPlaylists( + new DefaultHttpDataSourceFactory(), + Uri.parse(mockWebServer.url("/master.m3u8").toString()), + /* awaitedMediaPlaylistCount= */ 2); + + assertRequestUrlsCalled(httpUrls); + assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(0).segments).hasSize(4); + assertThat(mediaPlaylists.get(0).trailingParts).isEmpty(); + assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(1).segments).hasSize(4); + assertThat(mediaPlaylists.get(1).trailingParts).hasSize(1); + } + + @Test + public void start_playlistCanBlockReloadLowLatencyFullSegmentWithPreloadPart_ignoresPreloadPart() + throws IOException, TimeoutException, InterruptedException { + List httpUrls = + enqueueWebServerResponses( + new String[] { + "/master.m3u8", + "/media0/playlist.m3u8", + "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0" + }, + getMockResponse(SAMPLE_M3U8_LIVE_MASTER), + getMockResponse( + SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD), + getMockResponse( + SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD_NEXT)); + + List mediaPlaylists = + runPlaylistTrackerAndCollectMediaPlaylists( + new DefaultHttpDataSourceFactory(), + Uri.parse(mockWebServer.url("/master.m3u8").toString()), + /* awaitedMediaPlaylistCount= */ 2); + + assertRequestUrlsCalled(httpUrls); + assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(0).segments).hasSize(4); + assertThat(mediaPlaylists.get(0).trailingParts).hasSize(1); + assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(1).segments).hasSize(4); + assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2); + } + @Test public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest() throws IOException, TimeoutException, InterruptedException { @@ -263,9 +368,9 @@ public class DefaultHlsPlaylistTrackerTest { new String[] { "/master.m3u8", "/media0/playlist.m3u8", - "/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=16", + "/media0/playlist.m3u8?_HLS_msn=16&_HLS_skip=YES", "/media0/playlist.m3u8", - "/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=17" + "/media0/playlist.m3u8?_HLS_msn=17&_HLS_skip=YES" }, getMockResponse(SAMPLE_M3U8_LIVE_MASTER), getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD), diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 9f81d8db14..0d9a75cce2 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -354,6 +354,7 @@ public class HlsMediaPlaylistParserTest { assertThat(firstPart.durationUs).isEqualTo(2_000_000); assertThat(firstPart.relativeStartTimeUs).isEqualTo(playlist.segments.get(0).durationUs); assertThat(firstPart.isIndependent).isTrue(); + assertThat(firstPart.isPreload).isFalse(); assertThat(firstPart.hasGapTag).isTrue(); assertThat(firstPart.url).isEqualTo("part267.1.ts"); HlsMediaPlaylist.Part secondPart = playlist.segments.get(1).parts.get(1); @@ -518,6 +519,7 @@ public class HlsMediaPlaylistParserTest { assertThat(preloadPart.byteRangeOffset).isEqualTo(1234); assertThat(preloadPart.initializationSegment.url).isEqualTo("map.mp4"); assertThat(preloadPart.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34"); + assertThat(preloadPart.isPreload).isTrue(); } @Test @@ -539,6 +541,7 @@ public class HlsMediaPlaylistParserTest { assertThat(playlist.trailingParts).hasSize(2); assertThat(playlist.trailingParts.get(1).url).isEqualTo("filePart267.2.ts"); + assertThat(playlist.trailingParts.get(1).isPreload).isTrue(); } @Test diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency new file mode 100644 index 0000000000..cb39e1bead --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXTINF:4.00000, +fileSequence13.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.1.ts" diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment new file mode 100644 index 0000000000..4a13112d7a --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment @@ -0,0 +1,18 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts" +#EXTINF:4.00000, +fileSequence13.ts diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next new file mode 100644 index 0000000000..88315024bb --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts" +#EXTINF:4.00000, +fileSequence13.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts" diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload new file mode 100644 index 0000000000..f7651fb6c9 --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts" +#EXTINF:4.00000, +fileSequence13.ts +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.0.ts" diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload_next b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload_next new file mode 100644 index 0000000000..baa701e8eb --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload_next @@ -0,0 +1,20 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts" +#EXTINF:4.00000, +fileSequence13.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.1.ts" diff --git a/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_next b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_next new file mode 100644 index 0000000000..537bc81b81 --- /dev/null +++ b/testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_next @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=1.000000 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:10 +#EXTINF:4.00000, +fileSequence10.ts +#EXTINF:4.00000, +fileSequence11.ts +#EXTINF:4.00000, +fileSequence12.ts +#EXTINF:4.00000, +fileSequence13.ts +#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts" +#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.1.ts" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.2.ts"