f328f16d by David LaPalomento

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.
1 parent a3e64063
...@@ -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' +
......