Skip to content

Commit

Permalink
Parse <SegmentList> and <SegmentBase> (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
OshinKaramian authored and forbesjo committed Feb 5, 2018
1 parent 7dad5d5 commit 71b8976
Show file tree
Hide file tree
Showing 20 changed files with 1,549 additions and 168 deletions.
4 changes: 3 additions & 1 deletion src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export default {
INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
DASH_INVALID_XML: 'DASH_INVALID_XML',
UNSUPPORTED_SEGMENTATION_TYPE: 'UNSUPPORTED_SEGMENTATION_TYPE'
NO_BASE_URL: 'NO_BASE_URL',
MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED'
};
45 changes: 38 additions & 7 deletions src/inheritAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { flatten } from './utils/list';
import { shallowMerge, getAttributes } from './utils/object';
import { parseDuration } from './utils/time';
import { findChildren, getContent } from './utils/xml';
import resolveUrl from './resolveUrl';
import resolveUrl from './utils/resolveUrl';
import errors from './errors';

/**
Expand Down Expand Up @@ -50,17 +50,48 @@ export const buildBaseUrls = (referenceUrls, baseUrlElements) => {
*/
export const getSegmentInformation = (adaptationSet) => {
const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
const segmentTimeline =
segmentTemplate && findChildren(segmentTemplate, 'SegmentTimeline')[0];
const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL')
.map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s)));
const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
const segmentTimelineParentNode = segmentList || segmentTemplate;
const segmentTimeline = segmentTimelineParentNode &&
findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
const segmentInitialization = segmentInitializationParentNode &&
findChildren(segmentInitializationParentNode, 'Initialization')[0];

// SegmentTemplate is handled slightly differently, since it can have both
// @initialization and an <Initialization> node. @initialization can be templated,
// while the node can have a url and range specified. If the <SegmentTemplate> has
// both @initialization and an <Initialization> subelement we opt to override with
// the node, as this interaction is not defined in the spec.
const template = segmentTemplate && getAttributes(segmentTemplate);

if (template && segmentInitialization) {
template.initialization =
(segmentInitialization && getAttributes(segmentInitialization));
} else if (template && template.initialization) {
// If it is @initialization we convert it to an object since this is the format that
// later functions will rely on for the initialization segment. This is only valid
// for <SegmentTemplate>
template.initialization = { sourceURL: template.initialization };
}

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

Expand Down
64 changes: 64 additions & 0 deletions src/segment/segmentBase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import errors from '../errors';
import urlTypeConverter from './urlType';
import { parseByDuration } from './timeParser';

/**
* Translates SegmentBase into a set of segments.
* (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @return {Object.<Array>} list of segments
*/
export const segmentsFromBase = (attributes) => {
const {
baseUrl,
initialization = {},
sourceDuration,
timescale = 1,
startNumber = 1,
periodIndex = 0,
indexRange = '',
duration
} = attributes;

// base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
if (!baseUrl) {
throw new Error(errors.NO_BASE_URL);
}

const initSegment = urlTypeConverter({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeConverter({ baseUrl, source: baseUrl, range: indexRange });

segment.map = initSegment;

const parsedTimescale = parseInt(timescale, 10);

// If there is a duration, use it, otherwise use the given duration of the source
// (since SegmentBase is only for one total segment)
if (duration) {
const parsedDuration = parseInt(duration, 10);
const start = parseInt(startNumber, 10);
const segmentTimeInfo = parseByDuration(start,
periodIndex,
parsedTimescale,
parsedDuration,
sourceDuration);

if (segmentTimeInfo.length >= 1) {
segment.duration = segmentTimeInfo[0].duration;
segment.timeline = segmentTimeInfo[0].timeline;
}
} else if (sourceDuration) {
segment.duration = (sourceDuration / parsedTimescale);
segment.timeline = 0;
}

return [segment];
};
103 changes: 103 additions & 0 deletions src/segment/segmentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { parseByDuration, parseByTimeline } from './timeParser';
import urlTypeConverter from './urlType';
import errors from '../errors';

/**
* Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
* to an object that matches the output of a segment in videojs/mpd-parser
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object} segmentUrl
* <SegmentURL> node to translate into a segment object
* @return {Object} translated segment object
*/
const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
const { baseUrl, initialization = {} } = attributes;

const initSegment = urlTypeConverter({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});

const segment = urlTypeConverter({
baseUrl,
source: segmentUrl.media,
range: segmentUrl.mediaRange
});

segment.map = initSegment;

return segment;
};

/**
* Generates a list of segments using information provided by the SegmentList element
* SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object.<Array>} list of segments
*/
export const segmentsFromList = (attributes, segmentTimeline) => {
const {
timescale = 1,
duration,
segmentUrls = [],
periodIndex = 0,
startNumber = 1
} = attributes;

// Per spec (5.3.9.2.1) no way to determine segment duration OR
// if both SegmentTimeline and @duration are defined, it is outside of spec.
if ((!duration && !segmentTimeline) ||
(duration && segmentTimeline)) {
throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
}

const parsedTimescale = parseInt(timescale, 10);
const start = parseInt(startNumber, 10);
const segmentUrlMap = segmentUrls.map(segmentUrlObject =>
SegmentURLToSegmentObject(attributes, segmentUrlObject));
let segmentTimeInfo;

if (duration) {
const parsedDuration = parseInt(duration, 10);

segmentTimeInfo = parseByDuration(start,
periodIndex,
parsedTimescale,
parsedDuration,
attributes.sourceDuration);
}

if (segmentTimeline) {
segmentTimeInfo = parseByTimeline(start,
periodIndex,
parsedTimescale,
segmentTimeline,
attributes.sourceDuration);
}

const segments = segmentTimeInfo.map((segmentTime, index) => {
if (segmentUrlMap[index]) {
const segment = segmentUrlMap[index];

segment.timeline = segmentTime.timeline;
segment.duration = segmentTime.duration;
return segment;
}
// Since we're mapping we should get rid of any blank segments (in case
// the given SegmentTimeline is handling for more elements than we have
// SegmentURLs for).
}).filter(segment => segment);

return segments;
};
Loading

0 comments on commit 71b8976

Please sign in to comment.