diff --git a/lib/media/presentation_timeline.js b/lib/media/presentation_timeline.js index 155ecb6e82..ba3ea3ec54 100644 --- a/lib/media/presentation_timeline.js +++ b/lib/media/presentation_timeline.js @@ -354,14 +354,23 @@ shaka.media.PresentationTimeline = class { * starting after this time should be assumed to be unavailable. * * @return {number} The current segment availability end time, in seconds, - * relative to the start of the presentation. Always returns the - * presentation's duration for video-on-demand. + * relative to the start of the presentation. For VOD, the availability + * end time is the content's duration. If the Player's playRangeEnd + * configuration is used, this can override the duration. * @export */ getSegmentAvailabilityEnd() { if (!this.isLive() && !this.isInProgress()) { // It's a static manifest (can also be a dynamic->static conversion) - return this.maxSegmentEndTime_ || this.duration_; + if (this.maxSegmentEndTime_) { + // If we know segment times, use the min of that and duration. + // Note that the playRangeEnd configuration changes this.duration_. + // See https://github.com/shaka-project/shaka-player/issues/4026 + return Math.min(this.maxSegmentEndTime_, this.duration_); + } else { + // If we don't have segment times, use duration. + return this.duration_; + } } // Can be either live or "in-progress recording" (live with known duration) return Math.min(this.getLiveEdge_() + this.availabilityTimeOffset_, diff --git a/test/player_integration.js b/test/player_integration.js index 36b421a2da..33254fd77b 100644 --- a/test/player_integration.js +++ b/test/player_integration.js @@ -801,7 +801,7 @@ describe('Player', () => { // Check that the final seek range is as expected. const seekRange = player.seekRange(); - expect(seekRange.end).toBe(14); + expect(seekRange.end).toBeCloseTo(14); }); }); diff --git a/test/player_unit.js b/test/player_unit.js index 9a5164e532..968205a897 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -687,9 +687,9 @@ describe('Player', () => { }); it('configures play and seek range for VOD', async () => { - player.configure({playRangeStart: 5, playRangeEnd: 10}); const timeline = new shaka.media.PresentationTimeline(300, 0); timeline.setStatic(true); + timeline.setDuration(300); manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.presentationTimeline = timeline; manifest.addVariant(0, (variant) => { @@ -697,7 +697,46 @@ describe('Player', () => { }); }); goog.asserts.assert(manifest, 'manifest must be non-null'); + + player.configure({playRangeStart: 5, playRangeEnd: 10}); + await player.load(fakeManifestUri, 0, fakeMimeType); + + const seekRange = player.seekRange(); + expect(seekRange.start).toBe(5); + expect(seekRange.end).toBe(10); + }); + + // Test for https://github.com/shaka-project/shaka-player/issues/4026 + it('configures play and seek range with notifySegments', async () => { + const timeline = new shaka.media.PresentationTimeline(300, 0); + timeline.setStatic(true); + // This duration is used by useSegmentTemplate below to decide how many + // references to generate. + timeline.setDuration(300); + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.presentationTimeline = timeline; + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.useSegmentTemplate( + '$Number$.mp4', /* segmentDuration= */ 10); + }); + }); + }); + goog.asserts.assert(manifest, 'manifest must be non-null'); + + // Explicitly notify the timeline of the segment references. + const videoStream = manifest.variants[0].video; + await videoStream.createSegmentIndex(); + goog.asserts.assert(videoStream.segmentIndex, + 'SegmentIndex must be non-null'); + const references = Array.from(videoStream.segmentIndex); + goog.asserts.assert(references.length != 0, + 'Must have references for this test!'); + timeline.notifySegments(references); + + player.configure({playRangeStart: 5, playRangeEnd: 10}); await player.load(fakeManifestUri, 0, fakeMimeType); + const seekRange = player.seekRange(); expect(seekRange.start).toBe(5); expect(seekRange.end).toBe(10); diff --git a/test/test/util/manifest_generator.js b/test/test/util/manifest_generator.js index fbff247fe4..4906842ac4 100644 --- a/test/test/util/manifest_generator.js +++ b/test/test/util/manifest_generator.js @@ -608,9 +608,16 @@ shaka.test.ManifestGenerator.Stream = class { segmentDuration, 'Must pass a non-zero segment duration'); const shaka_ = this.manifest_.shaka_; - const totalDuration = this.manifest_.presentationTimeline.getDuration(); + let totalDuration = this.manifest_.presentationTimeline.getDuration(); goog.asserts.assert( isFinite(totalDuration), 'Must specify a manifest duration'); + if (!isFinite(totalDuration)) { + // Without this, a mistake of an infinite duration would result in an + // infinite loop and a crash, which would in turn prevent you from seeing + // the above assertion fail. + totalDuration = 0; + } + const segmentCount = totalDuration / segmentDuration; const references = [];