From a5adb397139cd2f55cb238725aa18cb3891ef742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Tue, 23 Jul 2024 08:49:22 +0200 Subject: [PATCH] feat(DASH): Support Annex I: Flexible Insertion of URL Parameters (#7086) Resolves https://github.com/shaka-project/shaka-player/issues/6472 --- lib/dash/dash_parser.js | 148 +++++++++++++-- lib/dash/segment_base.js | 4 +- lib/dash/segment_list.js | 7 +- lib/dash/segment_template.js | 24 ++- lib/util/manifest_parser_utils.js | 15 +- test/dash/dash_parser_manifest_unit.js | 252 +++++++++++++++++++++++++ 6 files changed, 417 insertions(+), 33 deletions(-) diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 2722008d07..f32cf0f847 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -7,6 +7,7 @@ goog.provide('shaka.dash.DashParser'); goog.require('goog.asserts'); +goog.require('goog.Uri'); goog.require('shaka.Deprecate'); goog.require('shaka.abr.Ewma'); goog.require('shaka.dash.ContentProtection'); @@ -176,6 +177,9 @@ shaka.dash.DashParser = class { /** @private {boolean} */ this.isTransitionFromDynamicToStatic_ = false; + + /** @private {string} */ + this.lastManifestQueryParams_ = ''; } /** @@ -397,6 +401,9 @@ shaka.dash.DashParser = class { this.manifestUris_.unshift(response.uri); } + const uriObj = new goog.Uri(response.uri); + this.lastManifestQueryParams_ = uriObj.getQueryData().toString(); + // This may throw, but it will result in a failed promise. await this.parseManifest_(response.data, response.uri, rootElement); // Keep track of how long the longest manifest update took. @@ -731,6 +738,7 @@ shaka.dash.DashParser = class { mediaPresentationDuration: null, profiles: profiles.split(','), roles: null, + urlParams: () => '', }; this.gapCount_ = 0; @@ -1011,6 +1019,7 @@ shaka.dash.DashParser = class { mediaPresentationDuration: this.manifestPatchContext_.mediaPresentationDuration, roles: null, + urlParams: () => '', }; const periodsAndDuration = this.parsePeriods_(context, @@ -1464,6 +1473,19 @@ shaka.dash.DashParser = class { periodInfo.start, periodInfo.duration, node, availabilityStart); } + + const supplementalProperties = + TXml.findChildren(periodInfo.node, 'SupplementalProperty'); + for (const prop of supplementalProperties) { + const schemeId = prop.attributes['schemeIdUri']; + if (schemeId == 'urn:mpeg:dash:urlparam:2014') { + const urlParams = this.getURLParametersFunction_(prop); + if (urlParams) { + context.urlParams = urlParams; + } + } + } + const adaptationSetNodes = TXml.findChildren(periodInfo.node, 'AdaptationSet'); const adaptationSets = adaptationSetNodes @@ -1674,7 +1696,8 @@ shaka.dash.DashParser = class { const fontUrl = prop.attributes['dvb:url']; if (fontFamily && fontUrl) { const uris = shaka.util.ManifestParserUtils.resolveUris( - context.adaptationSet.getBaseUris(), [fontUrl]); + context.adaptationSet.getBaseUris(), [fontUrl], + context.urlParams()); this.playerInterface_.addFont(fontFamily, uris[0]); } }; @@ -1684,6 +1707,7 @@ shaka.dash.DashParser = class { // ID of real AdaptationSet if this is a trick mode set: let trickModeFor = null; let isFastSwitching = false; + let adaptationSetUrlParams = null; let unrecognizedEssentialProperty = false; for (const prop of essentialProperties) { const schemeId = prop.attributes['schemeIdUri']; @@ -1704,11 +1728,27 @@ shaka.dash.DashParser = class { isFastSwitching = true; } else if (schemeId == 'urn:dvb:dash:fontdownload:2014') { parseFont(prop); + } else if (schemeId == 'urn:mpeg:dash:urlparam:2014') { + adaptationSetUrlParams = this.getURLParametersFunction_(prop); + if (!adaptationSetUrlParams) { + unrecognizedEssentialProperty = true; + } } else { unrecognizedEssentialProperty = true; } } + // According to DASH spec (2014) section 5.8.4.8, "the successful processing + // of the descriptor is essential to properly use the information in the + // parent element". According to DASH IOP v3.3, section 3.3.4, "if the + // scheme or the value" for EssentialProperty is not recognized, "the DASH + // client shall ignore the parent element." + if (unrecognizedEssentialProperty) { + // Stop parsing this AdaptationSet and let the caller filter out the + // nulls. + return null; + } + let lastSegmentNumber = null; const supplementalProperties = @@ -1727,9 +1767,15 @@ shaka.dash.DashParser = class { ); } else if (schemeId == 'urn:dvb:dash:fontdownload:2014') { parseFont(prop); + } else if (schemeId == 'urn:mpeg:dash:urlparam:2014') { + adaptationSetUrlParams = this.getURLParametersFunction_(prop); } } + if (adaptationSetUrlParams) { + context.urlParams = adaptationSetUrlParams; + } + const accessibilities = TXml.findChildren(elem, 'Accessibility'); const LanguageUtils = shaka.util.LanguageUtils; const closedCaptions = new Map(); @@ -1831,17 +1877,6 @@ shaka.dash.DashParser = class { } } - // According to DASH spec (2014) section 5.8.4.8, "the successful processing - // of the descriptor is essential to properly use the information in the - // parent element". According to DASH IOP v3.3, section 3.3.4, "if the - // scheme or the value" for EssentialProperty is not recognized, "the DASH - // client shall ignore the parent element." - if (unrecognizedEssentialProperty) { - // Stop parsing this AdaptationSet and let the caller filter out the - // nulls. - return null; - } - const contentProtectionElems = TXml.findChildren(elem, 'ContentProtection'); const contentProtection = ContentProtection.parseFromAdaptationSet( @@ -1933,6 +1968,48 @@ shaka.dash.DashParser = class { }; } + /** + * @param {!shaka.extern.xml.Node} elem + * @return {?function():string} + * @private + */ + getURLParametersFunction_(elem) { + const TXml = shaka.util.TXml; + const urlQueryInfo = TXml.findChildNS( + elem, shaka.dash.DashParser.UP_NAMESPACE_, 'UrlQueryInfo'); + if (urlQueryInfo && TXml.parseAttr(urlQueryInfo, 'useMPDUrlQuery', + TXml.parseBoolean, /* defaultValue= */ false)) { + const queryTemplate = urlQueryInfo.attributes['queryTemplate']; + if (queryTemplate) { + return () => { + if (queryTemplate == '$querypart$') { + return this.lastManifestQueryParams_; + } + const parameters = queryTemplate.split('&').map((param) => { + if (param == '$querypart$') { + return this.lastManifestQueryParams_; + } else { + const regex = /\$query:(.*?)\$/g; + const parts = regex.exec(param); + if (parts && parts.length == 2) { + const paramName = parts[1]; + const queryData = + new goog.Uri.QueryData(this.lastManifestQueryParams_); + const value = queryData.get(paramName); + if (value.length) { + return paramName + '=' + value[0]; + } + } + return param; + } + }); + return parameters.join('&'); + }; + } + } + return null; + } + /** * Parses a Representation XML element. * @@ -1986,6 +2063,34 @@ shaka.dash.DashParser = class { context.roles = roles; + const supplementalPropertyElems = + TXml.findChildren(node, 'SupplementalProperty'); + const essentialPropertyElems = + TXml.findChildren(node, 'EssentialProperty'); + + let representationUrlParams = null; + let urlParamsElement = essentialPropertyElems.find((element) => { + const schemeId = element.attributes['schemeIdUri']; + return schemeId == 'urn:mpeg:dash:urlparam:2014'; + }); + if (urlParamsElement) { + representationUrlParams = + this.getURLParametersFunction_(urlParamsElement); + } else { + urlParamsElement = supplementalPropertyElems.find((element) => { + const schemeId = element.attributes['schemeIdUri']; + return schemeId == 'urn:mpeg:dash:urlparam:2014'; + }); + if (urlParamsElement) { + representationUrlParams = + this.getURLParametersFunction_(urlParamsElement); + } + } + + if (representationUrlParams) { + context.urlParams = representationUrlParams; + } + /** @type {?shaka.dash.DashParser.StreamInfo} */ let streamInfo; @@ -1999,8 +2104,9 @@ shaka.dash.DashParser = class { let aesKey = undefined; if (contentProtection.aes128Info) { const getBaseUris = context.representation.getBaseUris; + const urlParams = context.urlParams; const uris = shaka.util.ManifestParserUtils.resolveUris( - getBaseUris(), [contentProtection.aes128Info.keyUri]); + getBaseUris(), [contentProtection.aes128Info.keyUri], urlParams()); const requestType = shaka.net.NetworkingEngine.RequestType.KEY; const request = shaka.net.NetworkingEngine.makeRequest( uris, this.config_.retryParameters); @@ -2095,8 +2201,6 @@ shaka.dash.DashParser = class { // Detect the presence of E-AC3 JOC audio content, using DD+JOC signaling. // See: ETSI TS 103 420 V1.2.1 (2018-10) - const supplementalPropertyElems = - TXml.findChildren(node, 'SupplementalProperty'); const hasJoc = supplementalPropertyElems.some((element) => { const expectedUri = 'tag:dolby.com,2018:dash:EC3_ExtensionType:2018'; const expectedValue = 'JOC'; @@ -2118,8 +2222,6 @@ shaka.dash.DashParser = class { let tilesLayout; if (isImage) { - const essentialPropertyElems = - TXml.findChildren(node, 'EssentialProperty'); const thumbnailTileElem = essentialPropertyElems.find((element) => { const expectedUris = [ 'http://dashif.org/thumbnail_tile', @@ -2991,6 +3093,13 @@ shaka.dash.DashParser.PatchContext; shaka.dash.DashParser.SCTE214_ = 'urn:scte:dash:scte214-extensions'; +/** + * @const {string} + * @private + */ +shaka.dash.DashParser.UP_NAMESPACE_ = 'urn:mpeg:dash:schema:urlparam:2014'; + + /** * @typedef { * function(!Array., ?number, ?number, boolean): @@ -3089,7 +3198,8 @@ shaka.dash.DashParser.InheritanceFrame; * availabilityTimeOffset: number, * mediaPresentationDuration: ?number, * profiles: !Array., - * roles: ?Array. + * roles: ?Array., + * urlParams: function():string * }} * * @description @@ -3120,6 +3230,8 @@ shaka.dash.DashParser.InheritanceFrame; * of the use of features. * @property {?number} mediaPresentationDuration * Media presentation duration, or null if unknown. + * @property {function():string} urlParams + * The query params for the segments. */ shaka.dash.DashParser.Context; diff --git a/lib/dash/segment_base.js b/lib/dash/segment_base.js index 758ce88bba..4c252e9689 100644 --- a/lib/dash/segment_base.js +++ b/lib/dash/segment_base.js @@ -53,7 +53,7 @@ shaka.dash.SegmentBase = class { if (uri) { resolvedUris = ManifestParserUtils.resolveUris(resolvedUris, [ StringUtils.htmlUnescape(uri), - ]); + ], context.urlParams()); } let startByte = 0; @@ -266,7 +266,7 @@ shaka.dash.SegmentBase = class { StringUtils.htmlUnescape(representationIndex.attributes['sourceURL']); if (representationUri) { indexUris = ManifestParserUtils.resolveUris( - indexUris, [representationUri]); + indexUris, [representationUri], context.urlParams()); } } diff --git a/lib/dash/segment_list.js b/lib/dash/segment_list.js index 6ec8481d71..9fbb279049 100644 --- a/lib/dash/segment_list.js +++ b/lib/dash/segment_list.js @@ -62,7 +62,7 @@ shaka.dash.SegmentList = class { context.periodInfo.start, context.periodInfo.duration, info.startNumber, context.representation.getBaseUris, info, initSegmentReference, aesKey, context.representation.mimeType, - context.representation.codecs); + context.representation.codecs, context.urlParams); const isNew = !segmentIndex; if (segmentIndex) { @@ -203,12 +203,13 @@ shaka.dash.SegmentList = class { * @param {shaka.extern.aesKey|undefined} aesKey * @param {string} mimeType * @param {string} codecs + * @param {function():string} urlParams * @return {!Array.} * @private */ static createSegmentReferences_( periodStart, periodDuration, startNumber, getBaseUris, info, - initSegmentReference, aesKey, mimeType, codecs) { + initSegmentReference, aesKey, mimeType, codecs, urlParams) { const ManifestParserUtils = shaka.util.ManifestParserUtils; let max = info.mediaSegments.length; @@ -251,7 +252,7 @@ shaka.dash.SegmentList = class { const getUris = () => { if (uris == null) { uris = ManifestParserUtils.resolveUris( - getBaseUris(), [segment.mediaUri]); + getBaseUris(), [segment.mediaUri], urlParams()); } return uris; }; diff --git a/lib/dash/segment_template.js b/lib/dash/segment_template.js index 4b09a8f857..9e3a2f1ddd 100644 --- a/lib/dash/segment_template.js +++ b/lib/dash/segment_template.js @@ -145,6 +145,7 @@ shaka.dash.SegmentTemplate = class { context.representation.id, context.bandwidth, context.representation.getBaseUris, + context.urlParams, periodStart, periodEnd, initSegmentReference, @@ -412,6 +413,7 @@ shaka.dash.SegmentTemplate = class { const bandwidth = context.bandwidth || null; const id = context.representation.id; const getBaseUris = context.representation.getBaseUris; + const urlParams = context.urlParams; const timestampOffset = periodStart - info.scaledPresentationTimeOffset; @@ -523,7 +525,8 @@ shaka.dash.SegmentTemplate = class { } const mediaUri = MpdUtils.fillUriTemplate( template, id, position, subNumber, bandwidth, time); - return ManifestParserUtils.resolveUris(getBaseUris(), [mediaUri]); + return ManifestParserUtils.resolveUris( + getBaseUris(), [mediaUri], urlParams()); }; const partial = new shaka.media.SegmentReference( start, @@ -564,7 +567,8 @@ shaka.dash.SegmentTemplate = class { } const mediaUri = MpdUtils.fillUriTemplate( template, id, position, /* subNumber= */ null, bandwidth, time); - return ManifestParserUtils.resolveUris(getBaseUris(), [mediaUri]); + return ManifestParserUtils.resolveUris( + getBaseUris(), [mediaUri], urlParams()); }; const ref = new shaka.media.SegmentReference( @@ -674,12 +678,13 @@ shaka.dash.SegmentTemplate = class { const repId = context.representation.id; const bandwidth = context.bandwidth || null; const getBaseUris = context.representation.getBaseUris; + const urlParams = context.urlParams; const getUris = () => { goog.asserts.assert(initialization, 'Should have returned earler'); const filledTemplate = MpdUtils.fillUriTemplate( initialization, repId, null, null, bandwidth, null); const resolvedUris = ManifestParserUtils.resolveUris( - getBaseUris(), [filledTemplate]); + getBaseUris(), [filledTemplate], urlParams()); return resolvedUris; }; const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context); @@ -716,6 +721,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { * @param {?string} representationId * @param {number} bandwidth * @param {function():Array.} getBaseUris + * @param {function():string} urlParams * @param {number} periodStart * @param {number} periodEnd * @param {shaka.media.InitSegmentReference} initSegmentReference @@ -724,7 +730,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { * @param {number} segmentSequenceCadence */ constructor(templateInfo, representationId, bandwidth, getBaseUris, - periodStart, periodEnd, initSegmentReference, shouldFit, + urlParams, periodStart, periodEnd, initSegmentReference, shouldFit, aesKey, segmentSequenceCadence) { super([]); @@ -736,6 +742,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { this.bandwidth_ = bandwidth; /** @private {function():Array.} */ this.getBaseUris_ = getBaseUris; + /** @private {function():string} */ + this.urlParams_ = urlParams; /** @private {number} */ this.periodStart_ = periodStart; /** @private {number} */ @@ -1017,7 +1025,8 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { this.bandwidth_, timeReplacement, subNumber, - this.getBaseUris_); + this.getBaseUris_, + this.urlParams_); } return uris; }; @@ -1062,6 +1071,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { timeReplacement, /* subNumber= */ null, this.getBaseUris_, + this.urlParams_, ); }; @@ -1098,12 +1108,12 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { * @private */ static createUris_(mediaTemplate, repId, segmentReplacement, - bandwidth, timeReplacement, subNumber, getBaseUris) { + bandwidth, timeReplacement, subNumber, getBaseUris, urlParams) { const mediaUri = shaka.dash.MpdUtils.fillUriTemplate( mediaTemplate, repId, segmentReplacement, subNumber, bandwidth || null, timeReplacement); return shaka.util.ManifestParserUtils - .resolveUris(getBaseUris(), [mediaUri]) + .resolveUris(getBaseUris(), [mediaUri], urlParams()) .map((g) => { return g.toString(); }); diff --git a/lib/util/manifest_parser_utils.js b/lib/util/manifest_parser_utils.js index cc4829529d..c288b43596 100644 --- a/lib/util/manifest_parser_utils.js +++ b/lib/util/manifest_parser_utils.js @@ -26,9 +26,10 @@ shaka.util.ManifestParserUtils = class { * * @param {!Array.} baseUris * @param {!Array.} relativeUris + * @param {string=} extraQueryParams * @return {!Array.} */ - static resolveUris(baseUris, relativeUris) { + static resolveUris(baseUris, relativeUris, extraQueryParams = '') { if (relativeUris.length == 0) { return baseUris; } @@ -36,7 +37,11 @@ shaka.util.ManifestParserUtils = class { if (baseUris.length == 1 && relativeUris.length == 1) { const baseUri = new goog.Uri(baseUris[0]); const relativeUri = new goog.Uri(relativeUris[0]); - return [baseUri.resolve(relativeUri).toString()]; + const resolvedUri = baseUri.resolve(relativeUri); + if (extraQueryParams) { + resolvedUri.setQueryData(extraQueryParams); + } + return [resolvedUri.toString()]; } const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri)); @@ -47,7 +52,11 @@ shaka.util.ManifestParserUtils = class { for (const baseStr of baseUris) { const base = new goog.Uri(baseStr); for (const relative of relativeAsGoog) { - resolvedUris.push(base.resolve(relative).toString()); + const resolvedUri = base.resolve(relative); + if (extraQueryParams) { + resolvedUri.setQueryData(extraQueryParams); + } + resolvedUris.push(resolvedUri.toString()); } } diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index f6c57f0c94..3be8fff242 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -3266,4 +3266,256 @@ describe('DashParser Manifest', () => { expect(addFontSpy).toHaveBeenCalledWith('foo', 'dummy://foo/foo.woff'); expect(addFontSpy).toHaveBeenCalledWith('foo2', 'htpps://foo/foo2.woff'); }); + + // DASH: Annex I + describe('supports flexible insertion of URL parameters', () => { + it('with SegmentList', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/s1.mp4?a=1&b=1']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1&b=1']); + }); + + it('with SegmentTemplate', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/l-1.mp4?a=1']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1']); + }); + + it('with SupplementalProperty in AdaptationSet', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/l-1.mp4?a=1']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1']); + }); + + it('with EssentialProperty in AdaptationSet', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/l-1.mp4?a=1']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1']); + }); + + it('with SupplementalProperty in Representation', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/l-1.mp4?a=1&b=foo']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1&b=foo']); + }); + + it('with EssentialProperty in Representation', async () => { + const source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ].join('\n'); + + fakeNetEngine.setResponseText('dummy://foo?a=1', source); + /** @type {shaka.extern.Manifest} */ + const manifest = await parser.start('dummy://foo?a=1', playerInterface); + expect(manifest.variants.length).toBe(1); + + const variant1 = manifest.variants[0]; + + await variant1.video.createSegmentIndex(); + goog.asserts.assert(variant1.video.segmentIndex, 'Null segmentIndex!'); + + const variant1Ref = Array.from(variant1.video.segmentIndex)[0]; + + expect(variant1Ref.getUris()).toEqual(['dummy://foo/l-1.mp4?a=1']); + expect(variant1Ref.initSegmentReference.getUris()) + .toEqual(['dummy://foo/init.mp4?a=1']); + }); + }); });