diff --git a/samples/advanced/ext-url-query-info.html b/samples/advanced/ext-url-query-info.html new file mode 100644 index 0000000000..1492c09bbf --- /dev/null +++ b/samples/advanced/ext-url-query-info.html @@ -0,0 +1,194 @@ + + + + + + Flexible Insertion of URL Parameters Sample + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+

Flexible Insertion of URL Parameters Sample

+

This sample demonstrates the Flexible Insertion of URL Parameters in + dash.js.

+
+
+
+
+
+ + + + + + +
+
+
+
+ +
+
+
+
+
+
Manifest Content:
+

+                
+
+
+
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/samples/samples.json b/samples/samples.json index ba01c7af92..3c8a44999b 100644 --- a/samples/samples.json +++ b/samples/samples.json @@ -708,6 +708,17 @@ "Audio" ] }, + { + "title": "Flexible Insertion of URL Parameters Sample", + "description": "This sample demonstrates the Flexible Insertion of URL Parameters in dash.js.", + "href": "advanced/ext-url-query-info.html", + "image": "lib/img/bbb-1.jpg", + "labels": [ + "VoD", + "Video", + "Audio" + ] + }, { "title": "Custom Capabilities Filters", "description": "This sample shows how to filter representations.", diff --git a/src/core/Settings.js b/src/core/Settings.js index f74b2ed8fa..d4d6ced573 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -76,6 +76,8 @@ import Events from './events/Events.js'; * supportedEssentialProperties: [ * { schemeIdUri: Constants.FONT_DOWNLOAD_DVB_SCHEME }, * { schemeIdUri: Constants.COLOUR_PRIMARIES_SCHEME_ID_URI, value: /1|5|6|7/ }, + * { schemeIdUri: Constants.URL_QUERY_INFO_SCHEME }, + * { schemeIdUri: Constants.EXT_URL_QUERY_INFO_SCHEME }, * { schemeIdUri: Constants.MATRIX_COEFFICIENTS_SCHEME_ID_URI, value: /0|1|5|6/ }, * { schemeIdUri: Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI, value: /1|6|13|14|15/ }, * ...Constants.THUMBNAILS_SCHEME_ID_URIS.map(ep => { return { 'schemeIdUri': ep }; }) @@ -1059,6 +1061,8 @@ function Settings() { supportedEssentialProperties: [ { schemeIdUri: Constants.FONT_DOWNLOAD_DVB_SCHEME }, { schemeIdUri: Constants.COLOUR_PRIMARIES_SCHEME_ID_URI, value: /1|5|6|7/ }, + { schemeIdUri: Constants.URL_QUERY_INFO_SCHEME }, + { schemeIdUri: Constants.EXT_URL_QUERY_INFO_SCHEME }, { schemeIdUri: Constants.MATRIX_COEFFICIENTS_SCHEME_ID_URI, value: /0|1|5|6/ }, { schemeIdUri: Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI, value: /1|6|13|14|15/ }, ...Constants.THUMBNAILS_SCHEME_ID_URIS.map(ep => { return { 'schemeIdUri': ep }; }) diff --git a/src/core/Utils.js b/src/core/Utils.js index 61e75645e4..4b2b08b728 100644 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -117,6 +117,20 @@ class Utils { return headers; } + /** + * Parses query parameters from a string and returns them as an array of key-value pairs. + * @param {string} queryParamString - A string containing the query parameters. + * @return {Array<{key: string, value: string}>} An array of objects representing the query parameters. + */ + static parseQueryParams(queryParamString) { + const params = []; + const searchParams = new URLSearchParams(queryParamString); + for (const [key, value] of searchParams.entries()) { + params.push({ key: decodeURIComponent(key), value: decodeURIComponent(value) }); + } + return params; + } + static generateUuid() { let dt = new Date().getTime(); const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { diff --git a/src/dash/constants/DashConstants.js b/src/dash/constants/DashConstants.js index 347e33f2c3..2fb0a8b152 100644 --- a/src/dash/constants/DashConstants.js +++ b/src/dash/constants/DashConstants.js @@ -120,6 +120,8 @@ export default { MIN_BUFFER_TIME: 'minBufferTime', MP4_PROTECTION_SCHEME: 'urn:mpeg:dash:mp4protection:2011', MPD: 'MPD', + MPD_TYPE: 'mpd', + MPD_PATCH_TYPE: 'mpdpatch', ORIGINAL_MPD_ID: 'mpdId', ORIGINAL_PUBLISH_TIME: 'originalPublishTime', PATCH_LOCATION: 'PatchLocation', @@ -138,6 +140,7 @@ export default { PUBLISH_TIME: 'publishTime', QUALITY_RANKING : 'qualityRanking', QUERY_BEFORE_START: 'queryBeforeStart', + QUERY_PART: '$querypart$', RANGE: 'range', RATING: 'Rating', REF: 'ref', @@ -158,6 +161,7 @@ export default { SEGMENT_PROFILES: 'segmentProfiles', SEGMENT_TEMPLATE: 'SegmentTemplate', SEGMENT_TIMELINE: 'SegmentTimeline', + SEGMENT_TYPE: 'segment', SEGMENT_URL: 'SegmentURL', SERVICE_DESCRIPTION: 'ServiceDescription', SERVICE_DESCRIPTION_LATENCY: 'Latency', @@ -172,6 +176,7 @@ export default { START_NUMBER: 'startNumber', START_WITH_SAP: 'startWithSAP', STATIC: 'static', + STEERING_TYPE: 'steering', SUBSET: 'Subset', SUBTITLE: 'subtitle', SUB_REPRESENTATION: 'SubRepresentation', diff --git a/src/streaming/constants/Constants.js b/src/streaming/constants/Constants.js index 5ba4ba19e9..a95bc2d6c0 100644 --- a/src/streaming/constants/Constants.js +++ b/src/streaming/constants/Constants.js @@ -246,6 +246,8 @@ export default { THUMBNAILS_SCHEME_ID_URIS: ['http://dashif.org/thumbnail_tile', 'http://dashif.org/guidelines/thumbnail_tile'], FONT_DOWNLOAD_DVB_SCHEME: 'urn:dvb:dash:fontdownload:2014', COLOUR_PRIMARIES_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:ColourPrimaries', + URL_QUERY_INFO_SCHEME: 'urn:mpeg:dash:urlparam:2014', + EXT_URL_QUERY_INFO_SCHEME: 'urn:mpeg:dash:urlparam:2016', MATRIX_COEFFICIENTS_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:MatrixCoefficients', TRANSFER_CHARACTERISTICS_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:TransferCharacteristics', HDR_METADATA_FORMAT_SCHEME_ID_URI: 'urn:dvb:dash:hdr-dmi', diff --git a/src/streaming/controllers/ExtUrlQueryInfoController.js b/src/streaming/controllers/ExtUrlQueryInfoController.js new file mode 100644 index 0000000000..dca56ee6c2 --- /dev/null +++ b/src/streaming/controllers/ExtUrlQueryInfoController.js @@ -0,0 +1,185 @@ +/** + * The copyright in this software is being made available under the BSD License, + * included below. This software may be subject to other third party and contributor + * rights, including patent rights, and no such rights are granted under this license. + * + * Copyright (c) 2013, Dash Industry Forum. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Dash Industry Forum nor the names of its + * contributors may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import FactoryMaker from '../../core/FactoryMaker.js'; +import Utils from '../../core/Utils.js'; +import DashConstants from '../../dash/constants/DashConstants.js'; +import Constants from '../constants/Constants.js'; +import { HTTPRequest } from '../vo/metrics/HTTPRequest.js'; + +function ExtUrlQueryInfoController() { + let instance, + mpdQueryStringInformation; + + + function _generateQueryParams(resultObject, manifestObject, mpdUrlQuery, parentLevelInfo, level) { + const property = _getPropertyFromManifestObject(manifestObject, level); + _generateInitialQueryString(property, parentLevelInfo.initialQueryString, resultObject, mpdUrlQuery); + _generateFinalQueryString(property, resultObject, parentLevelInfo.finalQueryString); + resultObject.sameOriginOnly = property?.ExtUrlQueryInfo?.sameOriginOnly; + resultObject.queryParams = Utils.parseQueryParams(resultObject?.finalQueryString); + resultObject.includeInRequests = _getIncludeInRequestFromProperty(property, parentLevelInfo.includeInRequests); + } + + function _getPropertyFromManifestObject(manifestObject, level) { + let properties = []; + if (level === DashConstants.PERIOD) { + properties = manifestObject[DashConstants.SUPPLEMENTAL_PROPERTY] || []; + } else { + properties = [ + ...(manifestObject[DashConstants.ESSENTIAL_PROPERTY] || []), + ...(manifestObject[DashConstants.SUPPLEMENTAL_PROPERTY] || []) + ]; + } + return properties.filter((prop) => ( + (prop.schemeIdUri === Constants.URL_QUERY_INFO_SCHEME && prop.UrlQueryInfo) || + (prop.schemeIdUri === Constants.EXT_URL_QUERY_INFO_SCHEME && prop.ExtUrlQueryInfo) + ))[0]; + } + + function _generateInitialQueryString(property, defaultInitialString, dst, mpdUrlQuery) { + dst.initialQueryString = ''; + let initialQueryString = ''; + + const queryInfo = property?.ExtUrlQueryInfo || property?.UrlQueryInfo; + + if (queryInfo && queryInfo.queryString) { + if (defaultInitialString && defaultInitialString.length > 0) { + initialQueryString = defaultInitialString + '&' + queryInfo.queryString; + } else { + initialQueryString = queryInfo.queryString; + } + } else { + initialQueryString = defaultInitialString; + } + if (queryInfo?.useMPDUrlQuery === 'true' && mpdUrlQuery) { + initialQueryString = initialQueryString ? initialQueryString + '&' + mpdUrlQuery : mpdUrlQuery; + } + dst.initialQueryString = initialQueryString; + } + + // The logic for supporting templates with queryTemplate=$query:$ is not in place yet, this only support queryTemplate="$querypart$". + function _generateFinalQueryString(property, resultObject, parentQueryString) { + if (!property) { + resultObject.finalQueryString = parentQueryString; + return; + } + const queryTemplate = property?.ExtUrlQueryInfo?.queryTemplate || property?.UrlQueryInfo?.queryTemplate || ''; + resultObject.finalQueryString = queryTemplate === DashConstants.QUERY_PART ? resultObject?.initialQueryString : ''; + } + + function _getIncludeInRequestFromProperty(property , parentIncludeInRequests) { + if (!property) { + return parentIncludeInRequests; + } + + if (property.ExtUrlQueryInfo?.includeInRequests) { + return property.ExtUrlQueryInfo.includeInRequests.split(' '); + } else { + return [DashConstants.SEGMENT_TYPE]; + } + } + + function createFinalQueryStrings(manifest) { + mpdQueryStringInformation = { + origin: new URL(manifest.url).origin, + period: [] + }; + + const mpdUrlQuery = manifest.url.split('?')[1]; + const initialMpdObject = {initialQueryString: '', includeInRequests: []}; + + _generateQueryParams(mpdQueryStringInformation, manifest, mpdUrlQuery, initialMpdObject, DashConstants.MPD); + + manifest.Period.forEach((period) => { + const periodObject = { + adaptation: [] + }; + _generateQueryParams(periodObject, period, mpdUrlQuery, mpdQueryStringInformation, DashConstants.PERIOD); + + period.AdaptationSet.forEach((adaptationSet) => { + const adaptationObject = { + representation: [] + }; + _generateQueryParams(adaptationObject, adaptationSet, mpdUrlQuery, periodObject, DashConstants.ADAPTATION_SET); + + adaptationSet.Representation.forEach((representation) => { + const representationObject = {}; + _generateQueryParams(representationObject, representation, mpdUrlQuery, adaptationObject, DashConstants.REPRESENTATION); + + adaptationObject.representation.push(representationObject); + }); + periodObject.adaptation.push(adaptationObject); + }); + mpdQueryStringInformation.period.push(periodObject); + }); + } + + function getFinalQueryString(request) { + if (!mpdQueryStringInformation) { + return + } + if (request.type === HTTPRequest.MEDIA_SEGMENT_TYPE || request.type === HTTPRequest.INIT_SEGMENT_TYPE) { + const representation = request.representation; + const adaptation = representation.adaptation; + const period = adaptation.period; + const queryInfo = mpdQueryStringInformation + .period[period.index] + .adaptation[adaptation.index] + .representation[representation.index]; + const requestUrl = new URL(request.url); + const canSendToOrigin = !queryInfo.sameOriginOnly || mpdQueryStringInformation.origin === requestUrl.origin; + const inRequest = queryInfo.includeInRequests.includes(DashConstants.SEGMENT_TYPE); + if (inRequest && canSendToOrigin) { + return queryInfo.queryParams; + } + } else if (request.type === HTTPRequest.MPD_TYPE) { + const inRequest = [DashConstants.MPD_TYPE, DashConstants.MPD_PATCH_TYPE].some(r => mpdQueryStringInformation.includeInRequests.includes(r)); + if (inRequest) { + return mpdQueryStringInformation.queryParams; + } + } else if (request.type === HTTPRequest.CONTENT_STEERING_TYPE) { + const inRequest = mpdQueryStringInformation.includeInRequests.includes(DashConstants.STEERING_TYPE); + if (inRequest) { + return mpdQueryStringInformation.queryParams; + } + } + } + + instance = { + getFinalQueryString, + createFinalQueryStrings + }; + return instance; +} + +ExtUrlQueryInfoController.__dashjs_factory_name = 'ExtUrlQueryInfoController'; +export default FactoryMaker.getSingletonFactory(ExtUrlQueryInfoController); diff --git a/src/streaming/controllers/StreamController.js b/src/streaming/controllers/StreamController.js index c2c6cd5303..c664f0876b 100644 --- a/src/streaming/controllers/StreamController.js +++ b/src/streaming/controllers/StreamController.js @@ -45,6 +45,7 @@ import DashJSError from '../vo/DashJSError.js'; import Errors from '../../core/errors/Errors.js'; import EventController from './EventController.js'; import ConformanceViolationConstants from '../constants/ConformanceViolationConstants.js'; +import ExtUrlQueryInfoController from './ExtUrlQueryInfoController.js'; import ProtectionEvents from '../protection/ProtectionEvents.js'; import ProtectionErrors from '../protection/errors/ProtectionErrors.js'; @@ -60,7 +61,7 @@ function StreamController() { dashMetrics, mediaSourceController, timeSyncController, contentSteeringController, baseURLController, segmentBaseController, uriFragmentModel, abrController, throughputController, mediaController, eventController, initCache, errHandler, timelineConverter, streams, activeStream, protectionController, textController, - protectionData, + protectionData, extUrlQueryInfoController, autoPlay, isStreamSwitchingInProgress, hasMediaError, hasInitialisationError, mediaSource, videoModel, playbackController, serviceDescriptionController, mediaPlayerModel, customParametersModel, isPaused, initialPlayback, initialSteeringRequest, playbackEndedTimerInterval, bufferSinks, preloadingStreams, settings, @@ -99,6 +100,7 @@ function StreamController() { }); eventController.start(); + extUrlQueryInfoController = ExtUrlQueryInfoController(context).getInstance(); timeSyncController.setConfig({ dashMetrics, baseURLController, errHandler, settings @@ -1282,6 +1284,8 @@ function StreamController() { }); } + extUrlQueryInfoController.createFinalQueryStrings(manifest); + let allUTCTimingSources = (!adapter.getIsDynamic()) ? manifestUTCTimingSources : manifestUTCTimingSources.concat(customParametersModel.getUTCTimingSources()); timeSyncController.attemptSync(allUTCTimingSources, adapter.getIsDynamic()); }); diff --git a/src/streaming/net/HTTPLoader.js b/src/streaming/net/HTTPLoader.js index e535bf814c..e69a0823d8 100644 --- a/src/streaming/net/HTTPLoader.js +++ b/src/streaming/net/HTTPLoader.js @@ -44,6 +44,7 @@ import Constants from '../constants/Constants.js'; import CustomParametersModel from '../models/CustomParametersModel.js'; import CommonAccessTokenController from '../controllers/CommonAccessTokenController.js'; import ClientDataReportingController from '../controllers/ClientDataReportingController.js'; +import ExtUrlQueryInfoController from '../controllers/ExtUrlQueryInfoController.js'; /** * @module HTTPLoader @@ -77,6 +78,7 @@ function HTTPLoader(cfg) { customParametersModel, commonAccessTokenController, clientDataReportingController, + extUrlQueryInfoController, logger; function setup() { @@ -89,6 +91,7 @@ function HTTPLoader(cfg) { cmsdModel = CmsdModel(context).getInstance(); customParametersModel = CustomParametersModel(context).getInstance(); commonAccessTokenController = CommonAccessTokenController(context).getInstance(); + extUrlQueryInfoController = ExtUrlQueryInfoController(context).getInstance(); downloadErrorToRequestTypeMap = { [HTTPRequest.MPD_TYPE]: errors.DOWNLOAD_ERROR_ID_MANIFEST_CODE, @@ -109,6 +112,10 @@ function HTTPLoader(cfg) { if (config.commonAccessTokenController) { commonAccessTokenController = config.commonAccessTokenController } + + if (config.extUrlQueryInfoController) { + extUrlQueryInfoController = config.extUrlQueryInfoController; + } } /** @@ -574,10 +581,19 @@ function HTTPLoader(cfg) { */ function _updateRequestUrlAndHeaders(request) { _updateRequestUrlAndHeadersWithCmcd(request); + _addExtUrlQueryParameters(request); _addPathwayCloningParameters(request); _addCommonAccessToken(request); } + function _addExtUrlQueryParameters(request) { + // Add ExtUrlQueryInfo parameters + let finalQueryString = extUrlQueryInfoController.getFinalQueryString(request); + if (finalQueryString) { + request.url = Utils.addAdditionalQueryParameterToUrl(request.url, finalQueryString); + } + } + function _addPathwayCloningParameters(request) { // Add queryParams that came from pathway cloning if (request.queryParams) { diff --git a/test/unit/test/streaming/streaming.controllers.ExtUrlQueryInfoController.js b/test/unit/test/streaming/streaming.controllers.ExtUrlQueryInfoController.js new file mode 100644 index 0000000000..091860ac88 --- /dev/null +++ b/test/unit/test/streaming/streaming.controllers.ExtUrlQueryInfoController.js @@ -0,0 +1,525 @@ +import {expect} from 'chai'; +import ExtUrlQueryInfoController from '../../../../src/streaming/controllers/ExtUrlQueryInfoController.js'; + +describe('ExtUrlQueryInfoController', () => { + + let extUrlQueryInfoController; + + before(() => { + extUrlQueryInfoController = ExtUrlQueryInfoController({}).getInstance(); + }); + + describe('complete manifest tests', () => { + + let manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + queryString: 'qsAdapSetParam=qsAdapSetValue', + } + }], + Representation: [ + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + } + }],}, + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + } + }],}, + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2016', + ExtUrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + sameOriginOnly: 'true', + } + }],}, + ] + }, + { + Representation: [ + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + } + }],}, + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + } + }],}, + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + queryString: 'qsRepParam=qsRepValue' + } + }],}, + {EssentialProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + queryString: 'qsRepParam=qsRepValue' + } + }],}, + ] + }, + ], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + } + }], + }], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2016', + ExtUrlQueryInfo: { + tagName: 'ExtUrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + includeInRequests:'mpd mpdpatch', + } + }], + }; + + beforeEach(() => { + extUrlQueryInfoController.createFinalQueryStrings(manifest); + }); + + + it('should return request query parameters when representation does not have a queryString', () => { + const request = { + url: 'http://manifesturl.com/rep-0/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 0, + adaptation: { + index: 1, + period: { + index: 0 + } + } + } + }; + + const expectedResult = [{key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + console.log(result) + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return an empty array when representation has no queryString and useMPDUrlQuery is false', () => { + const request = { + url: 'http://manifesturl.com/rep-1/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 1, + adaptation: { + index: 1, + period: { + index: 0 + } + } + } + }; + + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return queryString and request query parameters when representation has a queryString set', () => { + const request = { + url: 'http://manifesturl.com/rep-2/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 2, + adaptation: { + index: 1, + period: { + index: 0 + } + } + } + }; + const expectedResult = [{key: 'qsRepParam', value: 'qsRepValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return only queryString when representation has queryString set and useMPDUrlQuery is false', () => { + const request = { + url: 'http://manifesturl.com/rep-3/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 3, + adaptation: { + index: 1, + period: { + index: 0 + } + } + } + }; + const expectedResult = [{key: 'qsRepParam', value: 'qsRepValue'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should inherit queryString from adaptationSet property', () => { + const request = { + url: 'http://manifesturl.com/rep-0/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 0, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + const expectedResult = [{key: 'qsAdapSetParam', value: 'qsAdapSetValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should duplicate url query parameters when useMPDUrlQuery is true in representation and in adaptationSet', () => { + const request = { + url: 'http://manifesturl.com/rep-1/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 1, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + const expectedResult = [{key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}, {key: 'qsAdapSetParam', value: 'qsAdapSetValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return query parameters when sameOriginOnly is enabled and origin matches', () => { + const request = { + url: 'http://manifesturl.com/rep-2/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 2, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + + const expectedResult = [{key: 'qsAdapSetParam', value: 'qsAdapSetValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return undefined when sameOriginOnly is enabled and origin does not match', () => { + const request = { + url: 'http://othermanifesturl.com/rep-2/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 2, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + + const result = extUrlQueryInfoController.getFinalQueryString(request); + console.log(result) + expect(result).to.be.undefined; + }); + + }); + + + + describe('period supplemental propperty only', () => { + + it('should inherit queryString and url parameters from Period property', () => { + + let manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + queryString: 'qsPerParam=qsPerValue', + } + }], + }], + }; + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/rep-0/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 0, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + + const expectedResult = [{key: 'qsPerParam', value: 'qsPerValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should inherit queryString from Period property', () => { + + let manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + queryString: 'qsPerParam=qsPerValue', + } + }], + }], + }; + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/rep-0/seg-1.m4f', + type: 'MediaSegment', + representation: { + index: 0, + adaptation: { + index: 0, + period: { + index: 0 + } + } + } + }; + + const expectedResult = [{key: 'qsPerParam', value: 'qsPerValue'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return undefined for MPD request when no MPD supplemental property is configured', () => { + let manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2014', + UrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + queryString: 'qsPerParam=qsPerValue', + } + }], + }], + }; + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/Manifest.mpd', + type: 'MPD', + }; + + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.be.undefined; + }); + + + + }); + + describe('mpd supplemental propperty only', () => { + + it('should return empty array for MPD or MPD patch request when useMPDUrlQuery is false and no queryString is configured', () => { + + const manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + }], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2016', + ExtUrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'false', + includeInRequests: 'mpd', + } + }], + }; + + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/Manifest.mpd', + type: 'MPD', + }; + + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return request query parameters for MPD or MPD patch request when useMPDUrlQuery is true and no queryString is configured', () => { + const manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + }], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2016', + ExtUrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + includeInRequests: 'mpd', + } + }], + }; + + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/Manifest.mpd', + type: 'MPD', + }; + + const expectedResult = [{key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + it('should return queryString and request query parameters for MPD or MPD patch request when useMPDUrlQuery is true and queryString is configured', () => { + + const manifest = { + url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1&urlParam2=urlValue2', + Period : [{ + AdaptationSet: [ + { + Representation: [{},{}] + }, + { + Representation: [{},{}] + }, + ], + }], + SupplementalProperty: [{ + schemeIdUri: 'urn:mpeg:dash:urlparam:2016', + ExtUrlQueryInfo: { + tagName: 'UrlQueryInfo', + queryTemplate: '$querypart$', + useMPDUrlQuery: 'true', + queryString: 'qsMpdParam=qsMpdValue', + includeInRequests: 'mpd', + } + }], + }; + + extUrlQueryInfoController.createFinalQueryStrings(manifest); + + const request = { + url: 'http://manifesturl.com/Manifest.mpd', + type: 'MPD', + }; + + const expectedResult = [{key: 'qsMpdParam' , value: 'qsMpdValue'}, {key: 'urlParam1' , value: 'urlValue1'}, {key: 'urlParam2' , value: 'urlValue2'}]; + const result = extUrlQueryInfoController.getFinalQueryString(request); + expect(result).to.have.deep.members(expectedResult); + }); + + }); + + +});