feat(hls): parse EXT-X-GAP (#4134)

Parse EXT-X-GAP HLS tag and add a status enum to shaka.media.SegmentReference.

shaka.media.SegmentReference.Status.AVAILABLE --> Normal behaviour
shaka.media.SegmentReference.Status. UNAVAILABLE --> Related to https://github.com/shaka-project/shaka-player/issues/2541
shaka.media.SegmentReference.Status. MISSING --> EXT-X-GAP in HLS

Note: only the parsing is added, but the functionality is not yet implemented.

Issue https://github.com/shaka-project/shaka-player/issues/1308
This commit is contained in:
Álvaro Velad Galván
2022-04-18 19:22:30 +02:00
committed by GitHub
parent 4fecfb9652
commit 42eecc84f9
3 changed files with 145 additions and 1 deletions
+6
View File
@@ -1865,6 +1865,11 @@ shaka.hls.HlsParser = class {
}
}
let status = shaka.media.SegmentReference.Status.AVAILABLE;
if (shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-GAP')) {
status = shaka.media.SegmentReference.Status.MISSING;
}
// Create SegmentReferences for the partial segments.
const partialSegmentRefs = [];
if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
@@ -1978,6 +1983,7 @@ shaka.hls.HlsParser = class {
tilesLayout,
tileDuration,
syncTime,
status,
);
}
+40 -1
View File
@@ -162,12 +162,15 @@ shaka.media.SegmentReference = class {
* @param {?number=} syncTime
* A time value, expressed in the same scale as the start and end time, which
* is used to synchronize between streams.
* @param {shaka.media.SegmentReference.Status=} status
* The segment status is used to indicate that a segment does not exist or is
* not available.
*/
constructor(
startTime, endTime, uris, startByte, endByte, initSegmentReference,
timestampOffset, appendWindowStart, appendWindowEnd,
partialReferences = [], tilesLayout = '', tileDuration = null,
syncTime = null) {
syncTime = null, status = shaka.media.SegmentReference.Status.AVAILABLE) {
// A preload hinted Partial Segment has the same startTime and endTime.
goog.asserts.assert(startTime <= endTime,
'startTime must be less than or equal to endTime');
@@ -220,6 +223,9 @@ shaka.media.SegmentReference = class {
/** @type {?number} */
this.syncTime = syncTime;
/** @type {shaka.media.SegmentReference.Status} */
this.status = status;
}
/**
@@ -315,6 +321,39 @@ shaka.media.SegmentReference = class {
getTileDuration() {
return this.tileDuration;
}
/**
* Returns the segment's status.
*
* @return {shaka.media.SegmentReference.Status}
* @export
*/
getStatus() {
return this.status;
}
/**
* Mark the reference as unavailable.
*
* @export
*/
markAsUnavailable() {
this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
}
};
/**
* Rather than using booleans to communicate what the state of the reference,
* we have this enum.
*
* @enum {number}
* @export
*/
shaka.media.SegmentReference.Status = {
AVAILABLE: 0,
UNAVAILABLE: 1,
MISSING: 2,
};
+99
View File
@@ -1424,6 +1424,105 @@ describe('HlsParser', () => {
}
});
it('parse EXT-X-GAP', async () => {
const master = [
'#EXTM3U\n',
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
'CHANNELS="2",URI="audio"\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
'video\n',
].join('');
const video = [
'#EXTM3U\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
'#EXTINF:5,\n',
'main.mp4\n',
'#EXTINF:5,\n',
'#EXT-X-GAP\n',
'main.mp4\n',
'#EXTINF:5,\n',
'main.mp4\n',
].join('');
const audio = [
'#EXTM3U\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
'#EXTINF:5,\n',
'main.mp4\n',
'#EXTINF:5,\n',
'#EXT-X-GAP\n',
'main.mp4\n',
'#EXTINF:5,\n',
'main.mp4\n',
].join('');
fakeNetEngine
.setResponseText('test:/master', master)
.setResponseText('test:/audio', audio)
.setResponseText('test:/video', video)
.setResponseValue('test:/init.mp4', initSegmentData)
.setResponseValue('test:/main.mp4', segmentData);
const actual = await parser.start('test:/master', playerInterface);
expect(actual.variants.length).toBe(1);
const variant = actual.variants[0];
expect(variant.video).toBeTruthy();
expect(variant.audio).toBeTruthy();
const available = shaka.media.SegmentReference.Status.AVAILABLE;
const missing = shaka.media.SegmentReference.Status.MISSING;
await variant.video.createSegmentIndex();
goog.asserts.assert(variant.video.segmentIndex != null,
'Null segmentIndex!');
const firstVideoReference = variant.video.segmentIndex.get(0);
const secondVideoReference = variant.video.segmentIndex.get(1);
const thirdVideoReference = variant.video.segmentIndex.get(2);
expect(firstVideoReference).not.toBe(null);
expect(secondVideoReference).not.toBe(null);
expect(thirdVideoReference).not.toBe(null);
if (firstVideoReference) {
expect(firstVideoReference.getStatus()).toBe(available);
}
if (secondVideoReference) {
expect(secondVideoReference.getStatus()).toBe(missing);
}
if (thirdVideoReference) {
expect(thirdVideoReference.getStatus()).toBe(available);
}
await variant.audio.createSegmentIndex();
goog.asserts.assert(variant.audio.segmentIndex != null,
'Null segmentIndex!');
const firstAudioReference = variant.audio.segmentIndex.get(0);
const secondAudioReference = variant.audio.segmentIndex.get(1);
const thirdAudioReference = variant.audio.segmentIndex.get(2);
expect(firstAudioReference).not.toBe(null);
expect(secondAudioReference).not.toBe(null);
expect(thirdAudioReference).not.toBe(null);
if (firstAudioReference) {
expect(firstAudioReference.getStatus()).toBe(available);
}
if (secondAudioReference) {
expect(secondAudioReference.getStatus()).toBe(missing);
}
if (thirdAudioReference) {
expect(thirdAudioReference.getStatus()).toBe(available);
}
});
it('Disable audio does not create audio streams', async () => {
const master = [
'#EXTM3U\n',