6cde85b7 by David LaPalomento

Restart switching from the lowest bitrate if a segment times out

When there is a big enough drop in bandwidth that a segment request actually times out, restart the algorithm from the lowest bitrate playlist. Update tech disposal so that oustanding segment XHRs are aborted when the player is cleaned up.
1 parent 2a9a847b
...@@ -330,6 +330,20 @@ var ...@@ -330,6 +330,20 @@ var
330 }; 330 };
331 331
332 /** 332 /**
333 * Abort all outstanding work and cleanup.
334 */
335 player.hls.dispose = function() {
336 if (segmentXhr) {
337 segmentXhr.onreadystatechange = null;
338 segmentXhr.abort();
339 }
340 if (this.playlists) {
341 this.playlists.dispose();
342 }
343 videojs.Flash.prototype.dispose.call(this);
344 };
345
346 /**
333 * Determines whether there is enough video data currently in the buffer 347 * Determines whether there is enough video data currently in the buffer
334 * and downloads a new segment if the buffered time is less than the goal. 348 * and downloads a new segment if the buffered time is less than the goal.
335 * @param offset (optional) {number} the offset into the downloaded segment 349 * @param offset (optional) {number} the offset into the downloaded segment
...@@ -394,6 +408,12 @@ var ...@@ -394,6 +408,12 @@ var
394 segmentXhr = null; 408 segmentXhr = null;
395 409
396 if (error) { 410 if (error) {
411 // if a segment request times out, we may have better luck with another playlist
412 if (error === 'timeout') {
413 player.hls.bandwidth = 1;
414 return player.hls.playlists.media(player.hls.selectPlaylist());
415 }
416 // otherwise, try jumping ahead to the next segment
397 player.hls.error = { 417 player.hls.error = {
398 status: this.status, 418 status: this.status,
399 message: 'HLS segment request error at URL: ' + url, 419 message: 'HLS segment request error at URL: ' + url,
...@@ -607,13 +627,6 @@ videojs.Hls.prototype.duration = function() { ...@@ -607,13 +627,6 @@ videojs.Hls.prototype.duration = function() {
607 return 0; 627 return 0;
608 }; 628 };
609 629
610 videojs.Hls.prototype.dispose = function() {
611 if (this.playlists) {
612 this.playlists.dispose();
613 }
614 videojs.Flash.prototype.dispose.call(this);
615 };
616
617 videojs.Hls.isSupported = function() { 630 videojs.Hls.isSupported = function() {
618 return videojs.Flash.isSupported() && videojs.MediaSource; 631 return videojs.Flash.isSupported() && videojs.MediaSource;
619 }; 632 };
...@@ -665,10 +678,14 @@ xhr = videojs.Hls.xhr = function(url, callback) { ...@@ -665,10 +678,14 @@ xhr = videojs.Hls.xhr = function(url, callback) {
665 if (options.timeout) { 678 if (options.timeout) {
666 if (request.timeout === 0) { 679 if (request.timeout === 0) {
667 request.timeout = options.timeout; 680 request.timeout = options.timeout;
681 request.ontimeout = function() {
682 request.timedout = true;
683 };
668 } else { 684 } else {
669 // polyfill XHR2 by aborting after the timeout 685 // polyfill XHR2 by aborting after the timeout
670 abortTimeout = window.setTimeout(function() { 686 abortTimeout = window.setTimeout(function() {
671 if (request.readystate !== 4) { 687 if (request.readystate !== 4) {
688 request.timedout = true;
672 request.abort(); 689 request.abort();
673 } 690 }
674 }, options.timeout); 691 }, options.timeout);
...@@ -684,7 +701,12 @@ xhr = videojs.Hls.xhr = function(url, callback) { ...@@ -684,7 +701,12 @@ xhr = videojs.Hls.xhr = function(url, callback) {
684 // clear outstanding timeouts 701 // clear outstanding timeouts
685 window.clearTimeout(abortTimeout); 702 window.clearTimeout(abortTimeout);
686 703
687 // request error 704 // request timeout
705 if (request.timedout) {
706 return callback.call(this, 'timeout', url);
707 }
708
709 // request aborted or errored
688 if (this.status >= 400 || this.status === 0) { 710 if (this.status >= 400 || this.status === 0) {
689 return callback.call(this, true, url); 711 return callback.call(this, true, url);
690 } 712 }
......
...@@ -420,7 +420,6 @@ test('selects a playlist after segment downloads', function() { ...@@ -420,7 +420,6 @@ test('selects a playlist after segment downloads', function() {
420 player.trigger('timeupdate'); 420 player.trigger('timeupdate');
421 421
422 standardXHRResponse(requests[3]); 422 standardXHRResponse(requests[3]);
423 console.log(requests.map(function(i) { return i.url; }));
424 strictEqual(calls, 2, 'selects after additional segments'); 423 strictEqual(calls, 2, 'selects after additional segments');
425 }); 424 });
426 425
...@@ -1151,6 +1150,27 @@ test('clears the segment buffer on seek', function() { ...@@ -1151,6 +1150,27 @@ test('clears the segment buffer on seek', function() {
1151 strictEqual(aborts, 1, 'cleared the segment buffer on a seek'); 1150 strictEqual(aborts, 1, 'cleared the segment buffer on a seek');
1152 }); 1151 });
1153 1152
1153 test('resets the switching algorithm if a request times out', function() {
1154 player.src({
1155 src: 'master.m3u8',
1156 type: 'application/vnd.apple.mpegurl'
1157 });
1158 player.hls.mediaSource.trigger({
1159 type: 'sourceopen'
1160 });
1161 standardXHRResponse(requests.shift()); // master
1162 standardXHRResponse(requests.shift()); // media.m3u8
1163 // simulate a segment timeout
1164 requests[0].timedout = true;
1165 requests.shift().abort();
1166
1167 standardXHRResponse(requests.shift());
1168
1169 strictEqual(player.hls.playlists.media(),
1170 player.hls.playlists.master.playlists[1],
1171 'reset to the lowest bitrate playlist');
1172 });
1173
1154 test('disposes the playlist loader', function() { 1174 test('disposes the playlist loader', function() {
1155 var disposes = 0, player, loaderDispose; 1175 var disposes = 0, player, loaderDispose;
1156 player = createPlayer(); 1176 player = createPlayer();
......