Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sidx information to segment base playlists #41

Merged
merged 20 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mpd-parser",
"version": "0.7.0",
"version": "0.8.0-0",
gkatsev marked this conversation as resolved.
Show resolved Hide resolved
"description": "mpd parser",
"main": "dist/mpd-parser.cjs.js",
"module": "dist/mpd-parser.es.js",
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { parseUTCTimingScheme } from './parseUTCTimingScheme';

export const VERSION = version;

export const parse = (manifestString, options) =>
toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)));
export const parse = (manifestString, options = {}) =>
toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping);

/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
Expand Down
58 changes: 57 additions & 1 deletion src/segment/segmentBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export const segmentsFromBase = (attributes) => {
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeConverter({ baseUrl, source: baseUrl, range: indexRange });

const segment = urlTypeConverter({ baseUrl, source: baseUrl, indexRange });

segment.map = initSegment;

Expand All @@ -55,3 +56,58 @@ export const segmentsFromBase = (attributes) => {

return [segment];
};

export const addSegmentsToPlaylist = (playlist, sidx, baseUrl) => {
gkatsev marked this conversation as resolved.
Show resolved Hide resolved
// Retain init segment information
const initSegment = playlist.sidx.map ? playlist.sidx.map : null;
gkatsev marked this conversation as resolved.
Show resolved Hide resolved
// Retain source duration from initial master manifest parsing
const sourceDuration = playlist.sidx.duration;
// Retain source timeline
const timeline = playlist.timeline || 0;
const sidxByteRange = playlist.sidx.byterange;
const sidxEnd = sidxByteRange.offset + sidxByteRange.length;
// Retain timescale of the parsed sidx
const timescale = sidx.timescale;
// referenceType 1 refers to other sidx boxes
const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
const segments = [];

// firstOffset is the offset from the end of the sidx box
let startIndex = sidxEnd + sidx.firstOffset;

for (let i = 0; i < mediaReferences.length; i++) {
const reference = sidx.references[i];
// size of the referenced (sub)segment
const size = reference.referencedSize;
// duration of the referenced (sub)segment, in the timescale
// this will be converted to seconds when generating segments
const duration = reference.subsegmentDuration;
// should be an inclusive range
const endIndex = startIndex + size - 1;
const indexRange = `${startIndex}-${endIndex}`;

const attributes = {
baseUrl,
timescale,
timeline,
// this is used in parseByDuration
periodIndex: timeline,
duration,
sourceDuration,
indexRange
};

const segment = segmentsFromBase(attributes)[0];

if (initSegment) {
segment.map = initSegment;
}

segments.push(segment);
startIndex += size;
}

playlist.segments = segments;

return playlist;
};
21 changes: 15 additions & 6 deletions src/segment/urlType.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,35 @@ import resolveUrl from '../utils/resolveUrl';
* @return {SingleUri} full segment information transformed into a format similar
* to m3u8-parser
*/
export const urlTypeToSegment = ({ baseUrl = '', source = '', range = '' }) => {
const init = {
export const urlTypeToSegment = ({ baseUrl = '', source = '', range = '', indexRange = '' }) => {
const segment = {
uri: source,
resolvedUri: resolveUrl(baseUrl || '', source)
};

if (range) {
const ranges = range.split('-');
if (range || indexRange) {
const rangeStr = range ? range : indexRange;
const ranges = rangeStr.split('-');
const startRange = parseInt(ranges[0], 10);
const endRange = parseInt(ranges[1], 10);

// byterange should be inclusive according to
// RFC 2616, Clause 14.35.1
init.byterange = {
segment.byterange = {
length: endRange - startRange + 1,
offset: startRange
};
}

return init;
return segment;
};

export const byteRangeToString = (byterange) => {
// `endRange` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
const endRange = byterange.offset + byterange.length - 1;

return `${byterange.offset}-${endRange}`;
};

export default urlTypeToSegment;
83 changes: 69 additions & 14 deletions src/toM3u8.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { values } from './utils/object';
import { findIndexes } from './utils/list';
import { addSegmentsToPlaylist } from './segment/segmentBase';
import { byteRangeToString } from './segment/urlType';

const mergeDiscontiguousPlaylists = playlists => {
const mergedPlaylists = values(playlists.reduce((acc, playlist) => {
Expand All @@ -11,7 +13,9 @@ const mergeDiscontiguousPlaylists = playlists => {
// Periods after first
if (acc[name]) {
// first segment of subsequent periods signal a discontinuity
playlist.segments[0].discontinuity = true;
if (playlist.segments[0]) {
playlist.segments[0].discontinuity = true;
}
acc[name].segments.push(...playlist.segments);

// bubble up contentProtection, this assumes all DRM content
Expand All @@ -36,7 +40,26 @@ const mergeDiscontiguousPlaylists = playlists => {
});
};

export const formatAudioPlaylist = ({ attributes, segments }) => {
const addSegmentInfoFromSidx = (playlists, sidxMapping) => {
if (!Object.keys(sidxMapping).length) {
return playlists;
}

for (const i in playlists) {
const playlist = playlists[i];
const sidxKey = playlist.sidx.uri + '-' +
byteRangeToString(playlist.sidx.byterange);
const sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;

if (playlist.sidx && sidxMatch) {
addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
}
}

return playlists;
};

export const formatAudioPlaylist = ({ attributes, segments, sidx }) => {
const playlist = {
attributes: {
NAME: attributes.id,
Expand All @@ -57,6 +80,10 @@ export const formatAudioPlaylist = ({ attributes, segments }) => {
playlist.contentProtection = attributes.contentProtection;
}

if (sidx) {
playlist.sidx = sidx;
}

return playlist;
};

Expand Down Expand Up @@ -89,16 +116,20 @@ export const formatVttPlaylist = ({ attributes, segments }) => {
};
};

export const organizeAudioPlaylists = playlists => {
return playlists.reduce((a, playlist) => {
export const organizeAudioPlaylists = (playlists, sidxMapping) => {
brandonocasey marked this conversation as resolved.
Show resolved Hide resolved
let mainPlaylist;

const formattedPlaylists = playlists.reduce((a, playlist) => {
const role = playlist.attributes.role &&
playlist.attributes.role.value || 'main';
playlist.attributes.role.value || '';
const language = playlist.attributes.lang || '';

let label = 'main';

if (language) {
label = `${playlist.attributes.lang} (${role})`;
const roleLabel = role ? ` (${role})` : '';

label = `${playlist.attributes.lang}${roleLabel}`;
}

// skip if we already have the highest quality audio for a language
Expand All @@ -112,15 +143,32 @@ export const organizeAudioPlaylists = playlists => {
language,
autoselect: true,
default: role === 'main',
playlists: [formatAudioPlaylist(playlist)],
playlists: addSegmentInfoFromSidx(
[formatAudioPlaylist(playlist)],
sidxMapping
),
uri: ''
};

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 => {
export const organizeVttPlaylists = (playlists, sidxMapping) => {
return playlists.reduce((a, playlist) => {
const label = playlist.attributes.lang || 'text';

Expand All @@ -133,15 +181,18 @@ export const organizeVttPlaylists = playlists => {
language: label,
default: false,
autoselect: false,
playlists: [formatVttPlaylist(playlist)],
playlists: addSegmentInfoFromSidx(
[formatVttPlaylist(playlist)],
sidxMapping
),
uri: ''
};

return a;
}, {});
};

export const formatVideoPlaylist = ({ attributes, segments }) => {
export const formatVideoPlaylist = ({ attributes, segments, sidx }) => {
const playlist = {
attributes: {
NAME: attributes.id,
Expand All @@ -168,10 +219,14 @@ export const formatVideoPlaylist = ({ attributes, segments }) => {
playlist.contentProtection = attributes.contentProtection;
}

if (sidx) {
playlist.sidx = sidx;
}

return playlist;
};

export const toM3u8 = dashPlaylists => {
export const toM3u8 = (dashPlaylists, sidxMapping = {}) => {
if (!dashPlaylists.length) {
return {};
}
Expand Down Expand Up @@ -208,16 +263,16 @@ export const toM3u8 = dashPlaylists => {
},
uri: '',
duration,
playlists: videoPlaylists,
playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping),
minimumUpdatePeriod: minimumUpdatePeriod * 1000
};

if (audioPlaylists.length) {
master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
}

if (vttPlaylists.length) {
master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
}

return master;
Expand Down
24 changes: 18 additions & 6 deletions src/toPlaylists.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { merge } from './utils/object';
import { segmentsFromTemplate } from './segment/segmentTemplate';
import { segmentsFromList } from './segment/segmentList';
import { segmentsFromBase } from './segment/segmentBase';
import {
gkatsev marked this conversation as resolved.
Show resolved Hide resolved
segmentsFromBase
} from './segment/segmentBase';

export const generateSegments = ({ attributes, segmentInfo }) => {
let segmentAttributes;
Expand All @@ -18,8 +20,12 @@ export const generateSegments = ({ attributes, segmentInfo }) => {
segmentAttributes = merge(attributes, segmentInfo.list);
}

const segmentsInfo = {
attributes
};

if (!segmentsFn) {
return { attributes };
return segmentsInfo;
}

const segments = segmentsFn(segmentAttributes, segmentInfo.timeline);
Expand All @@ -41,10 +47,16 @@ export const generateSegments = ({ attributes, segmentInfo }) => {
segmentAttributes.duration = 0;
}

return {
attributes: segmentAttributes,
segments
};
segmentsInfo.attributes = segmentAttributes;
segmentsInfo.segments = segments;

// This is a sidx box without actual segment information
if (segmentInfo.base && segmentAttributes.indexRange) {
segmentsInfo.sidx = segments[0];
segmentsInfo.segments = [];
}

return segmentsInfo;
};

export const toPlaylists = (representations) => representations.map(generateSegments);
4 changes: 2 additions & 2 deletions test/manifests/maat_vtt_segmentTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export const parsedManifest = {
}],
uri: ''
},
['es (main)']: {
['es']: {
autoselect: true,
default: true,
default: false,
language: 'es',
playlists: [{
attributes: {
Expand Down
Loading