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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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);
+ });
+
+ });
+
+
+});