Seek to a safe position when resuming live playlists
If the player has fallen out of the live window, seek back to the earliest safe position when playback resumes. If the player is paused too far ahead of the live point, seek back to the latest safe position when resuming.
Showing
2 changed files
with
60 additions
and
6 deletions
... | @@ -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 | ... | ... |
... | @@ -1686,6 +1686,51 @@ test('live playlist starts with correct currentTime value', function() { | ... | @@ -1686,6 +1686,51 @@ 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 | |||
1689 | test('mediaIndex is zero before the first segment loads', function() { | 1734 | test('mediaIndex is zero before the first segment loads', function() { |
1690 | window.manifests['first-seg-load'] = | 1735 | window.manifests['first-seg-load'] = |
1691 | '#EXTM3U\n' + | 1736 | '#EXTM3U\n' + | ... | ... |
-
Please register or sign in to post a comment