You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
6.0 KiB
182 lines
6.0 KiB
import resolveUrl from '@videojs/vhs-utils/es/resolve-url';
|
|
import urlTypeToSegment from './urlType';
|
|
import { parseByTimeline } from './timelineTimeParser';
|
|
import { parseByDuration } from './durationTimeParser';
|
|
|
|
const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
|
|
|
|
/**
|
|
* Replaces template identifiers with corresponding values. To be used as the callback
|
|
* for String.prototype.replace
|
|
*
|
|
* @name replaceCallback
|
|
* @function
|
|
* @param {string} match
|
|
* Entire match of identifier
|
|
* @param {string} identifier
|
|
* Name of matched identifier
|
|
* @param {string} format
|
|
* Format tag string. Its presence indicates that padding is expected
|
|
* @param {string} width
|
|
* Desired length of the replaced value. Values less than this width shall be left
|
|
* zero padded
|
|
* @return {string}
|
|
* Replacement for the matched identifier
|
|
*/
|
|
|
|
/**
|
|
* Returns a function to be used as a callback for String.prototype.replace to replace
|
|
* template identifiers
|
|
*
|
|
* @param {Obect} values
|
|
* Object containing values that shall be used to replace known identifiers
|
|
* @param {number} values.RepresentationID
|
|
* Value of the Representation@id attribute
|
|
* @param {number} values.Number
|
|
* Number of the corresponding segment
|
|
* @param {number} values.Bandwidth
|
|
* Value of the Representation@bandwidth attribute.
|
|
* @param {number} values.Time
|
|
* Timestamp value of the corresponding segment
|
|
* @return {replaceCallback}
|
|
* Callback to be used with String.prototype.replace to replace identifiers
|
|
*/
|
|
export const identifierReplacement = (values) => (match, identifier, format, width) => {
|
|
if (match === '$$') {
|
|
// escape sequence
|
|
return '$';
|
|
}
|
|
|
|
if (typeof values[identifier] === 'undefined') {
|
|
return match;
|
|
}
|
|
|
|
const value = '' + values[identifier];
|
|
|
|
if (identifier === 'RepresentationID') {
|
|
// Format tag shall not be present with RepresentationID
|
|
return value;
|
|
}
|
|
|
|
if (!format) {
|
|
width = 1;
|
|
} else {
|
|
width = parseInt(width, 10);
|
|
}
|
|
|
|
if (value.length >= width) {
|
|
return value;
|
|
}
|
|
|
|
return `${(new Array(width - value.length + 1)).join('0')}${value}`;
|
|
};
|
|
|
|
/**
|
|
* Constructs a segment url from a template string
|
|
*
|
|
* @param {string} url
|
|
* Template string to construct url from
|
|
* @param {Obect} values
|
|
* Object containing values that shall be used to replace known identifiers
|
|
* @param {number} values.RepresentationID
|
|
* Value of the Representation@id attribute
|
|
* @param {number} values.Number
|
|
* Number of the corresponding segment
|
|
* @param {number} values.Bandwidth
|
|
* Value of the Representation@bandwidth attribute.
|
|
* @param {number} values.Time
|
|
* Timestamp value of the corresponding segment
|
|
* @return {string}
|
|
* Segment url with identifiers replaced
|
|
*/
|
|
export const constructTemplateUrl = (url, values) =>
|
|
url.replace(identifierPattern, identifierReplacement(values));
|
|
|
|
/**
|
|
* Generates a list of objects containing timing and duration information about each
|
|
* segment needed to generate segment uris and the complete segment object
|
|
*
|
|
* @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 {{number: number, duration: number, time: number, timeline: number}[]}
|
|
* List of Objects with segment timing and duration info
|
|
*/
|
|
export const parseTemplateInfo = (attributes, segmentTimeline) => {
|
|
if (!attributes.duration && !segmentTimeline) {
|
|
// if neither @duration or SegmentTimeline are present, then there shall be exactly
|
|
// one media segment
|
|
return [{
|
|
number: attributes.startNumber || 1,
|
|
duration: attributes.sourceDuration,
|
|
time: 0,
|
|
timeline: attributes.periodStart
|
|
}];
|
|
}
|
|
|
|
if (attributes.duration) {
|
|
return parseByDuration(attributes);
|
|
}
|
|
|
|
return parseByTimeline(attributes, segmentTimeline);
|
|
};
|
|
|
|
/**
|
|
* Generates a list of segments using information provided by the SegmentTemplate element
|
|
*
|
|
* @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[]}
|
|
* List of segment objects
|
|
*/
|
|
export const segmentsFromTemplate = (attributes, segmentTimeline) => {
|
|
const templateValues = {
|
|
RepresentationID: attributes.id,
|
|
Bandwidth: attributes.bandwidth || 0
|
|
};
|
|
|
|
const { initialization = { sourceURL: '', range: '' } } = attributes;
|
|
|
|
const mapSegment = urlTypeToSegment({
|
|
baseUrl: attributes.baseUrl,
|
|
source: constructTemplateUrl(initialization.sourceURL, templateValues),
|
|
range: initialization.range
|
|
});
|
|
|
|
const segments = parseTemplateInfo(attributes, segmentTimeline);
|
|
|
|
return segments.map(segment => {
|
|
templateValues.Number = segment.number;
|
|
templateValues.Time = segment.time;
|
|
|
|
const uri = constructTemplateUrl(attributes.media || '', templateValues);
|
|
// See DASH spec section 5.3.9.2.2
|
|
// - if timescale isn't present on any level, default to 1.
|
|
const timescale = attributes.timescale || 1;
|
|
// - if presentationTimeOffset isn't present on any level, default to 0
|
|
const presentationTimeOffset = attributes.presentationTimeOffset || 0;
|
|
const presentationTime =
|
|
// Even if the @t attribute is not specified for the segment, segment.time is
|
|
// calculated in mpd-parser prior to this, so it's assumed to be available.
|
|
attributes.periodStart + ((segment.time - presentationTimeOffset) / timescale);
|
|
|
|
const map = {
|
|
uri,
|
|
timeline: segment.timeline,
|
|
duration: segment.duration,
|
|
resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
|
|
map: mapSegment,
|
|
number: segment.number,
|
|
presentationTime
|
|
};
|
|
|
|
return map;
|
|
});
|
|
};
|
|
|