Skip to content

Commit

Permalink
feat(DASH): Support Annex I: Flexible Insertion of URL Parameters (#7086
Browse files Browse the repository at this point in the history
)

Resolves #6472
  • Loading branch information
avelad authored Jul 23, 2024
1 parent 9337143 commit a5adb39
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 33 deletions.
148 changes: 130 additions & 18 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -176,6 +177,9 @@ shaka.dash.DashParser = class {

/** @private {boolean} */
this.isTransitionFromDynamicToStatic_ = false;

/** @private {string} */
this.lastManifestQueryParams_ = '';
}

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -731,6 +738,7 @@ shaka.dash.DashParser = class {
mediaPresentationDuration: null,
profiles: profiles.split(','),
roles: null,
urlParams: () => '',
};

this.gapCount_ = 0;
Expand Down Expand Up @@ -1011,6 +1019,7 @@ shaka.dash.DashParser = class {
mediaPresentationDuration:
this.manifestPatchContext_.mediaPresentationDuration,
roles: null,
urlParams: () => '',
};

const periodsAndDuration = this.parsePeriods_(context,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
}
};
Expand All @@ -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'];
Expand All @@ -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 =
Expand All @@ -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();
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -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';
Expand All @@ -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',
Expand Down Expand Up @@ -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.<string>, ?number, ?number, boolean):
Expand Down Expand Up @@ -3089,7 +3198,8 @@ shaka.dash.DashParser.InheritanceFrame;
* availabilityTimeOffset: number,
* mediaPresentationDuration: ?number,
* profiles: !Array.<string>,
* roles: ?Array.<string>
* roles: ?Array.<string>,
* urlParams: function():string
* }}
*
* @description
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions lib/dash/segment_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ shaka.dash.SegmentBase = class {
if (uri) {
resolvedUris = ManifestParserUtils.resolveUris(resolvedUris, [
StringUtils.htmlUnescape(uri),
]);
], context.urlParams());
}

let startByte = 0;
Expand Down Expand Up @@ -266,7 +266,7 @@ shaka.dash.SegmentBase = class {
StringUtils.htmlUnescape(representationIndex.attributes['sourceURL']);
if (representationUri) {
indexUris = ManifestParserUtils.resolveUris(
indexUris, [representationUri]);
indexUris, [representationUri], context.urlParams());
}
}

Expand Down
7 changes: 4 additions & 3 deletions lib/dash/segment_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.<!shaka.media.SegmentReference>}
* @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;
Expand Down Expand Up @@ -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;
};
Expand Down
Loading

0 comments on commit a5adb39

Please sign in to comment.