Don't preload live videos
If play() was called before the media playlist has been downloaded, when the playlist finally was received we would still update media index to the live point to start buffering. We would have skipped the code that adjusted currentTime however, so playback would seem to begin at zero instead of seekable().end(0). That would cause media-timeline relative positions to be off by the live window length and things like cue points would fire 60 seconds (for example) after they should have. Now, don't preload live video at all and just seek to live immediately on play, updating the currentTime at that point.
Showing
2 changed files
with
47 additions
and
24 deletions
... | @@ -129,16 +129,6 @@ videojs.Hls.prototype.src = function(src) { | ... | @@ -129,16 +129,6 @@ videojs.Hls.prototype.src = function(src) { |
129 | }); | 129 | }); |
130 | } | 130 | } |
131 | 131 | ||
132 | // Start live playlists 30 seconds before the current time | ||
133 | // This is done using the old playlist because of a race condition | ||
134 | // where the playlist selected below may not be loaded quickly | ||
135 | // enough to have its segments available for review. When we receive | ||
136 | // a loadedplaylist event, we will call translateMediaIndex and | ||
137 | // maintain our position at the live point. | ||
138 | if (this.duration() === Infinity && this.mediaIndex === 0) { | ||
139 | this.mediaIndex = videojs.Hls.getMediaIndexForLive_(oldMediaPlaylist); | ||
140 | } | ||
141 | |||
142 | selectedPlaylist = this.selectPlaylist(); | 132 | selectedPlaylist = this.selectPlaylist(); |
143 | oldBitrate = oldMediaPlaylist.attributes && | 133 | oldBitrate = oldMediaPlaylist.attributes && |
144 | oldMediaPlaylist.attributes.BANDWIDTH || 0; | 134 | oldMediaPlaylist.attributes.BANDWIDTH || 0; |
... | @@ -235,15 +225,18 @@ videojs.Hls.prototype.handleSourceOpen = function() { | ... | @@ -235,15 +225,18 @@ videojs.Hls.prototype.handleSourceOpen = function() { |
235 | sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); | 225 | sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); |
236 | 226 | ||
237 | this.sourceBuffer = sourceBuffer; | 227 | this.sourceBuffer = sourceBuffer; |
238 | sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader()); | ||
239 | |||
240 | 228 | ||
241 | // if autoplay is enabled, begin playback. This is duplicative of | 229 | // if autoplay is enabled, begin playback. This is duplicative of |
242 | // code in video.js but is required because play() must be invoked | 230 | // code in video.js but is required because play() must be invoked |
243 | // *after* the media source has opened. | 231 | // *after* the media source has opened. |
232 | // NOTE: moving this invocation of play() after | ||
233 | // sourceBuffer.appendBuffer() below caused live streams with | ||
234 | // autoplay to stall | ||
244 | if (player.options().autoplay) { | 235 | if (player.options().autoplay) { |
245 | player.play(); | 236 | player.play(); |
246 | } | 237 | } |
238 | |||
239 | sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader()); | ||
247 | }; | 240 | }; |
248 | 241 | ||
249 | // register event listeners to transform in-band metadata events into | 242 | // register event listeners to transform in-band metadata events into |
... | @@ -320,13 +313,19 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { | ... | @@ -320,13 +313,19 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { |
320 | * ended. | 313 | * ended. |
321 | */ | 314 | */ |
322 | videojs.Hls.prototype.play = function() { | 315 | videojs.Hls.prototype.play = function() { |
316 | var media; | ||
323 | if (this.ended()) { | 317 | if (this.ended()) { |
324 | this.mediaIndex = 0; | 318 | this.mediaIndex = 0; |
325 | } | 319 | } |
326 | 320 | ||
327 | if (this.duration() === Infinity && this.playlists.media() && !this.player().hasClass('vjs-has-started')) { | 321 | // seek to the latest safe point in the media timeline when first |
328 | this.mediaIndex = videojs.Hls.getMediaIndexForLive_(this.playlists.media()); | 322 | // playing live streams |
329 | this.setCurrentTime(this.getCurrentTimeByMediaIndex_(this.playlists.media(), this.mediaIndex)); | 323 | if (this.duration() === Infinity && |
324 | this.playlists.media() && | ||
325 | !this.player().hasClass('vjs-has-started')) { | ||
326 | media = this.playlists.media(); | ||
327 | this.mediaIndex = videojs.Hls.getMediaIndexForLive_(media); | ||
328 | this.setCurrentTime(videojs.Hls.Playlist.seekable(media).end(0)); | ||
330 | } | 329 | } |
331 | 330 | ||
332 | // delegate back to the Flash implementation | 331 | // delegate back to the Flash implementation |
... | @@ -638,6 +637,14 @@ videojs.Hls.prototype.fillBuffer = function(offset) { | ... | @@ -638,6 +637,14 @@ videojs.Hls.prototype.fillBuffer = function(offset) { |
638 | return; | 637 | return; |
639 | } | 638 | } |
640 | 639 | ||
640 | // if this is a live video wait until playback has been requested to | ||
641 | // being buffering so we don't preload data that will never be | ||
642 | // played | ||
643 | if (!this.playlists.media().endList && | ||
644 | !this.player().hasClass('vjs-has-started')) { | ||
645 | return; | ||
646 | } | ||
647 | |||
641 | // if a playlist switch is in progress, wait for it to finish | 648 | // if a playlist switch is in progress, wait for it to finish |
642 | if (this.playlists.state === 'SWITCHING_MEDIA') { | 649 | if (this.playlists.state === 'SWITCHING_MEDIA') { |
643 | return; | 650 | return; | ... | ... |
... | @@ -1427,6 +1427,7 @@ test('translates ID3 PTS values across discontinuities', function() { | ... | @@ -1427,6 +1427,7 @@ test('translates ID3 PTS values across discontinuities', function() { |
1427 | }; | 1427 | }; |
1428 | 1428 | ||
1429 | // media playlist | 1429 | // media playlist |
1430 | player.trigger('play'); | ||
1430 | requests.shift().respond(200, null, | 1431 | requests.shift().respond(200, null, |
1431 | '#EXTM3U\n' + | 1432 | '#EXTM3U\n' + |
1432 | '#EXTINF:10,\n' + | 1433 | '#EXTINF:10,\n' + |
... | @@ -1627,6 +1628,7 @@ test('updates the media index when a playlist reloads', function() { | ... | @@ -1627,6 +1628,7 @@ test('updates the media index when a playlist reloads', function() { |
1627 | type: 'application/vnd.apple.mpegurl' | 1628 | type: 'application/vnd.apple.mpegurl' |
1628 | }); | 1629 | }); |
1629 | openMediaSource(player); | 1630 | openMediaSource(player); |
1631 | player.trigger('play'); | ||
1630 | 1632 | ||
1631 | requests[0].respond(200, null, | 1633 | requests[0].respond(200, null, |
1632 | '#EXTM3U\n' + | 1634 | '#EXTM3U\n' + |
... | @@ -1658,18 +1660,22 @@ test('updates the media index when a playlist reloads', function() { | ... | @@ -1658,18 +1660,22 @@ test('updates the media index when a playlist reloads', function() { |
1658 | strictEqual(player.hls.mediaIndex, 2, 'mediaIndex is updated after the reload'); | 1660 | strictEqual(player.hls.mediaIndex, 2, 'mediaIndex is updated after the reload'); |
1659 | }); | 1661 | }); |
1660 | 1662 | ||
1661 | test('live playlist starts 30s before live', function() { | 1663 | test('live playlist starts three target durations before live', function() { |
1664 | var mediaPlaylist; | ||
1662 | player.src({ | 1665 | player.src({ |
1663 | src: 'http://example.com/manifest/liveStart30sBefore.m3u8', | 1666 | src: 'http://example.com/manifest/liveStart30sBefore.m3u8', |
1664 | type: 'application/vnd.apple.mpegurl' | 1667 | type: 'application/vnd.apple.mpegurl' |
1665 | }); | 1668 | }); |
1666 | openMediaSource(player); | 1669 | openMediaSource(player); |
1670 | standardXHRResponse(requests.shift()); | ||
1667 | 1671 | ||
1668 | standardXHRResponse(requests[0]); | 1672 | equal(player.hls.mediaIndex, 0, 'waits for the first play to start buffering'); |
1673 | equal(requests.length, 0, 'no outstanding segment request'); | ||
1669 | 1674 | ||
1670 | player.hls.playlists.trigger('loadedmetadata'); | 1675 | player.play(); |
1671 | 1676 | mediaPlaylist = player.hls.playlists.media(); | |
1672 | strictEqual(player.hls.mediaIndex, 6, 'mediaIndex is updated after the reload'); | 1677 | equal(player.hls.mediaIndex, 6, 'mediaIndex is updated at play'); |
1678 | equal(player.currentTime(), videojs.Hls.Playlist.seekable(mediaPlaylist).end(0)); | ||
1673 | }); | 1679 | }); |
1674 | 1680 | ||
1675 | test('does not reset live currentTime if mediaIndex is one beyond the last available segment', function() { | 1681 | test('does not reset live currentTime if mediaIndex is one beyond the last available segment', function() { |
... | @@ -1703,7 +1709,9 @@ test('live playlist starts with correct currentTime value', function() { | ... | @@ -1703,7 +1709,9 @@ test('live playlist starts with correct currentTime value', function() { |
1703 | 1709 | ||
1704 | player.hls.play(); | 1710 | player.hls.play(); |
1705 | 1711 | ||
1706 | strictEqual(player.currentTime(), 70, 'currentTime is updated at playback'); | 1712 | strictEqual(player.currentTime(), |
1713 | videojs.Hls.Playlist.seekable(player.hls.playlists.media()).end(0), | ||
1714 | 'currentTime is updated at playback'); | ||
1707 | }); | 1715 | }); |
1708 | 1716 | ||
1709 | test('mediaIndex is zero before the first segment loads', function() { | 1717 | test('mediaIndex is zero before the first segment loads', function() { |
... | @@ -1798,6 +1806,7 @@ test('calls vjs_discontinuity() before appending bytes at a discontinuity', func | ... | @@ -1798,6 +1806,7 @@ test('calls vjs_discontinuity() before appending bytes at a discontinuity', func |
1798 | type: 'application/vnd.apple.mpegurl' | 1806 | type: 'application/vnd.apple.mpegurl' |
1799 | }); | 1807 | }); |
1800 | openMediaSource(player); | 1808 | openMediaSource(player); |
1809 | player.trigger('play'); | ||
1801 | player.currentTime = function() { return currentTime; }; | 1810 | player.currentTime = function() { return currentTime; }; |
1802 | player.buffered = function() { | 1811 | player.buffered = function() { |
1803 | return videojs.createTimeRange(0, bufferEnd); | 1812 | return videojs.createTimeRange(0, bufferEnd); |
... | @@ -2221,7 +2230,7 @@ test('keys are requested when an encrypted segment is loaded', function() { | ... | @@ -2221,7 +2230,7 @@ test('keys are requested when an encrypted segment is loaded', function() { |
2221 | type: 'application/vnd.apple.mpegurl' | 2230 | type: 'application/vnd.apple.mpegurl' |
2222 | }); | 2231 | }); |
2223 | openMediaSource(player); | 2232 | openMediaSource(player); |
2224 | 2233 | player.trigger('play'); | |
2225 | standardXHRResponse(requests.shift()); // playlist | 2234 | standardXHRResponse(requests.shift()); // playlist |
2226 | standardXHRResponse(requests.shift()); // first segment | 2235 | standardXHRResponse(requests.shift()); // first segment |
2227 | 2236 | ||
... | @@ -2314,7 +2323,6 @@ test('seeking should abort an outstanding key request and create a new one', fun | ... | @@ -2314,7 +2323,6 @@ test('seeking should abort an outstanding key request and create a new one', fun |
2314 | }); | 2323 | }); |
2315 | openMediaSource(player); | 2324 | openMediaSource(player); |
2316 | 2325 | ||
2317 | |||
2318 | requests.shift().respond(200, null, | 2326 | requests.shift().respond(200, null, |
2319 | '#EXTM3U\n' + | 2327 | '#EXTM3U\n' + |
2320 | '#EXT-X-TARGETDURATION:15\n' + | 2328 | '#EXT-X-TARGETDURATION:15\n' + |
... | @@ -2346,6 +2354,7 @@ test('retries key requests once upon failure', function() { | ... | @@ -2346,6 +2354,7 @@ test('retries key requests once upon failure', function() { |
2346 | type: 'application/vnd.apple.mpegurl' | 2354 | type: 'application/vnd.apple.mpegurl' |
2347 | }); | 2355 | }); |
2348 | openMediaSource(player); | 2356 | openMediaSource(player); |
2357 | player.trigger('play'); | ||
2349 | 2358 | ||
2350 | requests.shift().respond(200, null, | 2359 | requests.shift().respond(200, null, |
2351 | '#EXTM3U\n' + | 2360 | '#EXTM3U\n' + |
... | @@ -2381,6 +2390,7 @@ test('skip segments if key requests fail more than once', function() { | ... | @@ -2381,6 +2390,7 @@ test('skip segments if key requests fail more than once', function() { |
2381 | type: 'application/vnd.apple.mpegurl' | 2390 | type: 'application/vnd.apple.mpegurl' |
2382 | }); | 2391 | }); |
2383 | openMediaSource(player); | 2392 | openMediaSource(player); |
2393 | player.trigger('play'); | ||
2384 | 2394 | ||
2385 | requests.shift().respond(200, null, | 2395 | requests.shift().respond(200, null, |
2386 | '#EXTM3U\n' + | 2396 | '#EXTM3U\n' + |
... | @@ -2419,6 +2429,7 @@ test('the key is supplied to the decrypter in the correct format', function() { | ... | @@ -2419,6 +2429,7 @@ test('the key is supplied to the decrypter in the correct format', function() { |
2419 | type: 'application/vnd.apple.mpegurl' | 2429 | type: 'application/vnd.apple.mpegurl' |
2420 | }); | 2430 | }); |
2421 | openMediaSource(player); | 2431 | openMediaSource(player); |
2432 | player.trigger('play'); | ||
2422 | 2433 | ||
2423 | requests.pop().respond(200, null, | 2434 | requests.pop().respond(200, null, |
2424 | '#EXTM3U\n' + | 2435 | '#EXTM3U\n' + |
... | @@ -2453,6 +2464,7 @@ test('supplies the media sequence of current segment as the IV by default, if no | ... | @@ -2453,6 +2464,7 @@ test('supplies the media sequence of current segment as the IV by default, if no |
2453 | type: 'application/vnd.apple.mpegurl' | 2464 | type: 'application/vnd.apple.mpegurl' |
2454 | }); | 2465 | }); |
2455 | openMediaSource(player); | 2466 | openMediaSource(player); |
2467 | player.trigger('play'); | ||
2456 | 2468 | ||
2457 | requests.pop().respond(200, null, | 2469 | requests.pop().respond(200, null, |
2458 | '#EXTM3U\n' + | 2470 | '#EXTM3U\n' + |
... | @@ -2492,6 +2504,7 @@ test('switching playlists with an outstanding key request does not stall playbac | ... | @@ -2492,6 +2504,7 @@ test('switching playlists with an outstanding key request does not stall playbac |
2492 | type: 'application/vnd.apple.mpegurl' | 2504 | type: 'application/vnd.apple.mpegurl' |
2493 | }); | 2505 | }); |
2494 | openMediaSource(player); | 2506 | openMediaSource(player); |
2507 | player.trigger('play'); | ||
2495 | 2508 | ||
2496 | // master playlist | 2509 | // master playlist |
2497 | standardXHRResponse(requests.shift()); | 2510 | standardXHRResponse(requests.shift()); |
... | @@ -2532,7 +2545,8 @@ test('resolves relative key URLs against the playlist', function() { | ... | @@ -2532,7 +2545,8 @@ test('resolves relative key URLs against the playlist', function() { |
2532 | '#EXT-X-MEDIA-SEQUENCE:5\n' + | 2545 | '#EXT-X-MEDIA-SEQUENCE:5\n' + |
2533 | '#EXT-X-KEY:METHOD=AES-128,URI="key.php?r=52"\n' + | 2546 | '#EXT-X-KEY:METHOD=AES-128,URI="key.php?r=52"\n' + |
2534 | '#EXTINF:2.833,\n' + | 2547 | '#EXTINF:2.833,\n' + |
2535 | 'http://media.example.com/fileSequence52-A.ts\n'); | 2548 | 'http://media.example.com/fileSequence52-A.ts\n' + |
2549 | '#EXT-X-ENDLIST\n'); | ||
2536 | standardXHRResponse(requests.shift()); // segment | 2550 | standardXHRResponse(requests.shift()); // segment |
2537 | 2551 | ||
2538 | equal(requests[0].url, 'https://example.com/key.php?r=52', 'resolves the key URL'); | 2552 | equal(requests[0].url, 'https://example.com/key.php?r=52', 'resolves the key URL'); |
... | @@ -2552,6 +2566,7 @@ test('treats invalid keys as a key request failure', function() { | ... | @@ -2552,6 +2566,7 @@ test('treats invalid keys as a key request failure', function() { |
2552 | type: 'application/vnd.apple.mpegurl' | 2566 | type: 'application/vnd.apple.mpegurl' |
2553 | }); | 2567 | }); |
2554 | openMediaSource(player); | 2568 | openMediaSource(player); |
2569 | player.trigger('play'); | ||
2555 | requests.shift().respond(200, null, | 2570 | requests.shift().respond(200, null, |
2556 | '#EXTM3U\n' + | 2571 | '#EXTM3U\n' + |
2557 | '#EXT-X-MEDIA-SEQUENCE:5\n' + | 2572 | '#EXT-X-MEDIA-SEQUENCE:5\n' + |
... | @@ -2593,6 +2608,7 @@ test('live stream should not call endOfStream', function(){ | ... | @@ -2593,6 +2608,7 @@ test('live stream should not call endOfStream', function(){ |
2593 | type: 'application/vnd.apple.mpegurl' | 2608 | type: 'application/vnd.apple.mpegurl' |
2594 | }); | 2609 | }); |
2595 | openMediaSource(player); | 2610 | openMediaSource(player); |
2611 | player.trigger('play'); | ||
2596 | requests[0].respond(200, null, | 2612 | requests[0].respond(200, null, |
2597 | '#EXTM3U\n' + | 2613 | '#EXTM3U\n' + |
2598 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | 2614 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ... | ... |
-
Please register or sign in to post a comment