diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 5817a1d06d..5e5d0c0f86 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -1825,7 +1825,7 @@ shaka.hls.HlsParser = class { * @param {!Map.} variables * @param {string} absoluteMediaPlaylistUri * @param {string} type - * @return {!shaka.media.SegmentReference} + * @return {shaka.media.SegmentReference} * @private */ createSegmentReference_( @@ -1870,9 +1870,23 @@ shaka.hls.HlsParser = class { status = shaka.media.SegmentReference.Status.MISSING; } + if (!extinfTag) { + if (hlsSegment.partialSegments.length == 0) { + // EXTINF tag must be available if the segment has no partial segments. + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.MANIFEST, + shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF'); + } else if (!this.lowLatencyMode_) { + // Without EXTINF and without low-latency mode, partial segments get + // ignored. + return null; + } + } + // Create SegmentReferences for the partial segments. const partialSegmentRefs = []; - if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) { + if (this.lowLatencyMode_) { for (let i = 0; i < hlsSegment.partialSegments.length; i++) { const item = hlsSegment.partialSegments[i]; const pPreviousReference = i == 0 ? @@ -1913,14 +1927,6 @@ shaka.hls.HlsParser = class { /* appendWindowEnd= */ Infinity); partialSegmentRefs.push(partial); } // for-loop of hlsSegment.partialSegments - } else { - // EXTINF tag must be available if the segment has no partial segments. - if (!extinfTag) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.MANIFEST, - shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF'); - } } // If the segment has EXTINF tag, set the segment's end time, start byte @@ -1939,6 +1945,7 @@ shaka.hls.HlsParser = class { } else { endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime; } + // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte // base on the byterange information. If segment has no EXT-X-BYTERANGE tag // and has partial segments, set the start byte and end byte base on the @@ -2107,7 +2114,9 @@ shaka.hls.HlsParser = class { playlist.absoluteUri, type); - references.push(reference); + if (reference) { + references.push(reference); + } } // If some segments have sync times, but not all, extrapolate the sync diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js index d50150d5ac..e5994a1d0f 100644 --- a/test/hls/hls_live_unit.js +++ b/test/hls/hls_live_unit.js @@ -603,6 +603,28 @@ describe('HlsParser live', () => { ManifestParser.verifySegmentIndex(video, [ref, ref2]); }); + // Test for https://github.com/shaka-project/shaka-player/issues/4185 + it('does not fail on preload hints with LL mode off', async () => { + // LL mode must be off for this test! + playerInterface.isLowLatencyMode = () => false; + + const mediaWithPartialSegments = [ + '#EXTM3U\n', + '#EXT-X-TARGETDURATION:5\n', + '#EXTINF:4,\n', + 'main.mp4\n', + '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n', + '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n', + ].join(''); + + fakeNetEngine + .setResponseText('test:/master', master) + .setResponseText('test:/video', mediaWithPartialSegments); + + // If this throws, the test fails. Otherwise, it passes. + await parser.start('test:/master', playerInterface); + }); + describe('update', () => { it('adds new segments when they appear', async () => { const ref1 = makeReference(