diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index bd38cb55f9..fa1818a2ab 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -185,6 +185,9 @@ shaka.hls.HlsParser = class { */ this.partialTargetDuration_ = 0; + /** @private {number} */ + this.presentationDelay_ = 0; + /** @private {number} */ this.lowLatencyPresentationDelay_ = 0; @@ -2621,6 +2624,9 @@ shaka.hls.HlsParser = class { const targetDuration = Number(targetDurationTag.value); const partialTargetDurationTag = shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF'); + // Get the server-recommended min distance from the live edge. + const serverControlTag = shaka.hls.Utils.getFirstTagWithName( + playlist.tags, 'EXT-X-SERVER-CONTROL'); // According to the HLS spec, updates should not happen more often than // once in targetDuration. It also requires us to only update the active // variant. We might implement that later, but for now every variant @@ -2634,9 +2640,6 @@ shaka.hls.HlsParser = class { partialTargetDurationTag.getRequiredAttrValue('PART-TARGET')); this.lastTargetDuration_ = Math.min( this.partialTargetDuration_, this.lastTargetDuration_); - // Get the server-recommended min distance from the live edge. - const serverControlTag = shaka.hls.Utils.getFirstTagWithName( - playlist.tags, 'EXT-X-SERVER-CONTROL'); // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode. this.lowLatencyPresentationDelay_ = serverControlTag ? Number( serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0; @@ -2656,6 +2659,10 @@ shaka.hls.HlsParser = class { } this.lastTargetDuration_ = Math.min( lastTargetDuration, this.lastTargetDuration_); + // Use 'HOLD-BACK' as the presentation delay for default if defined. + const holdBack = serverControlTag ? + serverControlTag.getAttribute('HOLD-BACK') : null; + this.presentationDelay_ = holdBack ? Number(holdBack.value) : 0; } // 2. Update the longest target duration if need be to use as a // presentation delay later. @@ -2687,6 +2694,8 @@ shaka.hls.HlsParser = class { presentationDelay = this.config_.defaultPresentationDelay; } else if (this.lowLatencyPresentationDelay_) { presentationDelay = this.lowLatencyPresentationDelay_; + } else if (this.presentationDelay_) { + presentationDelay = this.presentationDelay_; } else { const numberOfSegments = this.config_.hls.liveSegmentsDelay; presentationDelay = this.maxTargetDuration_ * numberOfSegments; diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js index 6e06d4109d..5c0d61a259 100644 --- a/test/hls/hls_live_unit.js +++ b/test/hls/hls_live_unit.js @@ -544,6 +544,24 @@ describe('HlsParser live', () => { expect(manifest.presentationTimeline.getDelay()).toBe(15); }); + it('sets presentation delay if defined', async () => { + const media = [ + '#EXTM3U\n', + '#EXT-X-SERVER-CONTROL:HOLD-BACK=2\n', + '#EXT-X-TARGETDURATION:5\n', + '#EXT-X-PART-INF:PART-TARGET=0.5\n', + '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n', + '#EXT-X-MEDIA-SEQUENCE:0\n', + '#EXTINF:2,\n', + 'main.mp4\n', + ].join(''); + + const manifest = await testInitialManifest(master, media); + // Presentation delay should be the value of 'HOLD-BACK' if not + // configured. + expect(manifest.presentationTimeline.getDelay()).toBe(2); + }); + it('sets presentation delay for low latency mode', async () => { const mediaWithLowLatency = [ '#EXTM3U\n',