Skip to content

Commit

Permalink
Refresh HlsMediaPlaylist with delivery directives when possible
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tianyif authored and Copybara-Service committed Apr 29, 2024
1 parent b0e4817 commit e180e26
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public void maybeThrowPlaylistRefreshError(Uri url) throws IOException {

@Override
public void refreshPlaylist(Uri url) {
playlistBundles.get(url).loadPlaylist();
playlistBundles.get(url).loadPlaylist(/* allowDeliveryDirectives= */ true);
}

@Override
Expand Down Expand Up @@ -275,7 +275,7 @@ public void onLoadCompleted(
// 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);
Expand Down Expand Up @@ -552,8 +552,8 @@ public boolean isSnapshotValid() {
|| lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs;
}

public void loadPlaylist() {
loadPlaylistInternal(playlistUrl);
public void loadPlaylist(boolean allowDeliveryDirectives) {
loadPlaylistInternal(allowDeliveryDirectives ? getMediaPlaylistUriForReload() : playlistUrl);
}

public void maybeThrowPlaylistRefreshError() throws IOException {
Expand Down Expand Up @@ -642,7 +642,7 @@ public LoadErrorAction onLoadError(
// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -446,6 +449,80 @@ public boolean onPlaylistError(
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit e180e26

Please sign in to comment.