Skip to content

Commit

Permalink
handle xhr timeout and i/o errors
Browse files Browse the repository at this point in the history
related to dailymotion#2
  • Loading branch information
mangui committed May 22, 2015
1 parent 2db7b0e commit 522ad45
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 46 deletions.
37 changes: 13 additions & 24 deletions src/loader/fragment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,46 @@

import Event from '../events';
import observer from '../observer';
import {logger} from '../utils/logger';
import Xhr from '../utils/xhr';

class FragmentLoader {

constructor() {
}

destroy() {
this.abort();
this.xhr = null;
if(this.xhr) {
this.xhr.destroy();
this.xhr = null;
}
}

abort() {
if(this.xhr &&this.xhr.readyState !== 4) {
if(this.xhr) {
this.xhr.abort();
}
}

load(frag,levelId) {
load(frag,levelId, timeout = 60000, maxAttempts=3) {
this.frag = frag;
this.levelId = levelId;
this.trequest = new Date();
this.tfirst = null;
var xhr = this.xhr = new XMLHttpRequest();
xhr.onload= this.loadsuccess.bind(this);
xhr.onerror = this.loaderror.bind(this);
xhr.onprogress = this.loadprogress.bind(this);
xhr.open('GET', frag.url , true);
xhr.responseType = 'arraybuffer';
xhr.send();
observer.trigger(Event.FRAG_LOADING, { frag : frag});
this.xhr = new Xhr();
this.xhr.load(frag.url,'arraybuffer',this.loadsuccess.bind(this), this.loaderror.bind(this), timeout, maxAttempts);
}

loadsuccess(event) {

loadsuccess(event, stats) {
var payload = event.currentTarget.response;
stats.length = payload.byteLength;
observer.trigger(Event.FRAG_LOADED,
{ payload : payload,
frag : this.frag ,
stats : {trequest : this.trequest, tfirst : this.tfirst, tload : new Date(), length :payload.byteLength }});
stats : stats});
}

loaderror(event) {
logger.log('error loading ' + this.frag.url);
observer.trigger(Event.LOAD_ERROR, { url : this.frag.url, event:event});
}

loadprogress() {
if(this.tfirst === null) {
this.tfirst = new Date();
}
}
}

export default FragmentLoader;
34 changes: 12 additions & 22 deletions src/loader/playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import Event from '../events';
import observer from '../observer';
import Xhr from '../utils/xhr';
//import {logger} from '../utils/logger';

class PlaylistLoader {
Expand All @@ -14,23 +15,18 @@ import observer from '../observer';
}

destroy() {
if(this.xhr &&this.xhr.readyState !== 4) {
this.xhr.abort();
if(this.xhr) {
this.xhr.destroy();
this.xhr = null;
}
this.url = this.id = null;
}

load(url,requestId) {
load(url,requestId, timeout=10000, maxAttempts=3) {
this.url = url;
this.id = requestId;
this.stats = { trequest : new Date()};
var xhr = this.xhr = new XMLHttpRequest();
xhr.onload= this.loadsuccess.bind(this);
xhr.onerror = this.loaderror.bind(this);
xhr.onprogress = this.loadprogress.bind(this);
xhr.open('GET', url, true);
xhr.send();
this.xhr = new Xhr();
this.xhr.load(url,'',this.loadsuccess.bind(this), this.loaderror.bind(this), timeout, maxAttempts);
}

resolve(url, baseUrl) {
Expand Down Expand Up @@ -134,15 +130,15 @@ import observer from '../observer';
return level;
}

loadsuccess(event) {
loadsuccess(event, stats) {
var string = event.currentTarget.responseText, url = event.currentTarget.responseURL, id = this.id,levels;
// responseURL not supported on some browsers (it is used to detect URL redirection)
if(url === undefined) {
// fallback to initial URL
url = this.url;
}
this.stats.tload = new Date();
this.stats.mtime = new Date(this.xhr.getResponseHeader('Last-Modified'));
stats.tload = new Date();
stats.mtime = new Date(event.currentTarget.getResponseHeader('Last-Modified'));

if(string.indexOf('#EXTM3U') === 0) {
if (string.indexOf('#EXTINF:') > 0) {
Expand All @@ -153,12 +149,12 @@ import observer from '../observer';
observer.trigger(Event.MANIFEST_LOADED,
{ levels : [{url : url}],
url : url,
stats : this.stats});
stats : stats});
} else {
observer.trigger(Event.LEVEL_LOADED,
{ details : this.parseLevelPlaylist(string,url,id),
levelId : id,
stats : this.stats});
stats : stats});
}
} else {
levels = this.parseMasterPlaylist(string,url);
Expand All @@ -168,7 +164,7 @@ import observer from '../observer';
{ levels : levels,
url : url,
id : id,
stats : this.stats});
stats : stats});
} else {
observer.trigger(Event.LOAD_ERROR, { url : url, response : 'no level found in manifest'});
}
Expand All @@ -181,12 +177,6 @@ import observer from '../observer';
loaderror(event) {
observer.trigger(Event.LOAD_ERROR, { url : this.url, response : event.currentTarget});
}

loadprogress() {
if(this.stats.tfirst === undefined) {
this.stats.tfirst = new Date();
}
}
}

export default PlaylistLoader;
74 changes: 74 additions & 0 deletions src/utils/xhr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Xhr helper class
*
*/

import {logger} from '../utils/logger';

class Xhr {

constructor() {
}

destroy() {
this.abort();
this.xhr = null;
}

abort() {
if(this.xhr &&this.xhr.readyState !== 4) {
this.xhr.abort();
}
}

load(url,responseType,onSuccess,onError,timeout,maxAttempts, retryDelay=500) {
this.url = url;
this.responseType = responseType;
this.onSuccess = onSuccess;
this.onError = onError;
this.trequest = new Date();
this.timeout = timeout;
this.maxAttempts = maxAttempts;
this.retryDelay = retryDelay;
this.attempts = 0;
this.loadInternal();
}

loadInternal() {
var xhr = this.xhr = new XMLHttpRequest();
xhr.onload= this.loadsuccess.bind(this);
xhr.onerror = xhr.ontimeout = this.loaderror.bind(this);
xhr.onprogress = this.loadprogress.bind(this);
xhr.timeout = this.timeout;
xhr.open('GET', this.url , true);
xhr.responseType = this.responseType;
this.attempts++;
this.tfirst = null;
xhr.send();
}

loadsuccess(event) {
this.onSuccess(event,{trequest : this.trequest, tfirst : this.tfirst, tload : new Date() });
}

loaderror(event) {
if(this.attempts < this.maxAttempts) {
var retryDelay = this.retryDelay*this.attempts;
this.timeout*=2;
logger.log(`${event.type} while loading ${this.url}, retrying in ${retryDelay}...`);
this.destroy();
window.setTimeout(this.loadInternal.bind(this),retryDelay);
} else {
logger.log(`${event.type} while loading ${this.url}` );
this.onError(event);
}
}

loadprogress() {
if(this.tfirst === null) {
this.tfirst = new Date();
}
}
}

export default Xhr;

0 comments on commit 522ad45

Please sign in to comment.