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',