Skip to content

Commit

Permalink
feat(HLS): Add support to _HLS_part query param in LL streams (#5265)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Jun 7, 2023
1 parent 2ece86f commit ec8804d
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 18 deletions.
90 changes: 76 additions & 14 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,16 @@ shaka.hls.HlsParser = class {
// 'EXT-X-SKIP' tag in the media playlist.
queryData.add('_HLS_skip', 'YES');
}
if (streamInfo.lastMediaSequence >= 0) {
if (streamInfo.nextMediaSequence >= 0) {
// Indicates that the server must hold the request until a Playlist
// contains a Media Segment with Media Sequence
queryData.add('_HLS_msn', String(streamInfo.lastMediaSequence + 1));
queryData.add('_HLS_msn', String(streamInfo.nextMediaSequence));
}
if (streamInfo.nextPart >= 0) {
// Indicates, in combination with _HLS_msn, that the server must hold
// the request until a Playlist contains Partial Segment N of Media
// Sequence Number M or later.
queryData.add('_HLS_part', String(streamInfo.nextPart));
}
if (queryData.getCount()) {
uriObj.setQueryData(queryData);
Expand Down Expand Up @@ -399,17 +405,19 @@ shaka.hls.HlsParser = class {
this.playerInterface_.newDrmInfo(stream);
}

const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
streamInfo.lastMediaSequence = mediaSequenceNumber;

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

stream.segmentIndex.mergeAndEvict(
segments, this.presentationTimeline_.getSegmentAvailabilityStart());
if (segments.length) {
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
const {nextMediaSequence, nextPart} =
this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);
streamInfo.nextMediaSequence = nextMediaSequence;
streamInfo.nextPart = nextPart;
const playlistStartTime = mediaSequenceToStartTime.get(
mediaSequenceNumber);
stream.segmentIndex.evict(playlistStartTime);
Expand Down Expand Up @@ -1954,7 +1962,8 @@ shaka.hls.HlsParser = class {
canSkipSegments: false,
hasEndList: false,
firstSequenceNumber: -1,
lastMediaSequence: -1,
nextMediaSequence: -1,
nextPart: -1,
loadedOnce: false,
};

Expand Down Expand Up @@ -2001,7 +2010,8 @@ shaka.hls.HlsParser = class {
streamInfo.hasEndList = realStreamInfo.hasEndList;
streamInfo.mediaSequenceToStartTime =
realStreamInfo.mediaSequenceToStartTime;
streamInfo.lastMediaSequence = realStreamInfo.lastMediaSequence;
streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
streamInfo.nextPart = realStreamInfo.nextPart;
streamInfo.loadedOnce = true;
stream.segmentIndex = realStream.segmentIndex;
stream.encrypted = realStream.encrypted;
Expand Down Expand Up @@ -2278,7 +2288,8 @@ shaka.hls.HlsParser = class {
}

const firstStartTime = segments[0].startTime;
const lastEndTime = segments[segments.length - 1].endTime;
const lastSegment = segments[segments.length - 1];
const lastEndTime = lastSegment.endTime;
/** @type {!shaka.media.SegmentIndex} */
const segmentIndex = new shaka.media.SegmentIndex(segments);

Expand All @@ -2287,9 +2298,12 @@ shaka.hls.HlsParser = class {
const canSkipSegments = serverControlTag ?
serverControlTag.getAttribute('CAN-SKIP-UNTIL') != null : false;

const lastMediaSequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);

const {nextMediaSequence, nextPart} =
this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);

const stream = this.makeStreamObject_(codecs, type, language, primary, name,
channelsCount, closedCaptions, characteristics, forced, spatialAudio);
stream.segmentIndex = segmentIndex;
Expand All @@ -2308,12 +2322,54 @@ shaka.hls.HlsParser = class {
canSkipSegments,
hasEndList: false,
firstSequenceNumber: -1,
lastMediaSequence,
nextMediaSequence,
nextPart,
mediaSequenceToStartTime,
loadedOnce: false,
};
}

/**
* Get the next msn and part
*
* @param {number} mediaSequenceNumber
* @param {!Array.<!shaka.media.SegmentReference>} segments
* @return {{nextMediaSequence: number, nextPart:number}}}
* @private
*/
getNextMediaSequenceAndPart_(mediaSequenceNumber, segments) {
const currentMediaSequence = mediaSequenceNumber + segments.length - 1;
let nextMediaSequence = currentMediaSequence;
let nextPart = -1;
if (!segments.length) {
nextMediaSequence++;
return {
nextMediaSequence,
nextPart,
};
}
const lastSegment = segments[segments.length - 1];
const partialReferences = lastSegment.partialReferences;
if (!lastSegment.partialReferences.length) {
nextMediaSequence++;
return {
nextMediaSequence,
nextPart,
};
}
nextPart = partialReferences.length - 1;
const lastPartialReference =
partialReferences[partialReferences.length - 1];
if (!lastPartialReference.isPreload()) {
nextMediaSequence++;
nextPart = 0;
}
return {
nextMediaSequence,
nextPart,
};
}


/**
* Creates a stream object with the given parameters.
Expand Down Expand Up @@ -2831,6 +2887,9 @@ shaka.hls.HlsParser = class {
/* syncTime= */ null,
partialStatus,
hlsAes128Key);
if (item.name == 'EXT-X-PRELOAD-HINT') {
partial.markAsPreload();
}
partialSegmentRefs.push(partial);
} // for-loop of hlsSegment.partialSegments
}
Expand Down Expand Up @@ -3622,7 +3681,8 @@ shaka.hls.HlsParser = class {
* canSkipSegments: boolean,
* hasEndList: boolean,
* firstSequenceNumber: number,
* lastMediaSequence: number,
* nextMediaSequence: number,
* nextPart: number,
* loadedOnce: boolean
* }}
*
Expand Down Expand Up @@ -3655,8 +3715,10 @@ shaka.hls.HlsParser = class {
* True if the stream has an EXT-X-ENDLIST tag.
* @property {number} firstSequenceNumber
* The sequence number of the first reference. Only calculated if needed.
* @property {number} lastMediaSequence
* The last media sequence seen.
* @property {number} nextMediaSequence
* The next media sequence.
* @property {number} nextPart
* The next part.
* @property {boolean} loadedOnce
* True if the stream has been loaded at least once.
*/
Expand Down
22 changes: 22 additions & 0 deletions lib/media/segment_reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ shaka.media.SegmentReference = class {
/** @type {shaka.media.SegmentReference.Status} */
this.status = status;

/** @type {boolean} */
this.preload = false;

/** @type {?shaka.extern.HlsAes128Key} */
this.hlsAes128Key = hlsAes128Key;

Expand Down Expand Up @@ -385,6 +388,25 @@ shaka.media.SegmentReference = class {
this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
}

/**
* Mark the reference as preload.
*
* @export
*/
markAsPreload() {
this.preload = true;
}

/**
* Returns true if the segment is preloaded.
*
* @return {boolean}
* @export
*/
isPreload() {
return this.preload;
}

/**
* Set the segment's thumbnail sprite.
*
Expand Down
9 changes: 5 additions & 4 deletions test/hls/hls_live_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ describe('HlsParser live', () => {
const preloadRef = makeReference(
'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
/* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
preloadRef.markAsPreload();

// ref2 is not fully published yet, so it doesn't have a segment uri.
const ref2 = makeReference(
Expand Down Expand Up @@ -930,7 +931,7 @@ describe('HlsParser live', () => {
].join('');

fakeNetEngine.setResponseText(
'test:/video?_HLS_skip=YES&_HLS_msn=1', mediaWithSkippedSegments);
'test:/video?_HLS_skip=YES&_HLS_msn=2', mediaWithSkippedSegments);

playerInterface.isLowLatencyMode = () => true;

Expand All @@ -940,7 +941,7 @@ describe('HlsParser live', () => {
await delayForUpdatePeriod();

fakeNetEngine.expectRequest(
'test:/video?_HLS_skip=YES&_HLS_msn=1',
'test:/video?_HLS_skip=YES&_HLS_msn=2',
shaka.net.NetworkingEngine.RequestType.MANIFEST,
{type:
shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_PLAYLIST});
Expand Down Expand Up @@ -974,7 +975,7 @@ describe('HlsParser live', () => {
// and ref1 should be in the SegmentReferences list.
// ref3 should be appended to the SegmentReferences list.
await testUpdate(
manifest, mediaWithSkippedSegments, [ref1, ref2, ref3], 1);
manifest, mediaWithSkippedSegments, [ref1, ref2, ref3], 2);
});

it('skips older segments with discontinuity', async () => {
Expand Down Expand Up @@ -1039,7 +1040,7 @@ describe('HlsParser live', () => {
// and ref1,ref2 should be in the SegmentReferences list.
// ref3,ref4 should be appended to the SegmentReferences list.
await testUpdate(
manifest, mediaWithSkippedSegments2, [ref1, ref2, ref3, ref4], 1);
manifest, mediaWithSkippedSegments2, [ref1, ref2, ref3, ref4], 3);
});

it('updates encryption keys', async () => {
Expand Down

0 comments on commit ec8804d

Please sign in to comment.