Merge pull request #299 from dmlap/autoplay-live
Don't preload live videos
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