From 263a17b984d7014e9d080e4b7437a78c7620aec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Fri, 23 Jun 2023 13:27:17 +0200 Subject: [PATCH] feat(HLS): Support byterange optimization on servers with support to blocking playlist reload (#5347) Some servers only support blocking playlist reload for a short time (example: 3 partial segment times). If we are using byte range optimization we must change the reload time to be that of a complete segment and avoid these problems. --- lib/hls/hls_parser.js | 76 +++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 35451eb4d1..8fa7756e3d 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -212,6 +212,9 @@ shaka.hls.HlsParser = class { /** @private {boolean} */ this.lowLatencyMode_ = false; + + /** @private {boolean} */ + this.lowLatencyByterangeOptimization_ = false; } @@ -385,8 +388,6 @@ shaka.hls.HlsParser = class { shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY); } - this.determineLastTargetDuration_(playlist); - /** @type {!Array.} */ const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE'); @@ -446,6 +447,8 @@ shaka.hls.HlsParser = class { // until we've seen the ENDLIST tag for all active playlists. streamInfo.hasEndList = true; } + + this.determineLastTargetDuration_(playlist); } @@ -2265,6 +2268,10 @@ shaka.hls.HlsParser = class { this.determinePresentationType_(playlist); + if (this.isLive_()) { + this.determineLastTargetDuration_(playlist); + } + if (!mimeType) { mimeType = await this.guessMimeType_(type, codecs, playlist, mediaVariables); @@ -2282,14 +2289,12 @@ shaka.hls.HlsParser = class { const mediaSequenceToStartTime = this.isLive_() ? this.mediaSequenceToStartTimeByType_.get(type) : new Map(); - const prevLowLatencyMode = this.lowLatencyMode_; const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables); - // This happens when autoLowLatencyMode is true, so we need set the - // correct lowLatencyPresentationDelay_ - if (prevLowLatencyMode != this.lowLatencyMode_) { - this.determinePresentationType_(playlist); + // This new calculation is necessary for Low Latency streams. + if (this.isLive_()) { + this.determineLastTargetDuration_(playlist); } const firstStartTime = segments[0].startTime; @@ -2609,8 +2614,6 @@ shaka.hls.HlsParser = class { } else { this.setPresentationType_(PresentationType.EVENT); } - - this.determineLastTargetDuration_(playlist); } } @@ -2620,11 +2623,34 @@ shaka.hls.HlsParser = class { * @private */ determineLastTargetDuration_(playlist) { + let lastTargetDuration = Infinity; + const segments = playlist.segments; + if (segments.length) { + let segmentIndex = segments.length - 1; + while (segmentIndex >= 0) { + const segment = segments[segmentIndex]; + const extinfTag = + shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXTINF'); + if (extinfTag) { + // The EXTINF tag format is '#EXTINF:,[]'. + // We're interested in the duration part. + const extinfValues = extinfTag.value.split(','); + lastTargetDuration = Number(extinfValues[0]); + break; + } + segmentIndex--; + } + } + const targetDurationTag = this.getRequiredTag_(playlist.tags, 'EXT-X-TARGETDURATION'); const targetDuration = Number(targetDurationTag.value); const partialTargetDurationTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF'); + if (partialTargetDurationTag) { + this.partialTargetDuration_ = Number( + partialTargetDurationTag.getRequiredAttrValue('PART-TARGET')); + } // Get the server-recommended min distance from the live edge. const serverControlTag = shaka.hls.Utils.getFirstTagWithName( playlist.tags, 'EXT-X-SERVER-CONTROL'); @@ -2635,34 +2661,19 @@ shaka.hls.HlsParser = class { // targetDuration value across all playlists. // 1. Update the shortest one to use as update period and segment // availability time (for LIVE). - if (this.lowLatencyMode_ && partialTargetDurationTag) { + if (this.lowLatencyMode_ && this.partialTargetDuration_) { // For low latency streaming, use the partial segment target duration. - this.partialTargetDuration_ = Number( - partialTargetDurationTag.getRequiredAttrValue('PART-TARGET')); - this.lastTargetDuration_ = Math.min( - this.partialTargetDuration_, this.lastTargetDuration_); + if (this.lowLatencyByterangeOptimization_) { + this.lastTargetDuration_ = Math.min( + lastTargetDuration, this.lastTargetDuration_); + } else { + this.lastTargetDuration_ = Math.min( + this.partialTargetDuration_, this.lastTargetDuration_); + } // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode. this.lowLatencyPresentationDelay_ = serverControlTag ? Number( serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0; } else { - let lastTargetDuration = Infinity; - const segments = playlist.segments; - if (segments.length) { - let segmentIndex = segments.length - 1; - while (segmentIndex >= 0) { - const segment = segments[segmentIndex]; - const extinfTag = - shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXTINF'); - if (extinfTag) { - // The EXTINF tag format is '#EXTINF:<duration>,[<title>]'. - // We're interested in the duration part. - const extinfValues = extinfTag.value.split(','); - lastTargetDuration = Number(extinfValues[0]); - break; - } - segmentIndex--; - } - } this.lastTargetDuration_ = Math.min( lastTargetDuration, this.lastTargetDuration_); // Use 'HOLD-BACK' as the presentation delay for default if defined. @@ -3047,6 +3058,7 @@ shaka.hls.HlsParser = class { ); if (segmentWithByteRangeOptimization) { + this.lowLatencyByterangeOptimization_ = true; reference.markAsByterangeOptimization(); if (isPreloadSegment) {