Skip to content

Commit

Permalink
Adds option to fail gracefully on xlink failure.
Browse files Browse the repository at this point in the history
At the moment, when there is an xlink problem, the manifest parser
returns a rejected promise. This adds a configuration variable to
instead simply not replace the xml tag.

Closes #788

Change-Id: Iace953233c83a57820130033150e7cd9a9385d6f
  • Loading branch information
theodab committed May 25, 2017
1 parent bd79d60 commit 1e119e4
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 29 deletions.
8 changes: 7 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,8 @@ shakaExtern.DrmConfiguration;
* @typedef {{
* customScheme: shakaExtern.DashContentProtectionCallback,
* clockSyncUri: string,
* ignoreDrmInfo: boolean
* ignoreDrmInfo: boolean,
* xlinkFailGracefully: boolean
* }}
*
* @property {shakaExtern.DashContentProtectionCallback} customScheme
Expand All @@ -437,6 +438,11 @@ shakaExtern.DrmConfiguration;
* If true will cause DASH parser to ignore DRM information specified
* by the manifest and treat it as if it signaled no particular key
* system and contained no init data. Defaults to false if not provided.
* @property {boolean} xlinkFailGracefully
* If true, xlink-related errors will result in a fallback to the tag's
* existing contents. If false, xlink-related errors will be propagated
* to the application and will result in a playback failure. Defaults to
* false if not provided.
*
* @exportDoc
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ shaka.dash.DashParser.prototype.parseManifest_ =
}

// Process the mpd to account for xlink connections.
var failGracefully = this.config_.dash.xlinkFailGracefully;
var xlinkPromise = MpdUtils.processXlinks(
mpd, this.config_.retryParameters, finalManifestUri,
mpd, this.config_.retryParameters, failGracefully, finalManifestUri,
this.playerInterface_.networkingEngine);
return xlinkPromise.then(function(finalMpd) {
return this.processManifest_(finalMpd, finalManifestUri);
Expand Down
65 changes: 44 additions & 21 deletions lib/dash/mpd_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,29 +426,44 @@ shaka.dash.MpdUtils.parseXml = function(data, expectedRootElemName) {

/**
* Follow the xlink link contained in the given element.
* It also strips the xlink properties off of the element,
* even if the process fails.
*
* @param {!Element} element
* @param {!shakaExtern.RetryParameters} retryParameters
* @param {boolean} failGracefully
* @param {!string} baseUri
* @param {!shaka.net.NetworkingEngine} networkingEngine
* @param {number} linkDepth
* @return {!Promise.<!Element>}
* @private
*/
shaka.dash.MpdUtils.handleXlinkInElement_ =
function(element, retryParameters, baseUri, networkingEngine, linkDepth) {
function(element, retryParameters, failGracefully, baseUri,
networkingEngine, linkDepth) {
var MpdUtils = shaka.dash.MpdUtils;
var Error = shaka.util.Error;
var ManifestParserUtils = shaka.util.ManifestParserUtils;

var xlinkHref = element.getAttribute('xlink:href');
var xlinkActuate = element.getAttribute('xlink:actuate') || 'onRequest';

// Remove the xlink properties, so it won't download again
// when re-processed.
for (var i = 0; i < element.attributes.length; i++) {
var attribute = element.attributes[i].nodeName;
if (attribute.indexOf('xlink:') != -1) {
element.removeAttribute(attribute);
i -= 1;
}
}

if (linkDepth >= 5) {
return Promise.reject(new Error(
Error.Severity.CRITICAL, Error.Category.MANIFEST,
Error.Code.DASH_XLINK_DEPTH_LIMIT));
}

var xlinkHref = element.getAttribute('xlink:href');
var xlinkActuate = element.getAttribute('xlink:actuate') || 'onRequest';
if (xlinkActuate != 'onLoad') {
// Only xlink:actuate="onLoad" is supported.
// When no value is specified, the assumed value is "onRequest".
Expand All @@ -457,16 +472,6 @@ shaka.dash.MpdUtils.handleXlinkInElement_ =
Error.Code.DASH_UNSUPPORTED_XLINK_ACTUATE));
}

// Remove the xlink properties, so it won't download again
// when re-processed.
for (var i = 0; i < element.attributes.length; i++) {
var attribute = element.attributes[i].nodeName;
if (attribute.indexOf('xlink:') != -1) {
element.removeAttribute(attribute);
i -= 1;
}
}

// Resolve the xlink href, in case it's a relative URL.
var uris = ManifestParserUtils.resolveUris([baseUri], [xlinkHref]);

Expand All @@ -486,6 +491,9 @@ shaka.dash.MpdUtils.handleXlinkInElement_ =
Error.Code.DASH_INVALID_XML, xlinkHref));
}

// Now that there is no other possibility of the process erroring,
// the element can be changed further.

// Remove the current contents of the node.
while (element.childNodes.length)
element.removeChild(element.childNodes[0]);
Expand All @@ -505,7 +513,7 @@ shaka.dash.MpdUtils.handleXlinkInElement_ =
}

return shaka.dash.MpdUtils.processXlinks(
element, retryParameters, uris[0], networkingEngine,
element, retryParameters, failGracefully, uris[0], networkingEngine,
linkDepth + 1);
}.bind(element));
};
Expand All @@ -517,20 +525,35 @@ shaka.dash.MpdUtils.handleXlinkInElement_ =
*
* @param {!Element} element
* @param {!shakaExtern.RetryParameters} retryParameters
* @param {boolean} failGracefully
* @param {!string} baseUri
* @param {!shaka.net.NetworkingEngine} networkingEngine
* @param {number=} opt_linkDepth
* @return {!Promise.<!Element>}
*/
shaka.dash.MpdUtils.processXlinks =
function(element, retryParameters, baseUri, networkingEngine,
opt_linkDepth) {
function(element, retryParameters, failGracefully, baseUri,
networkingEngine, opt_linkDepth) {
var MpdUtils = shaka.dash.MpdUtils;
opt_linkDepth = opt_linkDepth || 0;

if (element.getAttribute('xlink:href'))
return MpdUtils.handleXlinkInElement_(
element, retryParameters, baseUri, networkingEngine, opt_linkDepth);
if (element.getAttribute('xlink:href')) {
var handled = MpdUtils.handleXlinkInElement_(
element, retryParameters, failGracefully, baseUri,
networkingEngine, opt_linkDepth);
if (failGracefully) {
// Catch any error and go on.
handled = handled.catch(function() {
// handleXlinkInElement_ strips the xlink properties off of the element
// even if it fails, so calling processXlinks again will handle whatever
// contents the element natively has.
return MpdUtils.processXlinks(element, retryParameters, failGracefully,
baseUri, networkingEngine,
opt_linkDepth);
});
}
return handled;
}

// Filter out any children that should be nulled.
for (var i = 0; i < element.childNodes.length; i++) {
Expand All @@ -552,8 +575,8 @@ shaka.dash.MpdUtils.processXlinks =
if (child.nodeType == Node.ELEMENT_NODE) {
// Replace the child with its processed form.
var childPromise = shaka.dash.MpdUtils.processXlinks(
/** @type {!Element} */ (child), retryParameters, baseUri,
networkingEngine, opt_linkDepth);
/** @type {!Element} */ (child), retryParameters, failGracefully,
baseUri, networkingEngine, opt_linkDepth);
childPromises.push(childPromise);
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1716,7 +1716,8 @@ shaka.Player.prototype.defaultConfig_ = function() {
if (node) return null;
},
clockSyncUri: '',
ignoreDrmInfo: false
ignoreDrmInfo: false,
xlinkFailGracefully: false
},
hls: {
defaultTimeOffset: 0
Expand Down
3 changes: 2 additions & 1 deletion test/dash/dash_parser_content_protection_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ describe('DashParser ContentProtection', function() {
dash: {
clockSyncUri: '',
customScheme: callback,
ignoreDrmInfo: ignoreDrmInfo
ignoreDrmInfo: ignoreDrmInfo,
xlinkFailGracefully: false
},
hls: { defaultTimeOffset: 0 }
});
Expand Down
3 changes: 2 additions & 1 deletion test/dash/dash_parser_live_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ describe('DashParser Live', function() {
dash: {
clockSyncUri: '',
customScheme: function(node) { return null; },
ignoreDrmInfo: false
ignoreDrmInfo: false,
xlinkFailGracefully: false
},
hls: { defaultTimeOffset: 0 }
});
Expand Down
21 changes: 21 additions & 0 deletions test/dash/dash_parser_manifest_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,27 @@ describe('DashParser Manifest', function() {
Dash.testFails(done, source, error);
});

it('xlink problems when xlinkFailGracefully is false', function(done) {
var source = [
'<MPD minBufferTime="PT75S" xmlns="urn:mpeg:dash:schema:mpd:2011" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink">',
' <Period id="1" duration="PT30S">',
' <AdaptationSet mimeType="video/mp4">',
' <Representation bandwidth="1" xlink:href="https://xlink1" ' +
'xlink:actuate="onInvalid">', // Incorrect actuate
' <SegmentBase indexRange="100-200" />',
' </Representation>',
' </AdaptationSet>',
' </Period>',
'</MPD>'
].join('\n');
var error = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_UNSUPPORTED_XLINK_ACTUATE);
Dash.testFails(done, source, error);
});

it('failed network requests', function(done) {
var expectedError = new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
Expand Down
19 changes: 18 additions & 1 deletion test/dash/mpd_utils_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,10 @@ describe('MpdUtils', function() {
var retry;
var parser;
var Error = shaka.util.Error;
var failGracefully;

beforeEach(function() {
failGracefully = false;
retry = shaka.net.NetworkingEngine.defaultRetryParameters();
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
parser = new DOMParser();
Expand Down Expand Up @@ -622,6 +624,20 @@ describe('MpdUtils', function() {
testFails(baseXMLString, null, 1, done);
});

it('doesn\'t error when set to fail gracefully', function(done) {
failGracefully = true;
var baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad">' +
'<DefaultContents />' +
'</ToReplace>');
var xlinkXMLString = '<BadTagName</BadTagName>';
var desiredXMLString = inBaseContainer(
'<ToReplace><DefaultContents /></ToReplace>');

fakeNetEngine.setResponseMapAsText({'https://xlink1': xlinkXMLString});
testSucceeds(baseXMLString, desiredXMLString, 1, done);
});

function testSucceeds(
baseXMLString, desiredXMLString, desiredNetCalls, done) {
var desiredXML = parser.parseFromString(desiredXMLString, 'text/xml')
Expand Down Expand Up @@ -682,7 +698,8 @@ describe('MpdUtils', function() {
function testRequest(baseXMLString) {
var xml = parser.parseFromString(baseXMLString, 'text/xml')
.documentElement;
return MpdUtils.processXlinks(xml, retry, 'https://base', fakeNetEngine);
return MpdUtils.processXlinks(xml, retry, failGracefully, 'https://base',
fakeNetEngine);
}
});
});
3 changes: 2 additions & 1 deletion test/hls/hls_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('HlsParser', function() {
dash: {
customScheme: function(node) { return null; },
clockSyncUri: '',
ignoreDrmInfo: false
ignoreDrmInfo: false,
xlinkFailGracefully: false
},
hls: {
defaultTimeOffset: 0
Expand Down
3 changes: 2 additions & 1 deletion test/test/util/dash_parser_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ shaka.test.Dash.makeDashParser = function() {
dash: {
customScheme: function(node) { return null; },
clockSyncUri: '',
ignoreDrmInfo: false
ignoreDrmInfo: false,
xlinkFailGracefully: false
},
hls: { defaultTimeOffset: 0 }
});
Expand Down

0 comments on commit 1e119e4

Please sign in to comment.