From 7f7e8cc171172143b20104f2af1c77cefa3ccfa9 Mon Sep 17 00:00:00 2001 From: Theodore Abshire Date: Tue, 25 Jul 2017 09:45:45 -0700 Subject: [PATCH] Fixes livestreams with no seek range. When the availability window of a live stream is very narrow, in at least some cases, the playhead can end up starting before the availability window. In that case, the stream will fail to load unless gap jumping is set up to jump long gaps. This makes the playhead jump ahead if it falls behind the availability window, to avoid that situation (and others that might have the same effect.) Closes #916 Change-Id: I87f8c70ba6053d3524a1546e57d55cb6528cc683 --- lib/media/playhead.js | 13 +++++++++++++ test/media/playhead_unit.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) 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;