diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 494c59d..cfee5f8 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -30,7 +30,7 @@ export const buildBaseUrls = (references, baseUrlElements) => { return references; } - return flatten(references.map(function(reference) { + const resolvedUrls = flatten(references.map(function(reference) { return baseUrlElements.map(function(baseUrlElement) { const initialBaseUrl = getContent(baseUrlElement); const resolvedBaseUrl = resolveUrl(reference.baseUrl, initialBaseUrl); @@ -49,6 +49,25 @@ export const buildBaseUrls = (references, baseUrlElements) => { return finalBaseUrl; }); })); + + // Absolute child BaseURLs do not resolve against the parent, so the product of + // N parent BaseURLs repeats that child URL N times. Keep one copy of each unique + // resolved BaseURL object so content-steering copies that differ by serviceLocation + // (or other attributes) are preserved. + // See: https://github.com/videojs/mpd-parser/issues/175 + const seen = {}; + + return resolvedUrls.filter(function(url) { + const key = JSON.stringify(url); + + if (seen[key]) { + return false; + } + + seen[key] = true; + + return true; + }); }; /** @@ -630,13 +649,6 @@ export const inheritAttributes = (mpd, options = {}) => { return { locations: mpdAttributes.locations, contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler), - // TODO: There are occurences where this `representationInfo` array contains undesired - // duplicates. This generally occurs when there are multiple BaseURL nodes that are - // direct children of the MPD node. When we attempt to resolve URLs from a combination of the - // parent BaseURL and a child BaseURL, and the value does not resolve, - // we end up returning the child BaseURL multiple times. - // We need to determine a way to remove these duplicates in a safe way. - // See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527 representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))), eventStream: flatten(periods.map(toEventStream)) }; diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index 637fecd..4e01eb9 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -81,6 +81,67 @@ QUnit.test('absolute BaseURL overwrites reference', function(assert) { ); }); +QUnit.test('absolute BaseURL is not duplicated across multiple references', function(assert) { + const reference = [ + { baseUrl: 'https://cdn1.example.com/' }, + { baseUrl: 'https://cdn2.example.com/' } + ]; + const node = [{ textContent: 'https://example.com/en.vtt' }]; + const expected = [{ baseUrl: 'https://example.com/en.vtt' }]; + + assert.deepEqual( + buildBaseUrls(reference, node), expected, + 'absolute url is not duplicated' + ); +}); + +QUnit.test('absolute BaseURL does not duplicate when mixed with relative BaseURLs', function(assert) { + const reference = [ + { baseUrl: 'https://cdn1.example.com/' }, + { baseUrl: 'https://cdn2.example.com/' } + ]; + const nodes = [ + { textContent: 'bar/' }, + { textContent: 'https://example.com/en.vtt' } + ]; + const expected = [ + { baseUrl: 'https://cdn1.example.com/bar/' }, + { baseUrl: 'https://example.com/en.vtt' }, + { baseUrl: 'https://cdn2.example.com/bar/' } + ]; + + assert.deepEqual( + buildBaseUrls(reference, nodes), expected, + 'relative urls still combine with each reference' + ); +}); + +QUnit.test('absolute BaseURLs with different serviceLocation are both kept', function(assert) { + const reference = [ + { baseUrl: 'https://cdn1.example.com/' }, + { baseUrl: 'https://cdn2.example.com/' } + ]; + const nodes = [ + { + textContent: 'https://example.com/en.vtt', + attributes: [{ name: 'serviceLocation', value: 'alpha' }] + }, + { + textContent: 'https://example.com/en.vtt', + attributes: [{ name: 'serviceLocation', value: 'beta' }] + } + ]; + const expected = [ + { baseUrl: 'https://example.com/en.vtt', serviceLocation: 'alpha' }, + { baseUrl: 'https://example.com/en.vtt', serviceLocation: 'beta' } + ]; + + assert.deepEqual( + buildBaseUrls(reference, nodes), expected, + 'distinct serviceLocation values are preserved' + ); +}); + QUnit.test('reference attributes are ignored when there is a BaseURL node', function(assert) { const reference = [{ baseUrl: 'https://example.com', attributes: [{ name: 'test', value: 'wow' }] }]; const node = [{ textContent: 'https://foo.com/bar/' }]; @@ -869,22 +930,6 @@ QUnit.test('end to end - content steering - non resolvable base URLs', function( template: {} } }, - { - attributes: { - NOW, - bandwidth: 256, - baseUrl: 'https://example.com/en.vtt', - clientOffset: 0, - id: 'en', - lang: 'en', - mimeType: 'text/vtt', - periodStart: 0, - role: {}, - sourceDuration: 0, - type: 'dyanmic' - }, - segmentInfo: {} - }, { attributes: { NOW, @@ -904,7 +949,7 @@ QUnit.test('end to end - content steering - non resolvable base URLs', function( ] }; - assert.equal(actual.representationInfo.length, 4); + assert.equal(actual.representationInfo.length, 3); assert.deepEqual(actual, expected); }); @@ -1388,26 +1433,10 @@ QUnit.test('end to end - alternate BaseURLs', function(assert) { clientOffset: 0 }, segmentInfo: {} - }, { - attributes: { - bandwidth: 256, - baseUrl: 'https://example.com/en.vtt', - id: 'en', - lang: 'en', - mediaPresentationDuration: 30, - mimeType: 'text/vtt', - periodStart: 0, - role: {}, - sourceDuration: 30, - type: 'static', - NOW, - clientOffset: 0 - }, - segmentInfo: {} }] }; - assert.equal(actual.representationInfo.length, 6); + assert.equal(actual.representationInfo.length, 5); assert.deepEqual(actual, expected); });