First release
This commit is contained in:
commit
fa6c85266e
2339 changed files with 761050 additions and 0 deletions
339
node_modules/mpd-parser/src/toM3u8.js
generated
vendored
Normal file
339
node_modules/mpd-parser/src/toM3u8.js
generated
vendored
Normal file
|
@ -0,0 +1,339 @@
|
|||
import { values } from './utils/object';
|
||||
import { findIndexes } from './utils/list';
|
||||
import { addSidxSegmentsToPlaylist as addSidxSegmentsToPlaylist_ } from './segment/segmentBase';
|
||||
import { byteRangeToString } from './segment/urlType';
|
||||
|
||||
export 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 || '');
|
||||
|
||||
// Periods after first
|
||||
if (acc[name]) {
|
||||
// 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;
|
||||
}
|
||||
} else {
|
||||
// first Period
|
||||
acc[name] = playlist;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
return mergedPlaylists.map(playlist => {
|
||||
playlist.discontinuityStarts =
|
||||
findIndexes(playlist.segments, 'discontinuity');
|
||||
|
||||
return playlist;
|
||||
});
|
||||
};
|
||||
|
||||
export const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
|
||||
const sidxKey = generateSidxKey(playlist.sidx);
|
||||
const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
|
||||
|
||||
if (sidxMatch) {
|
||||
addSidxSegmentsToPlaylist_(playlist, sidxMatch, playlist.sidx.resolvedUri);
|
||||
}
|
||||
|
||||
return playlist;
|
||||
};
|
||||
|
||||
export const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
|
||||
if (!Object.keys(sidxMapping).length) {
|
||||
return playlists;
|
||||
}
|
||||
|
||||
for (const i in playlists) {
|
||||
playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
|
||||
}
|
||||
|
||||
return playlists;
|
||||
};
|
||||
|
||||
export const formatAudioPlaylist = ({ attributes, segments, sidx }, isAudioOnly) => {
|
||||
const playlist = {
|
||||
attributes: {
|
||||
NAME: attributes.id,
|
||||
BANDWIDTH: attributes.bandwidth,
|
||||
CODECS: attributes.codecs,
|
||||
['PROGRAM-ID']: 1
|
||||
},
|
||||
uri: '',
|
||||
endList: (attributes.type || 'static') === 'static',
|
||||
timeline: attributes.periodIndex,
|
||||
resolvedUri: '',
|
||||
targetDuration: attributes.duration,
|
||||
segments,
|
||||
mediaSequence: segments.length ? segments[0].number : 1
|
||||
};
|
||||
|
||||
if (attributes.contentProtection) {
|
||||
playlist.contentProtection = attributes.contentProtection;
|
||||
}
|
||||
|
||||
if (sidx) {
|
||||
playlist.sidx = sidx;
|
||||
}
|
||||
|
||||
if (isAudioOnly) {
|
||||
playlist.attributes.AUDIO = 'audio';
|
||||
playlist.attributes.SUBTITLES = 'subs';
|
||||
}
|
||||
|
||||
return playlist;
|
||||
};
|
||||
|
||||
export const formatVttPlaylist = ({ attributes, segments }) => {
|
||||
if (typeof segments === 'undefined') {
|
||||
// vtt tracks may use single file in BaseURL
|
||||
segments = [{
|
||||
uri: attributes.baseUrl,
|
||||
timeline: attributes.periodIndex,
|
||||
resolvedUri: attributes.baseUrl || '',
|
||||
duration: attributes.sourceDuration,
|
||||
number: 0
|
||||
}];
|
||||
// targetDuration should be the same duration as the only segment
|
||||
attributes.duration = attributes.sourceDuration;
|
||||
}
|
||||
|
||||
const m3u8Attributes = {
|
||||
NAME: attributes.id,
|
||||
BANDWIDTH: attributes.bandwidth,
|
||||
['PROGRAM-ID']: 1
|
||||
};
|
||||
|
||||
if (attributes.codecs) {
|
||||
m3u8Attributes.CODECS = attributes.codecs;
|
||||
}
|
||||
return {
|
||||
attributes: m3u8Attributes,
|
||||
uri: '',
|
||||
endList: (attributes.type || 'static') === 'static',
|
||||
timeline: attributes.periodIndex,
|
||||
resolvedUri: attributes.baseUrl || '',
|
||||
targetDuration: attributes.duration,
|
||||
segments,
|
||||
mediaSequence: segments.length ? segments[0].number : 1
|
||||
};
|
||||
};
|
||||
|
||||
export const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
|
||||
let mainPlaylist;
|
||||
|
||||
const formattedPlaylists = playlists.reduce((a, playlist) => {
|
||||
const role = playlist.attributes.role &&
|
||||
playlist.attributes.role.value || '';
|
||||
const language = playlist.attributes.lang || '';
|
||||
|
||||
let label = playlist.attributes.label || 'main';
|
||||
|
||||
if (language && !playlist.attributes.label) {
|
||||
const roleLabel = role ? ` (${role})` : '';
|
||||
|
||||
label = `${playlist.attributes.lang}${roleLabel}`;
|
||||
}
|
||||
|
||||
if (!a[label]) {
|
||||
a[label] = {
|
||||
language,
|
||||
autoselect: true,
|
||||
default: role === 'main',
|
||||
playlists: [],
|
||||
uri: ''
|
||||
};
|
||||
}
|
||||
|
||||
const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
|
||||
|
||||
a[label].playlists.push(formatted);
|
||||
|
||||
if (typeof mainPlaylist === 'undefined' && role === 'main') {
|
||||
mainPlaylist = playlist;
|
||||
mainPlaylist.default = true;
|
||||
}
|
||||
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
// if no playlists have role "main", mark the first as main
|
||||
if (!mainPlaylist) {
|
||||
const firstLabel = Object.keys(formattedPlaylists)[0];
|
||||
|
||||
formattedPlaylists[firstLabel].default = true;
|
||||
}
|
||||
|
||||
return formattedPlaylists;
|
||||
};
|
||||
|
||||
export const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
|
||||
return playlists.reduce((a, playlist) => {
|
||||
const label = playlist.attributes.lang || 'text';
|
||||
|
||||
if (!a[label]) {
|
||||
a[label] = {
|
||||
language: label,
|
||||
default: false,
|
||||
autoselect: false,
|
||||
playlists: [],
|
||||
uri: ''
|
||||
};
|
||||
}
|
||||
a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
|
||||
|
||||
return a;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const organizeCaptionServices = (captionServices) => captionServices.reduce((svcObj, svc) => {
|
||||
if (!svc) {
|
||||
return svcObj;
|
||||
}
|
||||
|
||||
svc.forEach((service) => {
|
||||
const {
|
||||
channel,
|
||||
language
|
||||
} = service;
|
||||
|
||||
svcObj[language] = {
|
||||
autoselect: false,
|
||||
default: false,
|
||||
instreamId: channel,
|
||||
language
|
||||
};
|
||||
|
||||
if (service.hasOwnProperty('aspectRatio')) {
|
||||
svcObj[language].aspectRatio = service.aspectRatio;
|
||||
}
|
||||
if (service.hasOwnProperty('easyReader')) {
|
||||
svcObj[language].easyReader = service.easyReader;
|
||||
}
|
||||
if (service.hasOwnProperty('3D')) {
|
||||
svcObj[language]['3D'] = service['3D'];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return svcObj;
|
||||
}, {});
|
||||
|
||||
export const formatVideoPlaylist = ({ attributes, segments, sidx }) => {
|
||||
const playlist = {
|
||||
attributes: {
|
||||
NAME: attributes.id,
|
||||
AUDIO: 'audio',
|
||||
SUBTITLES: 'subs',
|
||||
RESOLUTION: {
|
||||
width: attributes.width,
|
||||
height: attributes.height
|
||||
},
|
||||
CODECS: attributes.codecs,
|
||||
BANDWIDTH: attributes.bandwidth,
|
||||
['PROGRAM-ID']: 1
|
||||
},
|
||||
uri: '',
|
||||
endList: (attributes.type || 'static') === 'static',
|
||||
timeline: attributes.periodIndex,
|
||||
resolvedUri: '',
|
||||
targetDuration: attributes.duration,
|
||||
segments,
|
||||
mediaSequence: segments.length ? segments[0].number : 1
|
||||
};
|
||||
|
||||
if (attributes.contentProtection) {
|
||||
playlist.contentProtection = attributes.contentProtection;
|
||||
}
|
||||
|
||||
if (sidx) {
|
||||
playlist.sidx = sidx;
|
||||
}
|
||||
|
||||
return playlist;
|
||||
};
|
||||
|
||||
const videoOnly = ({ attributes }) =>
|
||||
attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
|
||||
const audioOnly = ({ attributes }) =>
|
||||
attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
|
||||
const vttOnly = ({ attributes }) =>
|
||||
attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
|
||||
|
||||
export const toM3u8 = (dashPlaylists, locations, sidxMapping = {}) => {
|
||||
if (!dashPlaylists.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// grab all master attributes
|
||||
const {
|
||||
sourceDuration: duration,
|
||||
type = 'static',
|
||||
suggestedPresentationDelay,
|
||||
minimumUpdatePeriod
|
||||
} = dashPlaylists[0].attributes;
|
||||
|
||||
const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
|
||||
const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
|
||||
const vttPlaylists = dashPlaylists.filter(vttOnly);
|
||||
const captions = dashPlaylists.map((playlist) => playlist.attributes.captionServices).filter(Boolean);
|
||||
|
||||
const master = {
|
||||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
segments: [],
|
||||
endList: true,
|
||||
mediaGroups: {
|
||||
AUDIO: {},
|
||||
VIDEO: {},
|
||||
['CLOSED-CAPTIONS']: {},
|
||||
SUBTITLES: {}
|
||||
},
|
||||
uri: '',
|
||||
duration,
|
||||
playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
|
||||
};
|
||||
|
||||
if (minimumUpdatePeriod >= 0) {
|
||||
master.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
|
||||
}
|
||||
|
||||
if (locations) {
|
||||
master.locations = locations;
|
||||
}
|
||||
|
||||
if (type === 'dynamic') {
|
||||
master.suggestedPresentationDelay = suggestedPresentationDelay;
|
||||
}
|
||||
|
||||
const isAudioOnly = master.playlists.length === 0;
|
||||
|
||||
if (audioPlaylists.length) {
|
||||
master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly);
|
||||
}
|
||||
|
||||
if (vttPlaylists.length) {
|
||||
master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
|
||||
}
|
||||
|
||||
if (captions.length) {
|
||||
master.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
|
||||
}
|
||||
|
||||
return master;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue