Skip to content

Commit

Permalink
Stop using segment position in HLS parser
Browse files Browse the repository at this point in the history
As part of Period-flattening, I'm trying to reduce our dependence on
the "position" field of SegmentReference.  If it can be eliminated, we
can more easily concatenate Arrays of SegmentReferences without
modifying them.

The HLS parser uses positions to find previously-resolved segment
times when refreshing a live playlist.  What we're really doing is
using HLS media sequence numbers, since we use those to assign the
segment positions in the first place.

This change makes the use of media sequence numbers more explicit, and
adds a per-Stream map from media sequence number to segment start
time.

The only other users of segment positions are StreamingEngine and
various tests.

Issue #1339 (period-flattening)

Change-Id: If970ed2c8722ed5779a51349ca2e64208d78130d
  • Loading branch information
joeyparrish committed Mar 5, 2020
1 parent ea0131d commit b0238c3
Showing 1 changed file with 36 additions and 20 deletions.
56 changes: 36 additions & 20 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ shaka.hls.HlsParser = class {

const segments = await this.createSegments_(
streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
stream.mimeType);
stream.mimeType, streamInfo.mediaSequenceToStartTime);

stream.segmentIndex.replace(segments);

Expand Down Expand Up @@ -1169,10 +1169,13 @@ shaka.hls.HlsParser = class {
codecs = '';
}

/** @type {!Map.<number, number>} */
const mediaSequenceToStartTime = new Map();

let segments;
try {
segments = await this.createSegments_(verbatimMediaPlaylistUri,
playlist, type, mimeType);
playlist, type, mimeType, mediaSequenceToStartTime);
} catch (error) {
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
Expand Down Expand Up @@ -1221,12 +1224,13 @@ shaka.hls.HlsParser = class {
};

return {
stream: stream,
drmInfos: drmInfos,
verbatimMediaPlaylistUri: verbatimMediaPlaylistUri,
absoluteMediaPlaylistUri: absoluteMediaPlaylistUri,
minTimestamp: minTimestamp,
stream,
drmInfos,
verbatimMediaPlaylistUri,
absoluteMediaPlaylistUri,
minTimestamp,
maxTimestamp: lastEndTime,
mediaSequenceToStartTime,
};
}

Expand Down Expand Up @@ -1448,10 +1452,13 @@ shaka.hls.HlsParser = class {
* @param {!shaka.hls.Playlist} playlist
* @param {string} type
* @param {string} mimeType
* @param {!Map.<number, number>} mediaSequenceToStartTime
* @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
* @private
*/
async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType) {
async createSegments_(
verbatimMediaPlaylistUri, playlist, type, mimeType,
mediaSequenceToStartTime) {
/** @type {Array.<!shaka.hls.Segment>} */
const hlsSegments = playlist.segments;
/** @type {!Array.<!shaka.media.SegmentReference>} */
Expand All @@ -1464,7 +1471,8 @@ shaka.hls.HlsParser = class {

const mediaSequenceTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags,
'EXT-X-MEDIA-SEQUENCE');
const startPosition = mediaSequenceTag ? Number(mediaSequenceTag.value) : 0;
const mediaSequenceNumber =
mediaSequenceTag ? Number(mediaSequenceTag.value) : 0;

/** @type {?shaka.hls.Tag} */
let mapTag = shaka.hls.Utils.getFirstTagWithName(hlsSegments[0].tags,
Expand All @@ -1477,20 +1485,22 @@ shaka.hls.HlsParser = class {
initSegmentRef,
/* previousReference= */ null,
hlsSegments[0],
startPosition,
mediaSequenceNumber,
/* startTime= */ 0,
/* timestampOffset= */ 0);

const firstStartTime = await this.getPlaylistStartTime_(
verbatimMediaPlaylistUri, initSegmentRef, firstSegmentRef, type,
mimeType);
mimeType, mediaSequenceNumber);
shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
'starts at', firstStartTime);
const enumerate = (it) => shaka.util.Iterables.enumerate(it);
for (const {i, item} of enumerate(hlsSegments)) {
const previousReference = references[references.length - 1];
const startTime = (i == 0) ? firstStartTime : previousReference.endTime;
const position = startPosition + i;
const position = mediaSequenceNumber + i;

mediaSequenceToStartTime.set(position, startTime);

mapTag = shaka.hls.Utils.getFirstTagWithName(item.tags, 'EXT-X-MAP');
initSegmentRef = mapTag ?
Expand Down Expand Up @@ -1591,12 +1601,13 @@ shaka.hls.HlsParser = class {
* @param {!shaka.media.SegmentReference} firstSegmentRef
* @param {string} contentType
* @param {string} mimeType
* @param {number} mediaSequenceNumber
* @return {!Promise.<number>}
* @throws {shaka.util.Error}
* @private
*/
async getPlaylistStartTime_(verbatimMediaPlaylistUri, initSegmentRef,
firstSegmentRef, contentType, mimeType) {
firstSegmentRef, contentType, mimeType, mediaSequenceNumber) {
// For VOD and EVENT playlists, all variants must start at the same time, so
// we can fetch the start time once and reuse for the others.
// This is not guaranteed when updating a LIVE stream, we assume the first
Expand All @@ -1608,7 +1619,7 @@ shaka.hls.HlsParser = class {
'Should only get start time from audio or video streams');
this.playlistStartTime_ = await this.getStartTime_(
verbatimMediaPlaylistUri, initSegmentRef, firstSegmentRef,
mimeType);
mimeType, mediaSequenceNumber);
shaka.log.debug('Fetched start time for', contentType);
} else {
shaka.log.debug('Reusing cached start time for', contentType);
Expand All @@ -1624,11 +1635,13 @@ shaka.hls.HlsParser = class {
* @param {shaka.media.InitSegmentReference} initSegmentRef
* @param {!shaka.media.SegmentReference} segmentRef
* @param {string} mimeType
* @param {number} mediaSequenceNumber
* @return {!Promise.<number>}
* @private
*/
async getStartTime_(
verbatimMediaPlaylistUri, initSegmentRef, segmentRef, mimeType) {
verbatimMediaPlaylistUri, initSegmentRef, segmentRef, mimeType,
mediaSequenceNumber) {
// If we are updating the manifest, we can usually skip fetching the segment
// by examining the references we already have. This won't be possible if
// there was some kind of lag or delay updating the manifest on the server,
Expand All @@ -1638,12 +1651,12 @@ shaka.hls.HlsParser = class {
if (this.manifest_) {
const streamInfo =
this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
const segmentIndex = streamInfo.stream.segmentIndex;
const reference = segmentIndex.get(segmentRef.position);
if (reference) {
const startTime = streamInfo.mediaSequenceToStartTime.get(
mediaSequenceNumber);
if (startTime != undefined) {
// We found it! Avoid fetching and parsing the segment.
shaka.log.v1('Found segment start time in previous manifest');
return reference.startTime;
return startTime;
}

shaka.log.debug(
Expand Down Expand Up @@ -2220,7 +2233,8 @@ shaka.hls.HlsParser = class {
* verbatimMediaPlaylistUri: string,
* absoluteMediaPlaylistUri: string,
* minTimestamp: number,
* maxTimestamp: number
* maxTimestamp: number,
* mediaSequenceToStartTime: !Map.<number, number>
* }}
*
* @description
Expand All @@ -2242,6 +2256,8 @@ shaka.hls.HlsParser = class {
* The minimum timestamp found in the stream.
* @property {number} maxTimestamp
* The maximum timestamp found in the stream.
* @property {!Map.<number, number>} mediaSequenceToStartTime
* A map of media sequence numbers to media start times.
*/
shaka.hls.HlsParser.StreamInfo;

Expand Down

0 comments on commit b0238c3

Please sign in to comment.