Reload HLS media playlist when merging delta update fails
Issue: #5011 PiperOrigin-RevId: 350550204
This commit is contained in:
parent
6dec83238b
commit
6e8af81ddc
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user