diff --git a/lib/media/presentation_timeline.js b/lib/media/presentation_timeline.js index 155ecb6e827..309e4ea2f7c 100644 --- a/lib/media/presentation_timeline.js +++ b/lib/media/presentation_timeline.js @@ -361,6 +361,13 @@ shaka.media.PresentationTimeline = class { getSegmentAvailabilityEnd() { if (!this.isLive() && !this.isInProgress()) { // It's a static manifest (can also be a dynamic->static conversion) + if (this.maxSegmentEndTime_ && this.duration_) { + // If we have both, use the min of the two. + // 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_); + } + // If we don't have both, use whichever we do have. return this.maxSegmentEndTime_ || this.duration_; } // Can be either live or "in-progress recording" (live with known duration) diff --git a/test/player_unit.js b/test/player_unit.js index 9a5164e532e..df24ab49ef1 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -690,6 +690,7 @@ describe('Player', () => { 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) => { @@ -703,6 +704,39 @@ describe('Player', () => { 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 () => { + 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) => { + 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); + + await player.load(fakeManifestUri, 0, fakeMimeType); + const seekRange = player.seekRange(); + expect(seekRange.start).toBe(5); + expect(seekRange.end).toBe(10); + }); + it('configures play and seek range after playback starts', async () => { const timeline = new shaka.media.PresentationTimeline(300, 0); timeline.setStatic(true); diff --git a/test/test/util/manifest_generator.js b/test/test/util/manifest_generator.js index fbff247fe45..4906842ac43 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 = [];