From ecd27fbf45b5ddec9a05804386977fbbc7772f8d Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Fri, 20 Jul 2018 11:30:05 -0700 Subject: [PATCH] Move availabilityWindowOverride to manifest parsers This reverts the implementation to an earlier draft state. During review of the first version, I recommended moving the implementation to Player so that it would be independent of the manifest parsers. That was bad advice on my part, because this overlooked updates made by the parsers when live manifests are updated later. Closes #1177 Closes #1307 Change-Id: I4611e00824dead83c6467da04a2a11afa892ace7 --- externs/shaka/player.js | 4 ++- lib/dash/dash_parser.js | 21 +++++++++--- lib/hls/hls_parser.js | 7 +++- lib/player.js | 5 --- test/dash/dash_parser_live_unit.js | 49 +++++++++++++++++++++++++++ test/hls/hls_live_unit.js | 53 ++++++++++++++++++++++++++++++ test/player_unit.js | 39 ---------------------- 7 files changed, 127 insertions(+), 51 deletions(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index b5cfde0054..b0e9516fc7 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -537,7 +537,9 @@ shaka.extern.DashManifestConfiguration; * Retry parameters for manifest requests. * @property (number) availabilityWindowOverride * A number, in seconds, that overrides the availability window in the - * manifest, or NaN if the default value should be used. + * manifest, or NaN if the default value should be used. This is enforced by + * the manifest parser, so custom manifest parsers should take care to honor + * this parameter. * @property {shaka.extern.DashManifestConfiguration} dash * Advanced parameters used by the DASH manifest parser. * diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 3a359da58f..676c74a6ec 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -550,10 +550,23 @@ shaka.dash.DashParser.prototype.processManifest_ = // Ignore duration calculated from Period lengths if this is dynamic. presentationTimeline.setDuration(duration || Infinity); } + + let isLive = presentationTimeline.isLive(); + + // If it's live, we check for an override. + if (isLive && !isNaN(this.config_.availabilityWindowOverride)) { + segmentAvailabilityDuration = this.config_.availabilityWindowOverride; + } + + // If it's null, that means segments are always available. This is always the + // case for VOD, and sometimes the case for live. + if (segmentAvailabilityDuration == null) { + segmentAvailabilityDuration = Infinity; + } + presentationTimeline.setSegmentAvailabilityDuration( - segmentAvailabilityDuration != null ? - segmentAvailabilityDuration : - Infinity); + segmentAvailabilityDuration); + // Use @maxSegmentDuration to override smaller, derived values. presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1); if (goog.DEBUG) presentationTimeline.assertIsValid(); @@ -567,8 +580,6 @@ shaka.dash.DashParser.prototype.processManifest_ = // the clock offset. let timingElements = XmlUtils.findChildren(mpd, 'UTCTiming'); - let isLive = presentationTimeline.isLive(); - return this.parseUtcTiming_( baseUris, timingElements, isLive).then(function(offset) { // Detect calls to stop(). diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index f9cbfcee36..d69c58dced 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -351,9 +351,14 @@ shaka.hls.HlsParser.prototype.parseManifest_ = function(data, uri) { // will be able to buffer ahead three segments, but the seek window will // be zero-sized. const PresentationType = shaka.hls.HlsParser.PresentationType_; + if (this.presentationType_ == PresentationType.LIVE) { + let segmentAvailabilityDuration = threeSegmentDurations; + if (!isNaN(this.config_.availabilityWindowOverride)) { + segmentAvailabilityDuration = this.config_.availabilityWindowOverride; + } this.presentationTimeline_.setSegmentAvailabilityDuration( - threeSegmentDurations); + segmentAvailabilityDuration); } let rolloverSeconds = diff --git a/lib/player.js b/lib/player.js index 2032df4e8e..c86c0b4f1a 100644 --- a/lib/player.js +++ b/lib/player.js @@ -733,11 +733,6 @@ shaka.Player.prototype.load = async function( if (cancelValue) throw cancelValue; - if (!isNaN(this.config_.manifest.availabilityWindowOverride)) { - this.manifest_.presentationTimeline.setSegmentAvailabilityDuration( - this.config_.manifest.availabilityWindowOverride); - } - this.filterManifestForAVVariants_(); this.drmEngine_ = this.createDrmEngine(); diff --git a/test/dash/dash_parser_live_unit.js b/test/dash/dash_parser_live_unit.js index cf0271e41e..b59b7c0872 100644 --- a/test/dash/dash_parser_live_unit.js +++ b/test/dash/dash_parser_live_unit.js @@ -678,6 +678,55 @@ describe('DashParser Live', function() { PromiseMock.flush(); }); + describe('availabilityWindowOverride', function() { + it('overrides @timeShiftBufferDepth', function(done) { + let manifest = [ + '', + ' ', + ' ', + ' ', + ' http://example.com', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + fakeNetEngine.setResponseMapAsText({'dummy://foo': manifest}); + + parser.configure({ + retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(), + availabilityWindowOverride: 4 * 60, + dash: { + clockSyncUri: '', + customScheme: function(node) { return null; }, + ignoreDrmInfo: false, + xlinkFailGracefully: false, + defaultPresentationDelay: 10, + }, + }); + + Date.now = function() { return 600000; /* 10 minutes */ }; + parser.start('dummy://foo', playerInterface) + .then(function(manifest) { + expect(manifest).toBeTruthy(); + let timeline = manifest.presentationTimeline; + expect(timeline).toBeTruthy(); + + // The parser was configured to have a manifest availability window + // of 4 minutes. + let end = timeline.getSegmentAvailabilityEnd(); + let start = timeline.getSegmentAvailabilityStart(); + expect(end - start).toEqual(4 * 60); + }).catch(fail).then(done); + PromiseMock.flush(); + }); + }); + describe('maxSegmentDuration', function() { it('uses @maxSegmentDuration', function(done) { let manifest = [ diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js index c88251b5c3..8d2e4276c6 100644 --- a/test/hls/hls_live_unit.js +++ b/test/hls/hls_live_unit.js @@ -431,6 +431,17 @@ describe('HlsParser live', function() { 'test:/main2.mp4\n', ].join(''); + let mediaWithManySegments = [ + '#EXTM3U\n', + '#EXT-X-TARGETDURATION:5\n', + '#EXT-X-MAP:URI="test:/init.mp4",BYTERANGE="616@0"\n', + '#EXT-X-MEDIA-SEQUENCE:0\n', + ].join(''); + for (let i = 0; i < 1000; ++i) { + mediaWithManySegments += '#EXTINF:2,\n'; + mediaWithManySegments += 'test:/main.mp4\n'; + } + it('starts presentation as VOD when ENDLIST is present', function(done) { fakeNetEngine.setResponseMap({ 'test:/master': toUTF8(master), @@ -455,6 +466,48 @@ describe('HlsParser live', function() { parser.start('test:/master', playerInterface).catch(fail).then(done); }); + describe('availabilityWindowOverride', function() { + async function testWindowOverride(expectedWindow) { + fakeNetEngine.setResponseMap({ + 'test:/master': toUTF8(master), + 'test:/video': toUTF8(mediaWithManySegments), + 'test:/init.mp4': initSegmentData, + 'test:/main.mp4': segmentData, + }); + + let manifest = await parser.start('test:/master', playerInterface); + expect(manifest).toBeTruthy(); + let timeline = manifest.presentationTimeline; + expect(timeline).toBeTruthy(); + + const start = timeline.getSegmentAvailabilityStart(); + const end = timeline.getSegmentAvailabilityEnd(); + expect(end - start).toEqual(expectedWindow); + } + + it('does not affect seek range if unset', async () => { + // 15 seconds is three segment durations. + await testWindowOverride(15); + }); + + it('overrides default seek range if set', async () => { + parser.configure({ + retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(), + availabilityWindowOverride: 240, + dash: { + customScheme: function(node) { return null; }, + clockSyncUri: '', + ignoreDrmInfo: false, + xlinkFailGracefully: false, + defaultPresentationDelay: 10, + }, + }); + + // 240 is the availabilityWindowOverride setting. + await testWindowOverride(240); + }); + }); + it('offsets VTT text with rolled over TS timestamps', function(done) { const masterWithVtt = [ '#EXTM3U\n', diff --git a/test/player_unit.js b/test/player_unit.js index 37da58feba..95acebbf97 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -3135,45 +3135,6 @@ describe('Player', function() { }); }); - describe('live', function() { - function availabilityWindowTest(expectedAvailabilityWindow) { - // Create a live timeline and manifest. - let timeline = new shaka.media.PresentationTimeline(300, 0); - timeline.setStatic(false); - timeline.setSegmentAvailabilityDuration(100); - - manifest = new shaka.test.ManifestGenerator() - .setTimeline(timeline) - .addPeriod(0) - .addVariant(0) - .addVideo(1) - .build(); - - let parser = new shaka.test.FakeManifestParser(manifest); - let parserFactory = function() { return parser; }; - - return player.load('', /* startTime */ 0, parserFactory).then(() => { - // Availability window can be determined by finding the time difference - // between the avalability end and start. Thus, we can compare the - // expected and actual window. - let end = timeline.getSegmentAvailabilityEnd(); - expect(end - timeline.getSegmentAvailabilityStart()) - .toBeCloseTo(expectedAvailabilityWindow, 1); - }); - } - - it('uses normal availability window when not overridden', async () => { - // The availability start should be what was in the timeline (100). - await availabilityWindowTest(100); - }); - - it('honors availabilityWindowOverride', async () => { - player.configure({manifest: {availabilityWindowOverride: 200}}); - // The availability start should be what was configured (200). - await availabilityWindowTest(200); - }); - }); - describe('language methods', function() { let videoOnlyManifest; let parserFactory = function() {