6dbef597 by David LaPalomento

Clamp seeks to the seekable range. Closes #327

2 parents 0227bf64 1a0671b2
......@@ -3,6 +3,7 @@ CHANGELOG
## HEAD (Unreleased)
* @dmlap improved video duration calculation. ([view](https://github.com/videojs/videojs-contrib-hls/pull/321))
* Clamp seeks to the seekable range ([view](https://github.com/videojs/videojs-contrib-hls/pull/327))
--------------------
......
......@@ -321,12 +321,21 @@ videojs.Hls.prototype.play = function() {
this.mediaIndex = 0;
}
// seek to the latest safe point in the media timeline when first
// playing live streams
if (this.duration() === Infinity &&
this.playlists.media() &&
!this.player().hasClass('vjs-has-started')) {
this.setCurrentTime(this.seekable().end(0));
// we may need to seek to begin playing safely for live playlists
if (this.duration() === Infinity) {
// if this is the first time we're playing the stream or we're
// ahead of the latest safe playback position, seek to the live
// point
if (!this.player().hasClass('vjs-has-started') ||
this.currentTime() > this.seekable().end(0)) {
this.setCurrentTime(this.seekable().end(0));
} else if (this.currentTime() < this.seekable().start(0)) {
// if the viewer has paused and we fell out of the live window,
// seek forward to the earliest available position
this.setCurrentTime(this.seekable().start(0));
}
}
// delegate back to the Flash implementation
......@@ -356,6 +365,13 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
return 0;
}
// clamp seeks to the available seekable time range
if (currentTime < this.seekable().start(0)) {
currentTime = this.seekable().start(0);
} else if (currentTime > this.seekable().end(0)) {
currentTime = this.seekable().end(0);
}
// save the seek target so currentTime can report it correctly
// while the seek is pending
this.lastSeekedTime_ = currentTime;
......
......@@ -1686,6 +1686,87 @@ test('live playlist starts with correct currentTime value', function() {
'currentTime is updated at playback');
});
test('resets the time to a seekable position when resuming a live stream ' +
'after a long break', function() {
var seekTarget;
player.src({
src: 'live0.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:16\n' +
'#EXTINF:10,\n' +
'16.ts\n');
// mock out the player to simulate a live stream that has been
// playing for awhile
player.addClass('vjs-has-started');
player.hls.seekable = function() {
return {
start: function() {
return 160;
},
end: function() {
return 170;
}
};
};
player.hls.currentTime = function() {
return 0;
};
player.hls.setCurrentTime = function(time) {
if (time !== undefined) {
seekTarget = time;
}
};
player.play();
equal(seekTarget, player.seekable().start(0), 'seeked to the start of seekable');
player.hls.currentTime = function() {
return 180;
};
player.play();
equal(seekTarget, player.seekable().end(0), 'seeked to the end of seekable');
});
test('clamps seeks to the seekable window', function() {
var seekTarget;
player.src({
src: 'live0.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:16\n' +
'#EXTINF:10,\n' +
'16.ts\n');
// mock out a seekable window
player.hls.seekable = function() {
return {
start: function() {
return 160;
},
end: function() {
return 170;
}
};
};
player.hls.fillBuffer = function(time) {
if (time !== undefined) {
seekTarget = time;
}
};
player.currentTime(180);
equal(seekTarget * 0.001, player.seekable().end(0), 'forward seeks are clamped');
player.currentTime(45);
equal(seekTarget * 0.001, player.seekable().start(0), 'backward seeks are clamped');
});
test('mediaIndex is zero before the first segment loads', function() {
window.manifests['first-seg-load'] =
'#EXTM3U\n' +
......