mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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:
parent
b0e48175f0
commit
e180e263a5
3 changed files with 102 additions and 5 deletions
|
|
@ -225,7 +225,7 @@ public final class DefaultHlsPlaylistTracker
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refreshPlaylist(Uri url) {
|
public void refreshPlaylist(Uri url) {
|
||||||
playlistBundles.get(url).loadPlaylist();
|
playlistBundles.get(url).loadPlaylist(/* allowDeliveryDirectives= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -275,7 +275,7 @@ public final class DefaultHlsPlaylistTracker
|
||||||
// We don't need to load the playlist again. We can use the same result.
|
// We don't need to load the playlist again. We can use the same result.
|
||||||
primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadEventInfo);
|
primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result, loadEventInfo);
|
||||||
} else {
|
} else {
|
||||||
primaryBundle.loadPlaylist();
|
primaryBundle.loadPlaylist(/* allowDeliveryDirectives= */ false);
|
||||||
}
|
}
|
||||||
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
|
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
|
||||||
eventDispatcher.loadCompleted(loadEventInfo, C.DATA_TYPE_MANIFEST);
|
eventDispatcher.loadCompleted(loadEventInfo, C.DATA_TYPE_MANIFEST);
|
||||||
|
|
@ -552,8 +552,8 @@ public final class DefaultHlsPlaylistTracker
|
||||||
|| lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs;
|
|| lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPlaylist() {
|
public void loadPlaylist(boolean allowDeliveryDirectives) {
|
||||||
loadPlaylistInternal(playlistUrl);
|
loadPlaylistInternal(allowDeliveryDirectives ? getMediaPlaylistUriForReload() : playlistUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void maybeThrowPlaylistRefreshError() throws IOException {
|
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
|
// Service Unavailable (503). In such cases, force a full, non-blocking request (see RFC
|
||||||
// 8216, section 6.2.5.2 and 6.3.7).
|
// 8216, section 6.2.5.2 and 6.3.7).
|
||||||
earliestNextLoadTimeMs = SystemClock.elapsedRealtime();
|
earliestNextLoadTimeMs = SystemClock.elapsedRealtime();
|
||||||
loadPlaylist();
|
loadPlaylist(/* allowDeliveryDirectives= */ false);
|
||||||
castNonNull(eventDispatcher)
|
castNonNull(eventDispatcher)
|
||||||
.loadError(loadEventInfo, loadable.type, error, /* wasCanceled= */ true);
|
.loadError(loadEventInfo, loadable.type, error, /* wasCanceled= */ true);
|
||||||
return Loader.DONT_RETRY;
|
return Loader.DONT_RETRY;
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||||
private static final String
|
private static final String
|
||||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT =
|
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";
|
"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
|
private static final String
|
||||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD =
|
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";
|
"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);
|
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
|
@Test
|
||||||
public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest()
|
public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest()
|
||||||
throws IOException, TimeoutException, InterruptedException {
|
throws IOException, TimeoutException, InterruptedException {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
Loading…
Reference in a new issue