Skip to content

Commit

Permalink
Stop infinitely retrying on network errors for VOD
Browse files Browse the repository at this point in the history
Do not infinitely retry on top of the retry-policy already defined by
RetryParameters, especially for VOD. For live video the previous retry
logic is still useful to be robust against clock-sync or asset
availability issues. This commit adds a boolean configuration parameter,
infiniteRetriesForLiveStreams, to allow the user to turn this behavior
off even for live video.

Fixes shaka-project#830 and shaka-project#762
  • Loading branch information
Bryan Huh committed Jun 2, 2017
1 parent 859130f commit 5df744c
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 16 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Please keep the list sorted.

AdsWizz <*@adswizz.com>
Bryan Huh <bhh1988@gmail.com>
Esteban Dosztal <edosztal@gmail.com>
Google Inc. <*@google.com>
Edgeware AB <*@edgeware.tv>
Expand Down
1 change: 1 addition & 0 deletions docs/tutorials/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ player.getConfiguration();
bufferBehind: 30
bufferingGoal: 10
ignoreTextStreamFailures: false
infiniteRetriesForLiveStreams: true
rebufferingGoal: 2
retryParameters: Object

Expand Down
4 changes: 4 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ shakaExtern.ManifestConfiguration;
/**
* @typedef {{
* retryParameters: shakaExtern.RetryParameters,
* infiniteRetriesForLiveStreams: boolean,
* rebufferingGoal: number,
* bufferingGoal: number,
* bufferBehind: number,
Expand All @@ -503,6 +504,9 @@ shakaExtern.ManifestConfiguration;
*
* @property {shakaExtern.RetryParameters} retryParameters
* Retry parameters for segment requests.
* @property {boolean} infiniteRetriesForLiveStreams
* If true, will retry infinitely on network errors, for live streams only.
* Defaults to true.
* @property {number} rebufferingGoal
* The minimum number of seconds of content that the StreamingEngine must
* buffer before it can begin playback or can continue playback after it has
Expand Down
6 changes: 4 additions & 2 deletions lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1243,9 +1243,11 @@ shaka.media.StreamingEngine.prototype.fetchAndAppend_ = function(

mediaState.performingUpdate = false;

if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
if (this.manifest_.presentationTimeline.isLive() &&
this.config_.infiniteRetriesForLiveStreams &&
(error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
error.code == shaka.util.Error.Code.HTTP_ERROR ||
error.code == shaka.util.Error.Code.TIMEOUT) {
error.code == shaka.util.Error.Code.TIMEOUT)) {
this.handleNetworkError_(mediaState, error);
} else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
this.handleQuotaExceeded_(mediaState, error);
Expand Down
1 change: 1 addition & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,7 @@ shaka.Player.prototype.defaultConfig_ = function() {
},
streaming: {
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
rebufferingGoal: 2,
bufferingGoal: 10,
bufferBehind: 30,
Expand Down
1 change: 1 addition & 0 deletions test/media/playhead_observer_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('PlayheadObserver', function() {
rebufferingGoal: 10,
bufferingGoal: 5,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 15,
ignoreTextStreamFailures: false,
useRelativeCueTimestamps: false,
Expand Down
1 change: 1 addition & 0 deletions test/media/playhead_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ describe('Playhead', function() {
rebufferingGoal: 10,
bufferingGoal: 5,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 15,
ignoreTextStreamFailures: false,
useRelativeCueTimestamps: false,
Expand Down
10 changes: 7 additions & 3 deletions test/media/streaming_engine_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('StreamingEngine', function() {
rebufferingGoal: 2,
bufferingGoal: 5,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 15,
ignoreTextStreamFailures: false,
useRelativeCueTimestamps: false,
Expand Down Expand Up @@ -128,7 +129,8 @@ describe('StreamingEngine', function() {
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
0 /* segmentAvailabilityStart */,
60 /* segmentAvailabilityEnd */,
60 /* presentationDuration */);
60 /* presentationDuration */,
false /* isLive */);

setupNetworkingEngine(
0 /* firstPeriodStartTime */,
Expand Down Expand Up @@ -162,7 +164,8 @@ describe('StreamingEngine', function() {
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
275 - 10 /* segmentAvailabilityStart */,
295 - 10 /* segmentAvailabilityEnd */,
Infinity /* presentationDuration */);
Infinity /* presentationDuration */,
true /* isLive */);

setupNetworkingEngine(
0 /* firstPeriodStartTime */,
Expand Down Expand Up @@ -669,7 +672,8 @@ describe('StreamingEngine', function() {
shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
0 /* segmentAvailabilityStart */,
30 /* segmentAvailabilityEnd */,
30 /* presentationDuration */);
30 /* presentationDuration */,
false /* isLive */);

setupNetworkingEngine(
0 /* firstPeriodStartTime */,
Expand Down
137 changes: 128 additions & 9 deletions test/media/streaming_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ describe('StreamingEngine', function() {
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
0 /* segmentAvailabilityStart */,
40 /* segmentAvailabilityEnd */,
40 /* presentationDuration */);
40 /* presentationDuration */,
false /* isLive */);

setupManifest(
0 /* firstPeriodStartTime */,
Expand Down Expand Up @@ -259,8 +260,9 @@ describe('StreamingEngine', function() {

timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
100 /* segmentAvailabilityStart */,
120 /* segmentAvailabilityEnd */,
140 /* presentationDuration */);
140 /* segmentAvailabilityEnd */,
140 /* presentationDuration */,
true /* isLive */);

setupManifest(
0 /* firstPeriodStartTime */,
Expand Down Expand Up @@ -385,6 +387,7 @@ describe('StreamingEngine', function() {
rebufferingGoal: 2,
bufferingGoal: 5,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: Infinity,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
Expand Down Expand Up @@ -1544,7 +1547,7 @@ describe('StreamingEngine', function() {

describe('handles network errors', function() {
function testRecoverableError(targetUri, code) {
setupVod();
setupLive();

// Wrap the NetworkingEngine to perform errors.
var originalNetEngine = netEngine;
Expand Down Expand Up @@ -1574,9 +1577,9 @@ describe('StreamingEngine', function() {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();

playhead.getTime.and.returnValue(0);
playhead.getTime.and.returnValue(100);
onStartupComplete.and.callFake(function() {
setupFakeGetTime(0);
setupFakeGetTime(100);
});

onError.and.callFake(function(error) {
Expand All @@ -1603,7 +1606,7 @@ describe('StreamingEngine', function() {
null, '2_video_init', shaka.util.Error.Code.BAD_HTTP_STATUS));
it('from missing media, first Period',
testRecoverableError.bind(
null, '1_video_1', shaka.util.Error.Code.BAD_HTTP_STATUS));
null, '1_video_10', shaka.util.Error.Code.BAD_HTTP_STATUS));
it('from missing media, second Period',
testRecoverableError.bind(
null, '2_audio_2', shaka.util.Error.Code.BAD_HTTP_STATUS));
Expand All @@ -1616,7 +1619,7 @@ describe('StreamingEngine', function() {
null, '2_audio_init', shaka.util.Error.Code.HTTP_ERROR));
it('from missing media, first Period',
testRecoverableError.bind(
null, '1_audio_1', shaka.util.Error.Code.HTTP_ERROR));
null, '1_audio_10', shaka.util.Error.Code.HTTP_ERROR));
it('from missing media, second Period',
testRecoverableError.bind(
null, '2_video_2', shaka.util.Error.Code.HTTP_ERROR));
Expand All @@ -1629,7 +1632,7 @@ describe('StreamingEngine', function() {
null, '2_video_init', shaka.util.Error.Code.TIMEOUT));
it('from missing media, first Period',
testRecoverableError.bind(
null, '1_video_2', shaka.util.Error.Code.TIMEOUT));
null, '1_video_11', shaka.util.Error.Code.TIMEOUT));
it('from missing media, second Period',
testRecoverableError.bind(
null, '2_audio_1', shaka.util.Error.Code.TIMEOUT));
Expand Down Expand Up @@ -1735,6 +1738,118 @@ describe('StreamingEngine', function() {
expect(onError.calls.count()).toBe(0);
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
});

it('Does not retry if configured not to', function() {
setupLive();
// Wrap the NetworkingEngine to perform errors.
var originalNetEngine = netEngine;
netEngine = {
request: jasmine.createSpy('request')
};
var attempts = 0;
var targetUri = '1_audio_init';
netEngine.request.and.callFake(function(requestType, request) {
if (request.uris[0] == targetUri) {
++attempts;
if (attempts == 1) {
var data = [targetUri];
data.push(404);
data.push('');

return Promise.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.NETWORK,
shaka.util.Error.Code.BAD_HTTP_STATUS, data));
}
}
return originalNetEngine.request(requestType, request);
});

mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);

var config = {
rebufferingGoal: 2,
bufferingGoal: 5,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: false,
bufferBehind: Infinity,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
smallGapLimit: 0.5,
jumpLargeGaps: false
};
createStreamingEngine(config);

playhead.getTime.and.returnValue(100);
onStartupComplete.and.callFake(function() {
setupFakeGetTime(100);
});

onError.and.callFake(function(error) {
expect(error.severity).toBe(shaka.util.Error.Severity.CRITICAL);
expect(error.category).toBe(shaka.util.Error.Category.NETWORK);
expect(error.code).toBe(shaka.util.Error.Code.BAD_HTTP_STATUS);
});

// Here we go!
onChooseStreams.and.callFake(defaultOnChooseStreams.bind(null));
streamingEngine.init();

runTest();
expect(onError.calls.count()).toBe(1);
expect(attempts).toBe(1);
expect(mediaSourceEngine.endOfStream).toHaveBeenCalledTimes(0);
});

it('Does not retry for VOD', function() {
setupVod();
// Wrap the NetworkingEngine to perform errors.
var originalNetEngine = netEngine;
netEngine = {
request: jasmine.createSpy('request')
};
var attempts = 0;
var targetUri = '1_audio_init';
netEngine.request.and.callFake(function(requestType, request) {
if (request.uris[0] == targetUri) {
++attempts;
if (attempts == 1) {
var data = [targetUri];
data.push(404);
data.push('');

return Promise.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.NETWORK,
shaka.util.Error.Code.BAD_HTTP_STATUS, data));
}
}
return originalNetEngine.request(requestType, request);
});

mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();

playhead.getTime.and.returnValue(0);
onStartupComplete.and.callFake(function() {
setupFakeGetTime(0);
});

onError.and.callFake(function(error) {
expect(error.severity).toBe(shaka.util.Error.Severity.CRITICAL);
expect(error.category).toBe(shaka.util.Error.Category.NETWORK);
expect(error.code).toBe(shaka.util.Error.Code.BAD_HTTP_STATUS);
});

// Here we go!
onChooseStreams.and.callFake(defaultOnChooseStreams.bind(null));
streamingEngine.init();

runTest();
expect(onError.calls.count()).toBe(1);
expect(attempts).toBe(1);
expect(mediaSourceEngine.endOfStream).toHaveBeenCalledTimes(0);
});
});

describe('eviction', function() {
Expand All @@ -1748,6 +1863,7 @@ describe('StreamingEngine', function() {
rebufferingGoal: 1,
bufferingGoal: 1,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 10,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
Expand Down Expand Up @@ -1837,6 +1953,7 @@ describe('StreamingEngine', function() {
rebufferingGoal: 1,
bufferingGoal: 1,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 10,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
Expand Down Expand Up @@ -1906,6 +2023,7 @@ describe('StreamingEngine', function() {
rebufferingGoal: 1,
bufferingGoal: 1,
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: 10,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
Expand Down Expand Up @@ -2090,6 +2208,7 @@ describe('StreamingEngine', function() {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine({
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
infiniteRetriesForLiveStreams: true,
bufferBehind: Infinity,
ignoreTextStreamFailures: false,
startAtSegmentBoundary: false,
Expand Down
6 changes: 4 additions & 2 deletions test/test/util/streaming_engine_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ shaka.test.StreamingEngineUtil.createFakeNetworkingEngine = function(
* @param {number} segmentAvailabilityEnd The initial value of
* |segmentAvailabilityEnd|.
* @param {number} presentationDuration
* @param {boolean} isLive
* @return {!Object} A PresentationTimeline look-alike.
*
*/
shaka.test.StreamingEngineUtil.createFakePresentationTimeline = function(
segmentAvailabilityStart, segmentAvailabilityEnd, presentationDuration) {
segmentAvailabilityStart, segmentAvailabilityEnd, presentationDuration,
isLive) {
var timeline = {
getDuration: jasmine.createSpy('getDuration'),
setDuration: jasmine.createSpy('setDuration'),
Expand All @@ -127,7 +129,7 @@ shaka.test.StreamingEngineUtil.createFakePresentationTimeline = function(
timeline.getDuration.and.returnValue(presentationDuration);

timeline.isLive.and.callFake(function() {
return presentationDuration == Infinity;
return isLive;
});

timeline.getEarliestStart.and.callFake(function() {
Expand Down

1 comment on commit 5df744c

@bhh1988
Copy link
Owner

@bhh1988 bhh1988 commented on 5df744c Jun 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the conf value, I chose the name "infiniteRetriesForLiveStreams". Not sure if you guys agree with the name, but the suggested "retryForLiveStreams" seemed misleading to me because even with it false, there will still be retries for live streams according to the passed in RetryParameters. I'm open to suggestions for alternatives.

Please sign in to comment.