6dbef597 by David LaPalomento

Clamp seeks to the seekable range. Closes #327

2 parents 0227bf64 1a0671b2
...@@ -3,6 +3,7 @@ CHANGELOG ...@@ -3,6 +3,7 @@ CHANGELOG
3 3
4 ## HEAD (Unreleased) 4 ## HEAD (Unreleased)
5 * @dmlap improved video duration calculation. ([view](https://github.com/videojs/videojs-contrib-hls/pull/321)) 5 * @dmlap improved video duration calculation. ([view](https://github.com/videojs/videojs-contrib-hls/pull/321))
6 * Clamp seeks to the seekable range ([view](https://github.com/videojs/videojs-contrib-hls/pull/327))
6 7
7 -------------------- 8 --------------------
8 9
......
...@@ -321,12 +321,21 @@ videojs.Hls.prototype.play = function() { ...@@ -321,12 +321,21 @@ videojs.Hls.prototype.play = function() {
321 this.mediaIndex = 0; 321 this.mediaIndex = 0;
322 } 322 }
323 323
324 // seek to the latest safe point in the media timeline when first 324 // we may need to seek to begin playing safely for live playlists
325 // playing live streams 325 if (this.duration() === Infinity) {
326 if (this.duration() === Infinity && 326
327 this.playlists.media() && 327 // if this is the first time we're playing the stream or we're
328 !this.player().hasClass('vjs-has-started')) { 328 // ahead of the latest safe playback position, seek to the live
329 this.setCurrentTime(this.seekable().end(0)); 329 // point
330 if (!this.player().hasClass('vjs-has-started') ||
331 this.currentTime() > this.seekable().end(0)) {
332 this.setCurrentTime(this.seekable().end(0));
333
334 } else if (this.currentTime() < this.seekable().start(0)) {
335 // if the viewer has paused and we fell out of the live window,
336 // seek forward to the earliest available position
337 this.setCurrentTime(this.seekable().start(0));
338 }
330 } 339 }
331 340
332 // delegate back to the Flash implementation 341 // delegate back to the Flash implementation
...@@ -356,6 +365,13 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { ...@@ -356,6 +365,13 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
356 return 0; 365 return 0;
357 } 366 }
358 367
368 // clamp seeks to the available seekable time range
369 if (currentTime < this.seekable().start(0)) {
370 currentTime = this.seekable().start(0);
371 } else if (currentTime > this.seekable().end(0)) {
372 currentTime = this.seekable().end(0);
373 }
374
359 // save the seek target so currentTime can report it correctly 375 // save the seek target so currentTime can report it correctly
360 // while the seek is pending 376 // while the seek is pending
361 this.lastSeekedTime_ = currentTime; 377 this.lastSeekedTime_ = currentTime;
......
...@@ -1686,6 +1686,87 @@ test('live playlist starts with correct currentTime value', function() { ...@@ -1686,6 +1686,87 @@ test('live playlist starts with correct currentTime value', function() {
1686 'currentTime is updated at playback'); 1686 'currentTime is updated at playback');
1687 }); 1687 });
1688 1688
1689 test('resets the time to a seekable position when resuming a live stream ' +
1690 'after a long break', function() {
1691 var seekTarget;
1692 player.src({
1693 src: 'live0.m3u8',
1694 type: 'application/vnd.apple.mpegurl'
1695 });
1696 openMediaSource(player);
1697 requests.shift().respond(200, null,
1698 '#EXTM3U\n' +
1699 '#EXT-X-MEDIA-SEQUENCE:16\n' +
1700 '#EXTINF:10,\n' +
1701 '16.ts\n');
1702 // mock out the player to simulate a live stream that has been
1703 // playing for awhile
1704 player.addClass('vjs-has-started');
1705 player.hls.seekable = function() {
1706 return {
1707 start: function() {
1708 return 160;
1709 },
1710 end: function() {
1711 return 170;
1712 }
1713 };
1714 };
1715 player.hls.currentTime = function() {
1716 return 0;
1717 };
1718 player.hls.setCurrentTime = function(time) {
1719 if (time !== undefined) {
1720 seekTarget = time;
1721 }
1722 };
1723
1724 player.play();
1725 equal(seekTarget, player.seekable().start(0), 'seeked to the start of seekable');
1726
1727 player.hls.currentTime = function() {
1728 return 180;
1729 };
1730 player.play();
1731 equal(seekTarget, player.seekable().end(0), 'seeked to the end of seekable');
1732 });
1733
1734 test('clamps seeks to the seekable window', function() {
1735 var seekTarget;
1736 player.src({
1737 src: 'live0.m3u8',
1738 type: 'application/vnd.apple.mpegurl'
1739 });
1740 openMediaSource(player);
1741 requests.shift().respond(200, null,
1742 '#EXTM3U\n' +
1743 '#EXT-X-MEDIA-SEQUENCE:16\n' +
1744 '#EXTINF:10,\n' +
1745 '16.ts\n');
1746 // mock out a seekable window
1747 player.hls.seekable = function() {
1748 return {
1749 start: function() {
1750 return 160;
1751 },
1752 end: function() {
1753 return 170;
1754 }
1755 };
1756 };
1757 player.hls.fillBuffer = function(time) {
1758 if (time !== undefined) {
1759 seekTarget = time;
1760 }
1761 };
1762
1763 player.currentTime(180);
1764 equal(seekTarget * 0.001, player.seekable().end(0), 'forward seeks are clamped');
1765
1766 player.currentTime(45);
1767 equal(seekTarget * 0.001, player.seekable().start(0), 'backward seeks are clamped');
1768 });
1769
1689 test('mediaIndex is zero before the first segment loads', function() { 1770 test('mediaIndex is zero before the first segment loads', function() {
1690 window.manifests['first-seg-load'] = 1771 window.manifests['first-seg-load'] =
1691 '#EXTM3U\n' + 1772 '#EXTM3U\n' +
......