From 9afa4eb20da67adb0dcb50f34e41f0ea9c109972 Mon Sep 17 00:00:00 2001 From: Aidan Ridley Date: Tue, 22 Jun 2021 16:15:56 -0600 Subject: [PATCH] feat: Add callback for apps to pre-process DASH manifests (#3480) Added support for efficient preprocessing of DASH manifest after they have been parsed into an XMLDocument This is configured with the new manifest.dash.manifestPreprocessor setting. This is need to efficiently repair manifests that are not compatible with Shaka Player. Closes #3339 --- externs/shaka/player.js | 7 ++++- lib/dash/dash_parser.js | 5 +++ lib/util/player_configuration.js | 6 ++++ test/dash/dash_parser_manifest_unit.js | 42 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 92922d95f8..88ddbc6970 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -634,7 +634,8 @@ shaka.extern.DrmConfiguration; * ignoreSuggestedPresentationDelay: boolean, * ignoreEmptyAdaptationSet: boolean, * ignoreMaxSegmentDuration: boolean, - * keySystemsByURI: !Object. + * keySystemsByURI: !Object., + * manifestPreprocessor: function(!Element) * }} * * @property {string} clockSyncUri @@ -684,6 +685,10 @@ shaka.extern.DrmConfiguration; * @property {Object.} keySystemsByURI * A map of scheme URI to key system name. Defaults to default key systems * mapping handled by Shaka. + * @property {function(!Element)} manifestPreprocessor + * Called immediately after the DASH manifest has been parsed into an + * XMLDocument. Provides a way for applications to perform efficient + * preprocessing of the manifest. * @exportDoc */ shaka.extern.DashManifestConfiguration; diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 83b18dccb4..b41af6a923 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -289,6 +289,11 @@ shaka.dash.DashParser = class { const Functional = shaka.util.Functional; const XmlUtils = shaka.util.XmlUtils; + const manifestPreprocessor = this.config_.dash.manifestPreprocessor; + if (manifestPreprocessor) { + manifestPreprocessor(mpd); + } + // Get any Location elements. This will update the manifest location and // the base URI. /** @type {!Array.} */ diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 5a907f2328..500cbddcc4 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -109,6 +109,12 @@ shaka.util.PlayerConfiguration = class { 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime', }, + // Need some operation in the callback or else closure may remove calls + // to the function as it would be a no-op. The operation can't just be + // a log message, because those are stripped in the compiled build. + manifestPreprocessor: (element) => { + return element; + }, }, hls: { ignoreTextStreamFailures: false, diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index a7bc7760c6..017226d962 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -1790,6 +1790,48 @@ describe('DashParser Manifest', () => { expect(manifest.presentationTimeline).toBeTruthy(); }); + it('Invokes manifestPreprocessor in config', async () => { + const manifestText = [ + '', + ' ', + ' ', + ' ', + ' v-sd.mp4', + ' ', + ' ', + ' ', + ' ', + ' ', + ' a-en.mp4', + ' ', + ' ', + ' ', + ' ', + ' ', + ' http://example.com/de.vtt', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo', manifestText); + const config = shaka.util.PlayerConfiguration.createDefault().manifest; + config.dash.manifestPreprocessor = (mpd) => { + const selector = 'AdaptationSet[mimeType="text/vtt"'; + const vttElements = mpd.querySelectorAll(selector); + for (const element of vttElements) { + element.parentNode.removeChild(element); + } + }; + parser.configure(config); + + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo', playerInterface); + const stream = manifest.textStreams[0]; + expect(stream).toBeUndefined(); + }); + it('converts Accessibility element to "kind"', async () => { const manifestText = [ '',