Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(HLS): Add support to _HLS_part query param in LL streams #5265

Merged
merged 1 commit into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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