diff --git a/lib/media/playhead.js b/lib/media/playhead.js index 0423df9729..7cc5342af0 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -342,6 +342,19 @@ shaka.media.Playhead.prototype.onPollGapJump_ = function() { var currentTime = this.video_.currentTime; var buffered = this.video_.buffered; + // If seeking is not possible, clamp the playhead manually here. + var timeline = this.manifest_.presentationTimeline; + var availabilityStart = timeline.getSegmentAvailabilityStart(); + if (currentTime < availabilityStart) { + // The availability window has moved past the playhead. + // Move ahead to catch up. + var targetTime = this.reposition_(currentTime); + shaka.log.info('Jumping forward ' + (targetTime - currentTime) + + ' seconds to catch up with the availability window.'); + this.movePlayhead_(currentTime, targetTime); + return; + } + var gapIndex = shaka.media.TimeRangesUtils.getGapIndex(buffered, currentTime); // The current time is unbuffered or is too far from a gap. diff --git a/test/media/playhead_unit.js b/test/media/playhead_unit.js index be2a2845cb..1469857529 100644 --- a/test/media/playhead_unit.js +++ b/test/media/playhead_unit.js @@ -393,6 +393,35 @@ describe('Playhead', function() { expect(onSeek).toHaveBeenCalled(); }); + it('handles live manifests with no seek range', function() { + video.buffered = createFakeBuffered([{start: 1000, end: 1030}]); + video.readyState = HTMLMediaElement.HAVE_METADATA; + + timeline.isLive.and.returnValue(true); + timeline.getSegmentAvailabilityStart.and.returnValue(1000); + timeline.getSegmentAvailabilityEnd.and.returnValue(1000); + timeline.getSegmentAvailabilityDuration.and.returnValue(1000); + + playhead = new shaka.media.Playhead( + video, + manifest, + config, + 5 /* startTime */, + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); + expect(video.currentTime).toBe(1000); + video.on['seeking'](); + + // The availability window slips ahead. + timeline.getSegmentAvailabilityStart.and.returnValue(1030); + timeline.getSegmentAvailabilityEnd.and.returnValue(1030); + video.on['waiting'](); + // We expect this to move to 15 seconds ahead of the start of the + // availability window, due to the rebuffering goal (10s) and the 5s + // for the Chromecast. + expect(video.currentTime).toBe(1045); + }); + describe('clamps playhead after resuming', function() { beforeEach(function() { video.readyState = HTMLMediaElement.HAVE_METADATA;