diff --git a/externs/shaka/player.js b/externs/shaka/player.js
index 04ccb69221..39947751df 100644
--- a/externs/shaka/player.js
+++ b/externs/shaka/player.js
@@ -996,7 +996,8 @@ shaka.extern.MssManifestConfiguration;
* segmentRelativeVttTiming: boolean,
* dash: shaka.extern.DashManifestConfiguration,
* hls: shaka.extern.HlsManifestConfiguration,
- * mss: shaka.extern.MssManifestConfiguration
+ * mss: shaka.extern.MssManifestConfiguration,
+ * raiseFatalErrorOnManifestUpdateRequestFailure: boolean
* }}
*
* @property {shaka.extern.RetryParameters} retryParameters
@@ -1036,6 +1037,9 @@ shaka.extern.MssManifestConfiguration;
* Advanced parameters used by the HLS manifest parser.
* @property {shaka.extern.MssManifestConfiguration} mss
* Advanced parameters used by the MSS manifest parser.
+ * @property {boolean} raiseFatalErrorOnManifestUpdateRequestFailure
+ * If true, manifest update request failures will cause a fatal errror.
+ * Defaults to false
if not provided.
*
* @exportDoc
*/
diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js
index ce4b20578e..fc2b005472 100644
--- a/lib/dash/dash_parser.js
+++ b/lib/dash/dash_parser.js
@@ -1360,6 +1360,10 @@ shaka.dash.DashParser = class {
// Try updating again, but ensure we haven't been destroyed.
if (this.playerInterface_) {
+ if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
+ this.playerInterface_.onError(error);
+ return;
+ }
// We will retry updating, so override the severity of the error.
error.severity = shaka.util.Error.Severity.RECOVERABLE;
this.playerInterface_.onError(error);
diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js
index 04da52d2c9..c0abb127c6 100644
--- a/lib/hls/hls_parser.js
+++ b/lib/hls/hls_parser.js
@@ -3339,6 +3339,11 @@ shaka.hls.HlsParser = class {
goog.asserts.assert(error instanceof shaka.util.Error,
'Should only receive a Shaka error');
+ if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
+ this.playerInterface_.onError(error);
+ return;
+ }
+
// We will retry updating, so override the severity of the error.
error.severity = shaka.util.Error.Severity.RECOVERABLE;
this.playerInterface_.onError(error);
diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js
index 3dd2ddc870..9290a5d1a8 100644
--- a/lib/util/player_configuration.js
+++ b/lib/util/player_configuration.js
@@ -109,6 +109,7 @@ shaka.util.PlayerConfiguration = class {
disableThumbnails: false,
defaultPresentationDelay: 0,
segmentRelativeVttTiming: false,
+ raiseFatalErrorOnManifestUpdateRequestFailure: false,
dash: {
clockSyncUri: '',
ignoreDrmInfo: false,
diff --git a/test/dash/dash_parser_live_unit.js b/test/dash/dash_parser_live_unit.js
index c1b7385f2f..93da3225c5 100644
--- a/test/dash/dash_parser_live_unit.js
+++ b/test/dash/dash_parser_live_unit.js
@@ -589,6 +589,8 @@ describe('DashParser Live', () => {
const onError = jasmine.createSpy('onError');
playerInterface.onError = Util.spyFunc(onError);
+ const updateTick = updateTickSpy();
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
await parser.start('dummy://foo', playerInterface);
@@ -603,6 +605,39 @@ describe('DashParser Live', () => {
await updateManifest();
expect(onError).toHaveBeenCalledTimes(1);
+ expect(updateTick).toHaveBeenCalledTimes(2);
+ });
+
+ it('fatal error on manifest update request failure when ' +
+ 'raiseFatalErrorOnManifestUpdateRequestFailure is true', async () => {
+ const manifestConfig =
+ shaka.util.PlayerConfiguration.createDefault().manifest;
+ manifestConfig.raiseFatalErrorOnManifestUpdateRequestFailure = true;
+ parser.configure(manifestConfig);
+
+ const updateTick = updateTickSpy();
+
+ const lines = [
+ '',
+ ];
+ const manifestText = makeSimpleLiveManifestText(lines, updateTime);
+ /** @type {!jasmine.Spy} */
+ const onError = jasmine.createSpy('onError');
+ playerInterface.onError = Util.spyFunc(onError);
+
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
+ await parser.start('dummy://foo', playerInterface);
+
+ const error = new shaka.util.Error(
+ shaka.util.Error.Severity.CRITICAL,
+ shaka.util.Error.Category.NETWORK,
+ shaka.util.Error.Code.BAD_HTTP_STATUS);
+ const operation = shaka.util.AbortableOperation.failed(error);
+ fakeNetEngine.request.and.returnValue(operation);
+
+ await updateManifest();
+ expect(onError).toHaveBeenCalledWith(error);
+ expect(updateTick).toHaveBeenCalledTimes(1);
});
it('uses @minimumUpdatePeriod', async () => {
diff --git a/test/demo/demo_unit.js b/test/demo/demo_unit.js
index 8bcff57286..6f078ee0fe 100644
--- a/test/demo/demo_unit.js
+++ b/test/demo/demo_unit.js
@@ -98,6 +98,7 @@ describe('Demo', () => {
.add('manifest.mss.keySystemsBySystemId')
.add('drm.keySystemsMapping')
.add('streaming.parsePrftBox')
+ .add('manifest.raiseFatalErrorOnManifestUpdateRequestFailure')
.add('drm.persistentSessionOnlinePlayback')
.add('drm.persistentSessionsMetadata');
diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js
index 3850a73db1..1f793144f5 100644
--- a/test/hls/hls_live_unit.js
+++ b/test/hls/hls_live_unit.js
@@ -88,6 +88,15 @@ describe('HlsParser live', () => {
parser.stop();
});
+ /**
+ * Gets a spy on the function that sets the update period.
+ * @return {!jasmine.Spy}
+ * @suppress {accessControls}
+ */
+ function updateTickSpy() {
+ return spyOn(parser.updatePlaylistTimer_, 'tickAfter');
+ }
+
/**
* Trigger a manifest update.
* @suppress {accessControls}
@@ -332,6 +341,34 @@ describe('HlsParser live', () => {
expect(notifySegmentsSpy).toHaveBeenCalled();
});
+ it('fatal error on manifest update request failure when ' +
+ 'raiseFatalErrorOnManifestUpdateRequestFailure is true', async () => {
+ const manifestConfig =
+ shaka.util.PlayerConfiguration.createDefault().manifest;
+ manifestConfig.raiseFatalErrorOnManifestUpdateRequestFailure = true;
+ parser.configure(manifestConfig);
+
+ const updateTick = updateTickSpy();
+
+ await testInitialManifest(master, media);
+ expect(updateTick).toHaveBeenCalledTimes(1);
+
+ /** @type {!jasmine.Spy} */
+ const onError = jasmine.createSpy('onError');
+ playerInterface.onError = shaka.test.Util.spyFunc(onError);
+
+ const error = new shaka.util.Error(
+ shaka.util.Error.Severity.CRITICAL,
+ shaka.util.Error.Category.NETWORK,
+ shaka.util.Error.Code.BAD_HTTP_STATUS);
+ const operation = shaka.util.AbortableOperation.failed(error);
+ fakeNetEngine.request.and.returnValue(operation);
+
+ await delayForUpdatePeriod();
+ expect(onError).toHaveBeenCalledWith(error);
+ expect(updateTick).toHaveBeenCalledTimes(1);
+ });
+
it('converts to VOD only after all playlists end', async () => {
const master = [
'#EXTM3U\n',