Skip to content

Commit

Permalink
feat(HLS): Poll HLS playlists using last segment duration (shaka-proj…
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Dec 7, 2022
1 parent 806a9a8 commit 1ba3806
Showing 1 changed file with 78 additions and 53 deletions.
131 changes: 78 additions & 53 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,6 @@ shaka.hls.HlsParser = class {
/** @private {shaka.hls.ManifestTextParser} */
this.manifestTextParser_ = new shaka.hls.ManifestTextParser();

/**
* This is the number of seconds we want to wait between finishing a
* manifest update and starting the next one. This will be set when we parse
* the manifest.
*
* @private {number}
*/
this.updatePlaylistDelay_ = 0;

/**
* The minimum sequence number for generated segments, when ignoring
* EXT-X-PROGRAM-DATE-TIME.
Expand Down Expand Up @@ -186,7 +177,7 @@ shaka.hls.HlsParser = class {
this.maxTargetDuration_ = 0;

/** @private {number} */
this.minTargetDuration_ = Infinity;
this.lastTargetDuration_ = Infinity;

/** Partial segments target duration.
* @private {number}
Expand Down Expand Up @@ -294,6 +285,9 @@ shaka.hls.HlsParser = class {
const updates = [];
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());

// This is necessary to calculate correctly the update time.
this.lastTargetDuration_ = Infinity;

// Only update active streams.
const activeStreamInfos = streamInfos.filter((s) => s.stream.segmentIndex);
for (const streamInfo of activeStreamInfos) {
Expand Down Expand Up @@ -363,6 +357,8 @@ 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 @@ -735,7 +731,7 @@ shaka.hls.HlsParser = class {
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
if (this.isLive_()) {
this.changePresentationTimelineToLive_();
const delay = this.updatePlaylistDelay_;
const delay = this.getUpdatePlaylistDelay_();
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
}
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
Expand All @@ -761,13 +757,6 @@ shaka.hls.HlsParser = class {
'Presentation timeline not created!');

if (this.isLive_()) {
// The HLS spec (RFC 8216) states in 6.3.4:
// "the client MUST wait for at least the target duration before
// attempting to reload the Playlist file again".
// For LL-HLS, the server must add a new partial segment to the Playlist
// every part target duration.
this.updatePlaylistDelay_ = this.minTargetDuration_;

// The spec says nothing much about seeking in live content, but Safari's
// built-in HLS implementation does not allow it. Therefore we will set
// the availability window equal to the presentation delay. The player
Expand Down Expand Up @@ -1723,7 +1712,7 @@ shaka.hls.HlsParser = class {
this.determineDuration_();
// Finally, start the update timer, if this asset has been determined
// to be a livestream.
const delay = this.updatePlaylistDelay_;
const delay = this.getUpdatePlaylistDelay_();
if (delay > 0) {
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
}
Expand Down Expand Up @@ -2210,42 +2199,64 @@ shaka.hls.HlsParser = class {
this.setPresentationType_(PresentationType.EVENT);
}

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');
// According to the HLS spec, updates should not happen more often than
// once in targetDuration. It also requires us to only update the active
// variant. We might implement that later, but for now every variant
// will be updated. To get the update period, choose the smallest
// 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) {
// For low latency streaming, use the partial segment target duration.
this.partialTargetDuration_ = Number(
partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
this.minTargetDuration_ = Math.min(
this.partialTargetDuration_, this.minTargetDuration_);
// Get the server-recommended min distance from the live edge.
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
playlist.tags, 'EXT-X-SERVER-CONTROL');
// Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
} else {
// For regular HLS, use the target duration of regular segments.
this.minTargetDuration_ = Math.min(
targetDuration, this.minTargetDuration_);
this.determineLastTargetDuration_(playlist);
}
}


/**
* @param {!shaka.hls.Playlist} playlist
* @private
*/
determineLastTargetDuration_(playlist) {
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');
// According to the HLS spec, updates should not happen more often than
// once in targetDuration. It also requires us to only update the active
// variant. We might implement that later, but for now every variant
// will be updated. To get the update period, choose the smallest
// 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) {
// 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_);
// Get the server-recommended min distance from the live edge.
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
playlist.tags, 'EXT-X-SERVER-CONTROL');
// 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) {
const lastSegment = segments[segments.length - 1];
const extinfTag =
shaka.hls.Utils.getFirstTagWithName(lastSegment.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]);
}
}
// 2. Update the longest target duration if need be to use as a
// presentation delay later.
this.maxTargetDuration_ = Math.max(
targetDuration, this.maxTargetDuration_);
this.lastTargetDuration_ = Math.min(
lastTargetDuration, this.lastTargetDuration_);
}
// 2. Update the longest target duration if need be to use as a
// presentation delay later.
this.maxTargetDuration_ = Math.max(
targetDuration, this.maxTargetDuration_);
}


/**
* @private
*/
Expand Down Expand Up @@ -2967,7 +2978,7 @@ shaka.hls.HlsParser = class {
shaka.log.info('Updating manifest...');

goog.asserts.assert(
this.updatePlaylistDelay_ > 0,
this.getUpdatePlaylistDelay_() > 0,
'We should only call |onUpdate_| when we are suppose to be updating.');

// Detect a call to stop()
Expand All @@ -2980,7 +2991,7 @@ shaka.hls.HlsParser = class {

// This may have converted to VOD, in which case we stop updating.
if (this.isLive_()) {
const delay = this.updatePlaylistDelay_;
const delay = this.getUpdatePlaylistDelay_();
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
}
} catch (error) {
Expand Down Expand Up @@ -3012,6 +3023,20 @@ shaka.hls.HlsParser = class {
}


/**
* @return {number}
* @private
*/
getUpdatePlaylistDelay_() {
// The HLS spec (RFC 8216) states in 6.3.4:
// "the client MUST wait for at least the target duration before
// attempting to reload the Playlist file again".
// For LL-HLS, the server must add a new partial segment to the Playlist
// every part target duration.
return this.lastTargetDuration_;
}


/**
* @param {shaka.hls.HlsParser.PresentationType_} type
* @private
Expand Down

0 comments on commit 1ba3806

Please sign in to comment.