Skip to content

Commit

Permalink
Stop infinitely retrying on network errors for VOD (#842)
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 #830 and #762
  • Loading branch information
bhh1988 authored and joeyparrish committed Jun 5, 2017
1 parent ace0e8c commit 41eac10
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 @@ -497,6 +497,7 @@ shakaExtern.ManifestConfiguration;
/**
* @typedef {{
* retryParameters: shakaExtern.RetryParameters,
* infiniteRetriesForLiveStreams: boolean,
* rebufferingGoal: number,
* bufferingGoal: number,
* bufferBehind: number,
Expand All @@ -511,6 +512,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

0 comments on commit 41eac10

Please sign in to comment.