1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-04 18:29:39 +02:00
Daniel Neto 2023-10-25 10:14:46 -03:00
parent b6d47e94c8
commit 65f15c7e46
2882 changed files with 382239 additions and 10785 deletions

View file

@ -1,11 +1,11 @@
/*! @name mpd-parser @version 1.1.1 @license Apache-2.0 */
/*! @name mpd-parser @version 1.2.2 @license Apache-2.0 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@xmldom/xmldom')) :
typeof define === 'function' && define.amd ? define(['exports', '@xmldom/xmldom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.mpdParser = {}, global.window));
}(this, (function (exports, xmldom) { 'use strict';
var version = "1.1.1";
var version = "1.2.2";
const isObject = obj => {
return !!obj && typeof obj === 'object';
@ -81,6 +81,7 @@
var errors = {
INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING',
DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
DASH_INVALID_XML: 'DASH_INVALID_XML',
NO_BASE_URL: 'NO_BASE_URL',
@ -987,43 +988,56 @@
const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
const mergeDiscontiguousPlaylists = playlists => {
const mergedPlaylists = values(playlists.reduce((acc, playlist) => {
// assuming playlist IDs are the same across periods
// TODO: handle multiperiod where representation sets are not the same
// across periods
const name = playlist.attributes.id + (playlist.attributes.lang || '');
if (!acc[name]) {
// First Period
acc[name] = playlist;
acc[name].attributes.timelineStarts = [];
} else {
// Subsequent Periods
if (playlist.segments) {
// first segment of subsequent periods signal a discontinuity
if (playlist.segments[0]) {
playlist.segments[0].discontinuity = true;
}
acc[name].segments.push(...playlist.segments);
} // bubble up contentProtection, this assumes all DRM content
// has the same contentProtection
if (playlist.attributes.contentProtection) {
acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
}
// Break out playlists into groups based on their baseUrl
const playlistsByBaseUrl = playlists.reduce(function (acc, cur) {
if (!acc[cur.attributes.baseUrl]) {
acc[cur.attributes.baseUrl] = [];
}
acc[name].attributes.timelineStarts.push({
// Although they represent the same number, it's important to have both to make it
// compatible with HLS potentially having a similar attribute.
start: playlist.attributes.periodStart,
timeline: playlist.attributes.periodStart
});
acc[cur.attributes.baseUrl].push(cur);
return acc;
}, {}));
return mergedPlaylists.map(playlist => {
}, {});
let allPlaylists = [];
Object.values(playlistsByBaseUrl).forEach(playlistGroup => {
const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => {
// assuming playlist IDs are the same across periods
// TODO: handle multiperiod where representation sets are not the same
// across periods
const name = playlist.attributes.id + (playlist.attributes.lang || '');
if (!acc[name]) {
// First Period
acc[name] = playlist;
acc[name].attributes.timelineStarts = [];
} else {
// Subsequent Periods
if (playlist.segments) {
// first segment of subsequent periods signal a discontinuity
if (playlist.segments[0]) {
playlist.segments[0].discontinuity = true;
}
acc[name].segments.push(...playlist.segments);
} // bubble up contentProtection, this assumes all DRM content
// has the same contentProtection
if (playlist.attributes.contentProtection) {
acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
}
}
acc[name].attributes.timelineStarts.push({
// Although they represent the same number, it's important to have both to make it
// compatible with HLS potentially having a similar attribute.
start: playlist.attributes.periodStart,
timeline: playlist.attributes.periodStart
});
return acc;
}, {}));
allPlaylists = allPlaylists.concat(mergedPlaylists);
});
return allPlaylists.map(playlist => {
playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
return playlist;
});
@ -1068,7 +1082,7 @@
uri: '',
endList: attributes.type === 'static',
timeline: attributes.periodStart,
resolvedUri: '',
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
discontinuitySequence,
discontinuityStarts,
@ -1081,6 +1095,10 @@
playlist.contentProtection = attributes.contentProtection;
}
if (attributes.serviceLocation) {
playlist.attributes.serviceLocation = attributes.serviceLocation;
}
if (sidx) {
playlist.sidx = sidx;
}
@ -1122,7 +1140,7 @@
m3u8Attributes.CODECS = attributes.codecs;
}
return {
const vttPlaylist = {
attributes: m3u8Attributes,
uri: '',
endList: attributes.type === 'static',
@ -1135,6 +1153,12 @@
mediaSequence,
segments
};
if (attributes.serviceLocation) {
vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
}
return vttPlaylist;
};
const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
let mainPlaylist;
@ -1249,7 +1273,7 @@
uri: '',
endList: attributes.type === 'static',
timeline: attributes.periodStart,
resolvedUri: '',
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
discontinuityStarts,
timelineStarts: attributes.timelineStarts,
@ -1264,6 +1288,10 @@
playlist.contentProtection = attributes.contentProtection;
}
if (attributes.serviceLocation) {
playlist.attributes.serviceLocation = attributes.serviceLocation;
}
if (sidx) {
playlist.sidx = sidx;
}
@ -1356,6 +1384,7 @@
const toM3u8 = ({
dashPlaylists,
locations,
contentSteering,
sidxMapping = {},
previousManifest,
eventStream
@ -1399,6 +1428,10 @@
manifest.locations = locations;
}
if (contentSteering) {
manifest.contentSteering = contentSteering;
}
if (type === 'dynamic') {
manifest.suggestedPresentationDelay = suggestedPresentationDelay;
}
@ -2248,22 +2281,33 @@
/**
* 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 {Object[]} references
* List of objects containing the reference URL as well as its attributes
* @param {Node[]} baseUrlElements
* List of BaseURL nodes from the mpd
* @return {string[]}
* List of resolved urls
* @return {Object[]}
* List of objects with resolved urls and attributes
*/
const buildBaseUrls = (referenceUrls, baseUrlElements) => {
const buildBaseUrls = (references, baseUrlElements) => {
if (!baseUrlElements.length) {
return referenceUrls;
return references;
}
return flatten(referenceUrls.map(function (reference) {
return flatten(references.map(function (reference) {
return baseUrlElements.map(function (baseUrlElement) {
return resolveUrl(reference, getContent(baseUrlElement));
const initialBaseUrl = getContent(baseUrlElement);
const resolvedBaseUrl = resolveUrl(reference.baseUrl, initialBaseUrl);
const finalBaseUrl = merge(parseAttributes(baseUrlElement), {
baseUrl: resolvedBaseUrl
}); // If the URL is resolved, we want to get the serviceLocation from the reference
// assuming there is no serviceLocation on the initialBaseUrl
if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
finalBaseUrl.serviceLocation = reference.serviceLocation;
}
return finalBaseUrl;
});
}));
};
@ -2365,8 +2409,9 @@
*
* @param {Object} adaptationSetAttributes
* Contains attributes inherited by the AdaptationSet
* @param {string[]} adaptationSetBaseUrls
* Contains list of resolved base urls inherited by the AdaptationSet
* @param {Object[]} adaptationSetBaseUrls
* List of objects containing resolved base URLs and attributes
* inherited by the AdaptationSet
* @param {SegmentInformation} adaptationSetSegmentInfo
* Contains Segment information for the AdaptationSet
* @return {inheritBaseUrlsCallback}
@ -2381,9 +2426,7 @@
return repBaseUrls.map(baseUrl => {
return {
segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
attributes: merge(attributes, {
baseUrl
})
attributes: merge(attributes, baseUrl)
};
});
};
@ -2550,8 +2593,9 @@
*
* @param {Object} periodAttributes
* Contains attributes inherited by the Period
* @param {string[]} periodBaseUrls
* Contains list of resolved base urls inherited by the Period
* @param {Object[]} periodBaseUrls
* Contains list of objects with resolved base urls and attributes
* inherited by the Period
* @param {string[]} periodSegmentInfo
* Contains Segment Information at the period level
* @return {toRepresentationsCallback}
@ -2627,8 +2671,9 @@
*
* @param {Object} mpdAttributes
* Contains attributes inherited by the mpd
* @param {string[]} mpdBaseUrls
* Contains list of resolved base urls inherited by the mpd
* @param {Object[]} mpdBaseUrls
* Contains list of objects with resolved base urls and attributes
* inherited by the mpd
* @return {toAdaptationSetsCallback}
* Callback map function
*/
@ -2647,6 +2692,43 @@
const periodSegmentInfo = getSegmentInformation(period.node);
return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
};
/**
* Tranforms an array of content steering nodes into an object
* containing CDN content steering information from the MPD manifest.
*
* For more information on the DASH spec for Content Steering parsing, see:
* https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
*
* @param {Node[]} contentSteeringNodes
* Content steering nodes
* @param {Function} eventHandler
* The event handler passed into the parser options to handle warnings
* @return {Object}
* Object containing content steering data
*/
const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => {
// If there are more than one ContentSteering tags, throw an error
if (contentSteeringNodes.length > 1) {
eventHandler({
type: 'warn',
message: 'The MPD manifest should contain no more than one ContentSteering tag'
});
} // Return a null value if there are no ContentSteering tags
if (!contentSteeringNodes.length) {
return null;
}
const infoFromContentSteeringTag = merge({
serverURL: getContent(contentSteeringNodes[0])
}, parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value
// to `false` if it doesn't exist
infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true';
return infoFromContentSteeringTag;
};
/**
* Gets Period@start property for a given period.
*
@ -2726,7 +2808,14 @@
const {
manifestUri = '',
NOW = Date.now(),
clientOffset = 0
clientOffset = 0,
// TODO: For now, we are expecting an eventHandler callback function
// to be passed into the mpd parser as an option.
// In the future, we should enable stream parsing by using the Stream class from vhs-utils.
// This will support new features including a standardized event handler.
// See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing.
// https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9
eventHandler = function () {}
} = options;
const periodNodes = findChildren(mpd, 'Period');
@ -2736,7 +2825,10 @@
const locations = findChildren(mpd, 'Location');
const mpdAttributes = parseAttributes(mpd);
const mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
const mpdBaseUrls = buildBaseUrls([{
baseUrl: manifestUri
}], findChildren(mpd, 'BaseURL'));
const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
mpdAttributes.type = mpdAttributes.type || 'static';
mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
@ -2769,6 +2861,14 @@
});
return {
locations: mpdAttributes.locations,
contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler),
// TODO: There are occurences where this `representationInfo` array contains undesired
// duplicates. This generally occurs when there are multiple BaseURL nodes that are
// direct children of the MPD node. When we attempt to resolve URLs from a combination of the
// parent BaseURL and a child BaseURL, and the value does not resolve,
// we end up returning the child BaseURL multiple times.
// We need to determine a way to remove these duplicates in a safe way.
// See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527
representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))),
eventStream: flatten(periods.map(toEventStream))
};
@ -2786,7 +2886,7 @@
try {
xml = parser.parseFromString(manifestString, 'application/xml');
mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
} catch (e) {// ie 11 throwsw on invalid xml
} catch (e) {// ie 11 throws on invalid xml
}
if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
@ -2864,6 +2964,7 @@
return toM3u8({
dashPlaylists: playlists,
locations: parsedManifestInfo.locations,
contentSteering: parsedManifestInfo.contentSteeringInfo,
sidxMapping: options.sidxMapping,
previousManifest: options.previousManifest,
eventStream: parsedManifestInfo.eventStream