diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 6f06803ad0..c4b8baa776 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -302,7 +302,7 @@ shaka.hls.HlsParser = class { const PresentationType = shaka.hls.HlsParser.PresentationType_; const manifestUri = streamInfo.absoluteMediaPlaylistUri; const uriObj = new goog.Uri(manifestUri); - if (this.lowLatencyMode_) { + if (this.lowLatencyMode_ && streamInfo.canSkipSegments) { // Enable delta updates. This will replace older segments with // 'EXT-X-SKIP' tag in the media playlist. uriObj.setQueryData(new goog.Uri.QueryData('_HLS_skip=YES')); @@ -1383,6 +1383,11 @@ shaka.hls.HlsParser = class { } } + const serverControlTag = shaka.hls.Utils.getFirstTagWithName( + playlist.tags, 'EXT-X-SERVER-CONTROL'); + const canSkipSegments = serverControlTag ? + serverControlTag.getAttribute('CAN-SKIP-UNTIL') != null : false; + /** @type {shaka.extern.Stream} */ const stream = { id: this.globalId_++, @@ -1425,6 +1430,7 @@ shaka.hls.HlsParser = class { maxTimestamp: lastEndTime, mediaSequenceToStartTime, discontinuityToMediaSequence, + canSkipSegments, }; } @@ -2658,7 +2664,8 @@ shaka.hls.HlsParser = class { * minTimestamp: number, * maxTimestamp: number, * mediaSequenceToStartTime: !Map., - * discontinuityToMediaSequence: !Map. + * discontinuityToMediaSequence: !Map., + * canSkipSegments: boolean * }} * * @description @@ -2683,6 +2690,9 @@ shaka.hls.HlsParser = class { * @property {!Map.} discontinuityToMediaSequence * A map of discontinuity sequence numbers to the media sequence number of the * segment starting with that discontinuity sequence number. + * @property {boolean} canSkipSegments + * True if the server supports delta playlist updates, and we can send a + * request for a playlist that can skip older media segments. */ shaka.hls.HlsParser.StreamInfo; diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js index f9cb77b698..c3487fd9e7 100644 --- a/test/hls/hls_live_unit.js +++ b/test/hls/hls_live_unit.js @@ -907,6 +907,56 @@ describe('HlsParser live', () => { partialEndByte); // partial segment request }); + it('request playlist delta updates to skip segments', async () => { + const mediaWithDeltaUpdates = [ + '#EXTM3U\n', + '#EXT-X-PLAYLIST-TYPE:LIVE\n', + '#EXT-X-TARGETDURATION:5\n', + '#EXT-X-MEDIA-SEQUENCE:0\n', + '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', + '#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=60.0\n', + '#EXTINF:2,\n', + 'main.mp4\n', + '#EXTINF:2,\n', + 'main2.mp4\n', + ].join(''); + + const mediaWithSkippedSegments = [ + '#EXTM3U\n', + '#EXT-X-TARGETDURATION:5\n', + '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', + '#EXT-X-MEDIA-SEQUENCE:0\n', + '#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=60.0\n', + '#EXT-X-SKIP:SKIPPED-SEGMENTS=1\n', + '#EXTINF:2,\n', + 'main2.mp4\n', + '#EXTINF:2,\n', + 'main3.mp4\n', + ].join(''); + + fakeNetEngine + .setResponseText('test:/master', master) + .setResponseText('test:/video', mediaWithDeltaUpdates) + .setResponseText('test:/video?_HLS_skip=YES', + mediaWithSkippedSegments) + .setResponseValue('test:/init.mp4', initSegmentData) + .setResponseValue('test:/main.mp4', segmentData) + .setResponseValue('test:/main2.mp4', segmentData) + .setResponseValue('test:/main3.mp4', segmentData); + + playerInterface.isLowLatencyMode = () => true; + await parser.start('test:/master', playerInterface); + // Replace the entries with the updated values. + + fakeNetEngine.request.calls.reset(); + await delayForUpdatePeriod(); + + fakeNetEngine.expectRequest( + 'test:/video?_HLS_skip=YES', + shaka.net.NetworkingEngine.RequestType.MANIFEST); + }); + + it('skips older segments', async () => { const mediaWithSkippedSegments = [ '#EXTM3U\n', @@ -935,7 +985,7 @@ describe('HlsParser live', () => { it('skips older segments with discontinuity', async () => { const mediaWithDiscontinuity2 = [ '#EXTM3U\n', - '#EXT-X-PLAYLIST-TYPE:EVENT\n', + '#EXT-X-PLAYLIST-TYPE:LIVE\n', '#EXT-X-TARGETDURATION:5\n', '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', '#EXT-X-DISCONTINUITY-SEQUENCE:30\n',