Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DASH): Support Annex I: Flexible Insertion of URL Parameters #7086

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 126 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,44 @@ 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) {
if (queryTemplate == '$querypart$') {
return () => {
return this.lastManifestQueryParams_;
};
} else {
const regex = /\$query:(.*?)\$/g;
const parts = regex.exec(queryTemplate);
if (parts && parts.length == 2) {
const paramName = parts[1];
return () => {
const queryData =
new goog.Uri.QueryData(this.lastManifestQueryParams_);
const value = queryData.get(paramName);
if (value.length) {
return paramName + '=' + value[0];
}
return '';
};
}
}
}
}
return null;
}

/**
* Parses a Representation XML element.
*
Expand Down Expand Up @@ -1986,6 +2059,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 +2100,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 +2197,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 +2218,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 +3089,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 +3194,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 +3226,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
Loading