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.
Showing
2 changed files
with
51 additions
and
9 deletions
... | @@ -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(); | ... | ... |
-
Please register or sign in to post a comment