Skip to content

Commit

Permalink
Support for inheriting BaseURL and alternate BaseURLs (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjneil authored and forbesjo committed Jan 23, 2018
1 parent 87933f6 commit 7dad5d5
Show file tree
Hide file tree
Showing 4 changed files with 600 additions and 63 deletions.
232 changes: 184 additions & 48 deletions src/inheritAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,211 @@ import { findChildren, getContent } from './utils/xml';
import resolveUrl from './resolveUrl';
import errors from './errors';

export const rep = mpdAttributes => (period, periodIndex) => {
const adaptationSets = findChildren(period, 'AdaptationSet');

const representationsByAdaptationSet = adaptationSets.map(adaptationSet => {
const adaptationSetAttributes = getAttributes(adaptationSet);

const role = findChildren(adaptationSet, 'Role')[0];
const roleAttributes = { role: getAttributes(role) };

const attrs = shallowMerge({ periodIndex },
mpdAttributes,
adaptationSetAttributes,
roleAttributes);

const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
const segmentTimeline =
segmentTemplate && findChildren(segmentTemplate, 'SegmentTimeline')[0];
const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];

const segmentInfo = {
template: segmentTemplate && getAttributes(segmentTemplate),
timeline: segmentTimeline &&
findChildren(segmentTimeline, 'S').map(s => getAttributes(s)),
list: segmentList && getAttributes(segmentList),
base: segmentBase && getAttributes(segmentBase)
};
/**
* Builds a list of urls that is the product of the reference urls and BaseURL values
*
* @param {string[]} referenceUrls
* List of reference urls to resolve to
* @param {Node[]} baseUrlElements
* List of BaseURL nodes from the mpd
* @return {string[]}
* List of resolved urls
*/
export const buildBaseUrls = (referenceUrls, baseUrlElements) => {
if (!baseUrlElements.length) {
return referenceUrls;
}

const representations = findChildren(adaptationSet, 'Representation');
return flatten(
referenceUrls.map(
reference => baseUrlElements.map(
baseUrlElement => resolveUrl(reference, getContent(baseUrlElement)))));
};

const inherit = representation => {
// vtt tracks may use single file in BaseURL
const baseUrlElement = findChildren(representation, 'BaseURL')[0];
const baseUrl = baseUrlElement ? getContent(baseUrlElement) : '';
const attributes = shallowMerge(attrs,
getAttributes(representation),
{ url: baseUrl });
/**
* Contains all Segment information for its containing AdaptationSet
*
* @typedef {Object} SegmentInformation
* @property {Object|undefined} template
* Contains the attributes for the SegmentTemplate node
* @property {Object[]|undefined} timeline
* Contains a list of atrributes for each S node within the SegmentTimeline node
* @property {Object|undefined} list
* Contains the attributes for the SegmentList node
* @property {Object|undefined} base
* Contains the attributes for the SegmentBase node
*/

/**
* Returns all available Segment information contained within the AdaptationSet node
*
* @param {Node} adaptationSet
* The AdaptationSet node to get Segment information from
* @return {SegmentInformation}
* The Segment information contained within the provided AdaptationSet
*/
export const getSegmentInformation = (adaptationSet) => {
const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
const segmentTimeline =
segmentTemplate && findChildren(segmentTemplate, 'SegmentTimeline')[0];
const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];

return {
template: segmentTemplate && getAttributes(segmentTemplate),
timeline: segmentTimeline &&
findChildren(segmentTimeline, 'S').map(s => getAttributes(s)),
list: segmentList && getAttributes(segmentList),
base: segmentBase && getAttributes(segmentBase)
};
};

return { attributes, segmentInfo };
/**
* Contains Segment information and attributes needed to construct a Playlist object
* from a Representation
*
* @typedef {Object} RepresentationInformation
* @property {SegmentInformation} segmentInfo
* Segment information for this Representation
* @property {Object} attributes
* Inherited attributes for this Representation
*/

/**
* Maps a Representation node to an object containing Segment information and attributes
*
* @name inheritBaseUrlsCallback
* @function
* @param {Node} representation
* Representation node from the mpd
* @return {RepresentationInformation}
* Representation information needed to construct a Playlist object
*/

/**
* Returns a callback for Array.prototype.map for mapping Representation nodes to
* Segment information and attributes using inherited BaseURL nodes.
*
* @param {Object} adaptationSetAttributes
* Contains attributes inherited by the AdaptationSet
* @param {string[]} adaptationSetBaseUrls
* Contains list of resolved base urls inherited by the AdaptationSet
* @param {SegmentInformation} segmentInfo
* Contains Segment information for the AdaptationSet
* @return {inheritBaseUrlsCallback}
* Callback map function
*/
export const inheritBaseUrls =
(adaptationSetAttributes, adaptationSetBaseUrls, segmentInfo) => (representation) => {
const repBaseUrlElements = findChildren(representation, 'BaseURL');
const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
const attributes = shallowMerge(adaptationSetAttributes, getAttributes(representation));

return repBaseUrls.map(baseUrl => {
return {
segmentInfo,
attributes: shallowMerge(attributes, { baseUrl })
};

return representations.map(inherit);
});
};

return flatten(representationsByAdaptationSet);
/**
* Maps an AdaptationSet node to a list of Representation information objects
*
* @name toRepresentationsCallback
* @function
* @param {Node} adaptationSet
* AdaptationSet node from the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/

/**
* Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
* Representation information objects
*
* @param {Object} periodAttributes
* Contains attributes inherited by the Period
* @param {string[]} periodBaseUrls
* Contains list of resolved base urls inherited by the Period
* @return {toRepresentationsCallback}
* Callback map function
*/
export const toRepresentations =
(periodAttributes, periodBaseUrls) => (adaptationSet) => {
const adaptationSetAttributes = getAttributes(adaptationSet);
const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls,
findChildren(adaptationSet, 'BaseURL'));
const role = findChildren(adaptationSet, 'Role')[0];
const roleAttributes = { role: getAttributes(role) };
const attrs = shallowMerge(periodAttributes,
adaptationSetAttributes,
roleAttributes);
const segmentInfo = getSegmentInformation(adaptationSet);
const representations = findChildren(adaptationSet, 'Representation');

return flatten(
representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, segmentInfo)));
};

export const representationsByPeriod = (periods, mpdAttributes) => {
return periods.map(rep(mpdAttributes));
/**
* Maps an Period node to a list of Representation inforamtion objects for all
* AdaptationSet nodes contained within the Period
*
* @name toAdaptationSetsCallback
* @function
* @param {Node} period
* Period node from the mpd
* @param {number} periodIndex
* Index of the Period within the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/

/**
* Returns a callback for Array.prototype.map for mapping Period nodes to a list of
* Representation information objects
*
* @param {Object} mpdAttributes
* Contains attributes inherited by the mpd
* @param {string[]} mpdBaseUrls
* Contains list of resolved base urls inherited by the mpd
* @return {toAdaptationSetsCallback}
* Callback map function
*/
export const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, periodIndex) => {
const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
const periodAttributes = shallowMerge({ periodIndex }, mpdAttributes);
const adaptationSets = findChildren(period, 'AdaptationSet');

return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls)));
};

/**
* Traverses the mpd xml tree to generate a list of Representation information objects
* that have inherited attributes from parent nodes
*
* @param {Node} mpd
* The root node of the mpd
* @param {string} manifestUri
* The uri of the source mpd
* @return {RepresentationInformation[]}
* List of objects containing Representation information
*/
export const inheritAttributes = (mpd, manifestUri = '') => {
const periods = findChildren(mpd, 'Period');

if (!periods.length ||
periods.length &&
periods.length !== 1) {
if (periods.length !== 1) {
// TODO add support for multiperiod
throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
}

const mpdAttributes = getAttributes(mpd);
const baseUrlElement = findChildren(mpd, 'BaseURL')[0];
const baseUrl = baseUrlElement ? getContent(baseUrlElement) : '';
const mpdBaseUrls = buildBaseUrls([ manifestUri ], findChildren(mpd, 'BaseURL'));

mpdAttributes.baseUrl = resolveUrl(manifestUri, baseUrl);
mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration ?
parseDuration(mpdAttributes.mediaPresentationDuration) : 0;

return flatten(representationsByPeriod(periods, mpdAttributes));
return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
};

7 changes: 4 additions & 3 deletions src/toM3u8.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export const formatAudioPlaylist = ({ attributes, segments }) => {

export const formatVttPlaylist = ({ attributes, segments }) => {
if (typeof segments === 'undefined') {
// vtt tracks may use single file in BaseURL
segments = [{
uri: attributes.url,
uri: attributes.baseUrl,
timeline: attributes.periodIndex,
resolvedUri: attributes.url || '',
resolvedUri: attributes.baseUrl || '',
duration: attributes.sourceDuration
}];
}
Expand All @@ -32,7 +33,7 @@ export const formatVttPlaylist = ({ attributes, segments }) => {
uri: '',
endList: true,
timeline: attributes.periodIndex,
resolvedUri: attributes.url || '',
resolvedUri: attributes.baseUrl || '',
segments
};
};
Expand Down
Loading

0 comments on commit 7dad5d5

Please sign in to comment.