Reload HLS media playlist when merging delta update fails

Issue: #5011
PiperOrigin-RevId: 350550204
This commit is contained in:
bachinger 2021-01-07 14:48:39 +00:00 committed by Ian Baker
parent 6dec83238b
commit 6e8af81ddc
5 changed files with 72 additions and 42 deletions

View File

@ -603,11 +603,16 @@ public final class DefaultHlsPlaylistTracker
loadDurationMs,
loadable.bytesLoaded());
boolean isBlockingRequest = loadable.getUri().getQueryParameter(BLOCK_MSN_PARAM) != null;
if (isBlockingRequest && error instanceof HttpDataSource.InvalidResponseCodeException) {
int responseCode = ((HttpDataSource.InvalidResponseCodeException) error).responseCode;
if (responseCode == 400 || responseCode == 503) {
// Intercept bad request and service unavailable to force a full, non-blocking request
// (see RFC 8216, section 6.2.5.2).
boolean deltaUpdateFailed = error instanceof HlsPlaylistParser.DeltaUpdateException;
if (isBlockingRequest || deltaUpdateFailed) {
int responseCode = Integer.MAX_VALUE;
if (error instanceof HttpDataSource.InvalidResponseCodeException) {
responseCode = ((HttpDataSource.InvalidResponseCodeException) error).responseCode;
}
if (deltaUpdateFailed || responseCode == 400 || responseCode == 503) {
// Intercept failed delta updates and blocking requests producing a Bad Request (400) and
// 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();
castNonNull(eventDispatcher)

View File

@ -69,6 +69,9 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
*/
public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlaylist> {
/** Exception thrown when merging a delta update fails. */
public static final class DeltaUpdateException extends IOException {}
private static final String LOG_TAG = "HlsPlaylistParser";
private static final String PLAYLIST_HEADER = "#EXTM3U";
@ -744,36 +747,37 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
checkState(previousMediaPlaylist != null && segments.isEmpty());
int startIndex = (int) (mediaSequence - castNonNull(previousMediaPlaylist).mediaSequence);
int endIndex = startIndex + skippedSegmentCount;
if (startIndex >= 0 && endIndex <= previousMediaPlaylist.segments.size()) {
// Merge only if all skipped segments are available in the previous playlist.
for (int i = startIndex; i < endIndex; i++) {
Segment segment = previousMediaPlaylist.segments.get(i);
if (mediaSequence != previousMediaPlaylist.mediaSequence) {
// If the media sequences of the playlists are not the same, we need to recreate the
// object with the updated relative start time and the relative discontinuity
// sequence. With identical playlist media sequences these values do not change.
int newRelativeDiscontinuitySequence =
previousMediaPlaylist.discontinuitySequence
- playlistDiscontinuitySequence
+ segment.relativeDiscontinuitySequence;
segment = segment.copyWith(segmentStartTimeUs, newRelativeDiscontinuitySequence);
}
segments.add(segment);
segmentStartTimeUs += segment.durationUs;
partStartTimeUs = segmentStartTimeUs;
if (segment.byteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset = segment.byteRangeOffset + segment.byteRangeLength;
}
relativeDiscontinuitySequence = segment.relativeDiscontinuitySequence;
initializationSegment = segment.initializationSegment;
cachedDrmInitData = segment.drmInitData;
fullSegmentEncryptionKeyUri = segment.fullSegmentEncryptionKeyUri;
if (segment.encryptionIV == null
|| !segment.encryptionIV.equals(Long.toHexString(segmentMediaSequence))) {
fullSegmentEncryptionIV = segment.encryptionIV;
}
segmentMediaSequence++;
if (startIndex < 0 || endIndex > previousMediaPlaylist.segments.size()) {
// Throw to force a reload if not all segments are available in the previous playlist.
throw new DeltaUpdateException();
}
for (int i = startIndex; i < endIndex; i++) {
Segment segment = previousMediaPlaylist.segments.get(i);
if (mediaSequence != previousMediaPlaylist.mediaSequence) {
// If the media sequences of the playlists are not the same, we need to recreate the
// object with the updated relative start time and the relative discontinuity
// sequence. With identical playlist media sequences these values do not change.
int newRelativeDiscontinuitySequence =
previousMediaPlaylist.discontinuitySequence
- playlistDiscontinuitySequence
+ segment.relativeDiscontinuitySequence;
segment = segment.copyWith(segmentStartTimeUs, newRelativeDiscontinuitySequence);
}
segments.add(segment);
segmentStartTimeUs += segment.durationUs;
partStartTimeUs = segmentStartTimeUs;
if (segment.byteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset = segment.byteRangeOffset + segment.byteRangeLength;
}
relativeDiscontinuitySequence = segment.relativeDiscontinuitySequence;
initializationSegment = segment.initializationSegment;
cachedDrmInitData = segment.drmInitData;
fullSegmentEncryptionKeyUri = segment.fullSegmentEncryptionKeyUri;
if (segment.encryptionIV == null
|| !segment.encryptionIV.equals(Long.toHexString(segmentMediaSequence))) {
fullSegmentEncryptionIV = segment.encryptionIV;
}
segmentMediaSequence++;
}
} else if (line.startsWith(TAG_KEY)) {
String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);

View File

@ -37,7 +37,6 @@ import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -50,6 +49,8 @@ public class DefaultHlsPlaylistTrackerTest {
"media/m3u8/live_low_latency_master_media_uri_with_param";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL =
"media/m3u8/live_low_latency_media_can_skip_until";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR =
"media/m3u8/live_low_latency_media_can_skip_until_full_reload_after_error";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES =
"media/m3u8/live_low_latency_media_can_skip_dateranges";
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED =
@ -168,18 +169,21 @@ public class DefaultHlsPlaylistTrackerTest {
assertThat(mergedPlaylist.segments.get(1).relativeStartTimeUs).isEqualTo(4000000);
}
@Ignore // Test disabled because playlist delta updates are temporarily disabled.
@Test
public void start_playlistCanSkip_missingSegments_correctedMediaSequence()
public void start_playlistCanSkip_missingSegments_reloadsWithoutSkipping()
throws IOException, TimeoutException, InterruptedException {
List<HttpUrl> httpUrls =
enqueueWebServerResponses(
new String[] {
"/master.m3u8", "/media0/playlist.m3u8", "/media0/playlist.m3u8?_HLS_skip=YES"
"/master.m3u8",
"/media0/playlist.m3u8",
"/media0/playlist.m3u8?_HLS_skip=YES",
"/media0/playlist.m3u8"
},
getMockResponse(SAMPLE_M3U8_LIVE_MASTER),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING));
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING),
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_FULL_RELOAD_AFTER_ERROR));
List<HlsMediaPlaylist> mediaPlaylists =
runPlaylistTrackerAndCollectMediaPlaylists(
@ -192,8 +196,8 @@ public class DefaultHlsPlaylistTrackerTest {
assertThat(initialPlaylistWithAllSegments.mediaSequence).isEqualTo(10);
assertThat(initialPlaylistWithAllSegments.segments).hasSize(6);
HlsMediaPlaylist mergedPlaylist = mediaPlaylists.get(1);
assertThat(mergedPlaylist.mediaSequence).isEqualTo(22);
assertThat(mergedPlaylist.segments).hasSize(4);
assertThat(mergedPlaylist.mediaSequence).isEqualTo(20);
assertThat(mergedPlaylist.segments).hasSize(6);
}
@Test

View File

@ -2,8 +2,8 @@
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-BLOCK-RELOAD=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-SKIP:SKIPPED-SEGMENTS=2
#EXT-X-MEDIA-SEQUENCE:12
#EXT-X-SKIP:SKIPPED-SEGMENTS=2
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,

View File

@ -0,0 +1,17 @@
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:20
#EXTINF:4.00000,
fileSequence20.ts
#EXTINF:4.00000,
fileSequence21.ts
#EXTINF:4.00000,
fileSequence22.ts
#EXTINF:4.00000,
fileSequence23.ts
#EXTINF:4.00000,
fileSequence24.ts
#EXTINF:4.00000,
fileSequence25.ts