Skip to content

Commit

Permalink
Recover from failed segment requests.
Browse files Browse the repository at this point in the history
* Reject Task Promise if one of its stages fails so that the caller's
  catch handler is invoked.
* Use TypedBind consistently in affected Promise chains.

Closes shaka-project#131

Change-Id: I94fa6688949444212fa0b1edd3a94e0de4d6956f
  • Loading branch information
Timothy Drews authored and jonoward committed Jul 21, 2015
1 parent 74e300b commit c617142
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 38 deletions.
18 changes: 12 additions & 6 deletions lib/media/source_buffer_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,18 @@ shaka.media.SourceBufferManager.prototype.correct = function(
shaka.media.SourceBufferManager.prototype.startTask_ = function() {
shaka.asserts.assert(this.task_);
this.task_.start();
return this.task_.getPromise().then(function() {
this.task_ = null;
}.bind(this)).catch(function(error) {
this.task_ = null;
return Promise.reject(error);
}.bind(this));
return this.task_.getPromise().then(shaka.util.TypedBind(this,
function() {
this.task_ = null;
})
).catch(shaka.util.TypedBind(this,
/** @param {*} error */
function(error) {
shaka.log.v1('The task failed!');
this.task_ = null;
return Promise.reject(error);
})
);
};


Expand Down
1 change: 1 addition & 0 deletions lib/media/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ shaka.media.Stream.prototype.onUpdate_ = function() {
var recoverableErrors = [0, 404, 410];
if (error.type == 'net' &&
recoverableErrors.indexOf(error.xhr.status) != -1) {
shaka.log.debug('Calling onUpdate_() in 5 seconds...');
// Depending on application policy, this could be recoverable,
// so set a timer on the supposition that the app might not end
// playback.
Expand Down
72 changes: 40 additions & 32 deletions lib/util/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,38 +157,46 @@ shaka.util.Task.prototype.startNextStage_ = function(arg) {
this.aborter_ = null;
}

done.then(function(arg) {
if (this.abortedPromise_) {
// Aborted in between stages or in a way that didn't fail the stage.
// Clean up.
this.stages_ = [];
this.aborter_ = null;
this.completeAbort_();
return;
}

// Throw away the stage we just completed.
this.stages_.shift();

if (this.stages_.length) {
// Start the next stage.
this.startNextStage_(arg);
} else {
// All done. Clean up.
this.taskPromise_.resolve(arg);
this.aborter_ = null;
}
}.bind(this)).catch(function(error) {
// Task failed. Clean up.
this.stages_ = [];
this.aborter_ = null;

if (this.abortedPromise_) {
// Aborted during a stage in a way that failed the stage.
// Resolve the aborted promise.
this.completeAbort_();
}
}.bind(this));
done.then(shaka.util.TypedBind(this,
/** @param {?} arg */
function(arg) {
if (this.abortedPromise_) {
// Aborted in between stages or in a way that didn't fail the stage.
// Clean up.
this.stages_ = [];
this.aborter_ = null;
this.completeAbort_();
return;
}

// Throw away the stage we just completed.
this.stages_.shift();

if (this.stages_.length) {
// Start the next stage.
this.startNextStage_(arg);
} else {
// All done. Clean up.
this.taskPromise_.resolve(arg);
this.aborter_ = null;
}
})
).catch(shaka.util.TypedBind(this,
/** @param {*} error */
function(error) {
// Task failed. Clean up.
this.stages_ = [];
this.aborter_ = null;

if (this.abortedPromise_) {
// Aborted during a stage in a way that failed the stage.
// Resolve the aborted promise.
this.completeAbort_();
} else {
this.taskPromise_.reject(error);
}
})
);
};


Expand Down
27 changes: 27 additions & 0 deletions spec/task_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,5 +287,32 @@ describe('Task', function() {
});
});
});

describe('getPromise', function() {
it('is rejected when a stage fails', function(done) {
var p = new shaka.util.PublicPromise();
t.append(function() { return [p]; });
t.start();

var timer = setTimeout(function() {
p.resolve();
}, 1500);

setTimeout(function() {
clearTimeout(timer);
var error = new Error('This is a test error.');
error.type = 'test';
p.reject(error);
}, 500);

t.getPromise().then(function() {
// The first stage should never complete.
fail();
}).catch(function(error) {
expect(error.type).toBe('test');
done();
});
});
});
});

0 comments on commit c617142

Please sign in to comment.