Skip to content

Commit

Permalink
feat: Raise fatal error on linear manifest request update failure (sh…
Browse files Browse the repository at this point in the history
…aka-project#5138)

We (Sky/Peacock) required the ability to try a different ad stitched
manifest upon a manifest request update failure.

After the initial retry parameters (timeouts and retries) have been
exhausted, error immediately and not continue to retry with the same
manifest.
  • Loading branch information
dave-nicholas committed Apr 26, 2023
1 parent d26229d commit 3ff7ba3
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 1 deletion.
6 changes: 5 additions & 1 deletion externs/shaka/player.js
Expand Up @@ -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
Expand Down Expand Up @@ -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 <code>false</code> if not provided.
*
* @exportDoc
*/
Expand Down
4 changes: 4 additions & 0 deletions lib/dash/dash_parser.js
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions lib/hls/hls_parser.js
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions lib/util/player_configuration.js
Expand Up @@ -109,6 +109,7 @@ shaka.util.PlayerConfiguration = class {
disableThumbnails: false,
defaultPresentationDelay: 0,
segmentRelativeVttTiming: false,
raiseFatalErrorOnManifestUpdateRequestFailure: false,
dash: {
clockSyncUri: '',
ignoreDrmInfo: false,
Expand Down
35 changes: 35 additions & 0 deletions test/dash/dash_parser_live_unit.js
Expand Up @@ -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);

Expand All @@ -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 = [
'<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2"/>',
];
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 () => {
Expand Down
1 change: 1 addition & 0 deletions test/demo/demo_unit.js
Expand Up @@ -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');

Expand Down
37 changes: 37 additions & 0 deletions test/hls/hls_live_unit.js
Expand Up @@ -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}
Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit 3ff7ba3

Please sign in to comment.