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
This commit is contained in:
tianyifeng 2024-04-29 07:21:42 -07:00 committed by Copybara-Service
parent b0e48175f0
commit e180e263a5
3 changed files with 102 additions and 5 deletions

View File

@ -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;

View File

@ -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<HttpUrl> 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<HlsMediaPlaylist> mediaPlaylists = new ArrayList<>();
AtomicInteger playlistCounter = new AtomicInteger();
AtomicReference<TimeoutException> 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 {

View File

@ -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"