Skip to content

Commit

Permalink
feat(HLS): Support byterange optimization on servers with support to …
Browse files Browse the repository at this point in the history
…blocking playlist reload (shaka-project#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.
  • Loading branch information
avelad committed Jun 23, 2023
1 parent 79bb361 commit 263a17b
Showing 1 changed file with 44 additions and 32 deletions.
76 changes: 44 additions & 32 deletions lib/hls/hls_parser.js
Expand Up @@ -212,6 +212,9 @@ shaka.hls.HlsParser = class {

/** @private {boolean} */
this.lowLatencyMode_ = false;

/** @private {boolean} */
this.lowLatencyByterangeOptimization_ = false;
}


Expand Down Expand Up @@ -385,8 +388,6 @@ shaka.hls.HlsParser = class {
shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
}

this.determineLastTargetDuration_(playlist);

/** @type {!Array.<!shaka.hls.Tag>} */
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
'EXT-X-DEFINE');
Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -2609,8 +2614,6 @@ shaka.hls.HlsParser = class {
} else {
this.setPresentationType_(PresentationType.EVENT);
}

this.determineLastTargetDuration_(playlist);
}
}

Expand All @@ -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:<duration>,[<title>]'.
// 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');
Expand All @@ -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.
Expand Down Expand Up @@ -3047,6 +3058,7 @@ shaka.hls.HlsParser = class {
);

if (segmentWithByteRangeOptimization) {
this.lowLatencyByterangeOptimization_ = true;
reference.markAsByterangeOptimization();

if (isPreloadSegment) {
Expand Down

0 comments on commit 263a17b

Please sign in to comment.