From e180e263a59e95d623185097e50a658c0a6826e2 Mon Sep 17 00:00:00 2001 From: tianyifeng Date: Mon, 29 Apr 2024 07:21:42 -0700 Subject: [PATCH] Refresh `HlsMediaPlaylist` with delivery directives when possible When refreshing the media playlist, we should try to request via a url with [delivery directives](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.2.5). However, when initially loading the media playlist or when the last loading with delivery directives encountered an error, we should not allow using those directives. PiperOrigin-RevId: 629060177 --- .../playlist/DefaultHlsPlaylistTracker.java | 10 +-- .../DefaultHlsPlaylistTrackerTest.java | 77 +++++++++++++++++++ ...lock_reload_low_latency_full_segment_next2 | 20 +++++ 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 libraries/test_data/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next2 diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTracker.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTracker.java index 98d7c0c53e..32ec51e4be 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTracker.java @@ -225,7 +225,7 @@ public final class DefaultHlsPlaylistTracker @Override public void refreshPlaylist(Uri url) { - playlistBundles.get(url).loadPlaylist(); + playlistBundles.get(url).loadPlaylist(/* allowDeliveryDirectives= */ true); } @Override @@ -275,7 +275,7 @@ public final class DefaultHlsPlaylistTracker // We don't need to load the playlist again. We can use the same result. primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadEventInfo); } else { - primaryBundle.loadPlaylist(); + primaryBundle.loadPlaylist(/* allowDeliveryDirectives= */ false); } loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId); eventDispatcher.loadCompleted(loadEventInfo, C.DATA_TYPE_MANIFEST); @@ -552,8 +552,8 @@ public final class DefaultHlsPlaylistTracker || lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs; } - public void loadPlaylist() { - loadPlaylistInternal(playlistUrl); + public void loadPlaylist(boolean allowDeliveryDirectives) { + loadPlaylistInternal(allowDeliveryDirectives ? getMediaPlaylistUriForReload() : playlistUrl); } public void maybeThrowPlaylistRefreshError() throws IOException { @@ -642,7 +642,7 @@ public final class DefaultHlsPlaylistTracker // Service Unavailable (503). In such cases, force a full, non-blocking request (see RFC // 8216, section 6.2.5.2 and 6.3.7). earliestNextLoadTimeMs = SystemClock.elapsedRealtime(); - loadPlaylist(); + loadPlaylist(/* allowDeliveryDirectives= */ false); castNonNull(eventDispatcher) .loadError(loadEventInfo, loadable.type, error, /* wasCanceled= */ true); return Loader.DONT_RETRY; diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTrackerTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTrackerTest.java index 00be7e3c34..1a23152d88 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTrackerTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/DefaultHlsPlaylistTrackerTest.java @@ -78,6 +78,9 @@ public class DefaultHlsPlaylistTrackerTest { private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT = "media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next"; + private static final String + SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT2 = + "media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next2"; private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD = "media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload"; @@ -446,6 +449,80 @@ public class DefaultHlsPlaylistTrackerTest { assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2); } + @Test + public void + start_refreshPlaylistWithAllowingDeliveryDirectives_requestWithCorrectDeliveryDirectives() + throws Exception { + List httpUrls = + enqueueWebServerResponses( + new String[] { + "/multivariant.m3u8", + "/media0/playlist.m3u8", + "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0", + "/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=1" + }, + getMockResponse(SAMPLE_M3U8_LIVE_MULTIVARIANT), + 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), + getMockResponse( + SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT2)); + + DefaultHlsPlaylistTracker defaultHlsPlaylistTracker = + new DefaultHlsPlaylistTracker( + dataType -> new DefaultHttpDataSource.Factory().createDataSource(), + new DefaultLoadErrorHandlingPolicy(), + new DefaultHlsPlaylistParserFactory()); + List mediaPlaylists = new ArrayList<>(); + AtomicInteger playlistCounter = new AtomicInteger(); + AtomicReference playlistRefreshExceptionRef = new AtomicReference<>(); + defaultHlsPlaylistTracker.addListener( + new HlsPlaylistTracker.PlaylistEventListener() { + @Override + public void onPlaylistChanged() { + // Upon the first call of onPlaylistChanged(), we call refreshPlaylist(Uri) on the + // same url. + defaultHlsPlaylistTracker.refreshPlaylist( + defaultHlsPlaylistTracker.getMultivariantPlaylist().mediaPlaylistUrls.get(0)); + try { + // Make sure that playlist reload triggered by refreshPlaylist(Uri) call comes before + // the one triggered by the regular scheduling, to ensure the playlists to be + // verified are in the expected order. + RobolectricUtil.runMainLooperUntil(() -> playlistCounter.get() >= 2); + } catch (TimeoutException e) { + playlistRefreshExceptionRef.set(e); + } + } + + @Override + public boolean onPlaylistError( + Uri url, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, boolean forceRetry) { + return false; + } + }); + + defaultHlsPlaylistTracker.start( + Uri.parse(mockWebServer.url("/multivariant.m3u8").toString()), + new MediaSourceEventListener.EventDispatcher(), + mediaPlaylist -> { + mediaPlaylists.add(mediaPlaylist); + playlistCounter.addAndGet(1); + }); + RobolectricUtil.runMainLooperUntil(() -> playlistCounter.get() >= 3); + defaultHlsPlaylistTracker.stop(); + + assertThat(playlistRefreshExceptionRef.get()).isNull(); + 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); + assertThat(mediaPlaylists.get(2).mediaSequence).isEqualTo(10); + assertThat(mediaPlaylists.get(2).segments).hasSize(4); + assertThat(mediaPlaylists.get(2).trailingParts).hasSize(2); + } + @Test public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest() throws IOException, TimeoutException, InterruptedException { diff --git a/libraries/test_data/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next2 b/libraries/test_data/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next2 new file mode 100644 index 0000000000..4f86ad0eca --- /dev/null +++ b/libraries/test_data/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next2 @@ -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-PART:DURATION=1.00000,URI="fileSequence14.1.ts"