From e31b500d3549d688a313b58c073a0f6369699e0d Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Mon, 22 Jan 2018 17:01:42 -0500 Subject: [PATCH 01/12] Real messy early commit --- src/inheritAttributes.js | 13 ++++++++++++- src/toPlaylists.js | 9 ++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 12ab7606..79165335 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -25,11 +25,22 @@ export const rep = mpdAttributes => (period, periodIndex) => { const segmentList = findChildren(adaptationSet, 'SegmentList')[0]; const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; + // OSHIN TODO: Handle this cleaner as this is used in both template and list + const segmentListTimeline = + segmentList && findChildren(segmentList, 'SegmentTimeline')[0]; + + const segmentInfo = { template: segmentTemplate && getAttributes(segmentTemplate), timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), - list: segmentList && getAttributes(segmentList), + list: segmentList && + shallowMerge(getAttributes(segmentList), + { + segmentUrls: findChildren(segmentList, 'SegmentURL').map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), + segmentTimeline: segmentListTimeline && + findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)), + }), base: segmentBase && getAttributes(segmentBase) }; diff --git a/src/toPlaylists.js b/src/toPlaylists.js index d8698c11..4370826c 100644 --- a/src/toPlaylists.js +++ b/src/toPlaylists.js @@ -1,12 +1,12 @@ import { shallowMerge } from './utils/object'; import errors from './errors'; import { segmentsFromTemplate } from './segmentTemplate'; +import { segmentsFromList } from './segmentList'; // TODO export const segmentsFromBase = x => [{ uri: '' }]; // TODO -export const segmentsFromList = x => [{ uri: '' }]; export const generateSegments = (segmentInfo, attributes) => { if (segmentInfo.template) { @@ -23,11 +23,10 @@ export const generateSegments = (segmentInfo, attributes) => { // return segmentsFromBase(attributes); } - // TODO if (segmentInfo.list) { - throw new Error(errors.UNSUPPORTED_SEGMENTATION_TYPE); - - // return segmentsFromList(attributes); + return segmentsFromList( + shallowMerge(segmentInfo.list, attributes) + ); } }; From 6b89e5a8e5b42e5025131bcc76fa9f2011c98b4c Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Tue, 23 Jan 2018 18:19:01 -0500 Subject: [PATCH 02/12] Add segmentBase --- src/inheritAttributes.js | 76 +++++++++++------------- src/segmentBase.js | 26 +++++++++ src/segmentList.js | 78 +++++++++++++++++++++++++ src/segmentTemplate.js | 123 +-------------------------------------- src/segmentTimeParser.js | 106 +++++++++++++++++++++++++++++++++ src/toPlaylists.js | 10 +--- 6 files changed, 246 insertions(+), 173 deletions(-) create mode 100644 src/segmentBase.js create mode 100644 src/segmentList.js create mode 100644 src/segmentTimeParser.js diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 63cc13d4..a533476d 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -5,46 +5,6 @@ import { findChildren, getContent } from './utils/xml'; import resolveUrl from './resolveUrl'; import errors from './errors'; -<<<<<<< HEAD -export const rep = mpdAttributes => (period, periodIndex) => { - const adaptationSets = findChildren(period, 'AdaptationSet'); - - const representationsByAdaptationSet = adaptationSets.map(adaptationSet => { - const adaptationSetAttributes = getAttributes(adaptationSet); - - const role = findChildren(adaptationSet, 'Role')[0]; - const roleAttributes = { role: getAttributes(role) }; - - const attrs = shallowMerge({ periodIndex }, - mpdAttributes, - adaptationSetAttributes, - roleAttributes); - - const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; - const segmentTimeline = - segmentTemplate && findChildren(segmentTemplate, 'SegmentTimeline')[0]; - const segmentList = findChildren(adaptationSet, 'SegmentList')[0]; - const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; - - // OSHIN TODO: Handle this cleaner as this is used in both template and list - const segmentListTimeline = - segmentList && findChildren(segmentList, 'SegmentTimeline')[0]; - - - const segmentInfo = { - template: segmentTemplate && getAttributes(segmentTemplate), - timeline: segmentTimeline && - findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), - list: segmentList && - shallowMerge(getAttributes(segmentList), - { - segmentUrls: findChildren(segmentList, 'SegmentURL').map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), - segmentTimeline: segmentListTimeline && - findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)), - }), - base: segmentBase && getAttributes(segmentBase) - }; -======= /** * Builds a list of urls that is the product of the reference urls and BaseURL values * @@ -59,7 +19,6 @@ export const buildBaseUrls = (referenceUrls, baseUrlElements) => { if (!baseUrlElements.length) { return referenceUrls; } ->>>>>>> master return flatten( referenceUrls.map( @@ -96,11 +55,24 @@ export const getSegmentInformation = (adaptationSet) => { const segmentList = findChildren(adaptationSet, 'SegmentList')[0]; const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; + // OSHIN TODO: Handle this cleaner as this is used in both template and list + const segmentListTimeline = + segmentList && findChildren(segmentList, 'SegmentTimeline')[0]; + + console.log(adaptationSet); + console.log(segmentBase); + return { template: segmentTemplate && getAttributes(segmentTemplate), timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), - list: segmentList && getAttributes(segmentList), + list: segmentList && + shallowMerge(getAttributes(segmentList), + { + segmentUrls: findChildren(segmentList, 'SegmentURL').map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), + segmentTimeline: segmentListTimeline && + findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)), + }), base: segmentBase && getAttributes(segmentBase) }; }; @@ -186,9 +158,27 @@ export const toRepresentations = const attrs = shallowMerge(periodAttributes, adaptationSetAttributes, roleAttributes); - const segmentInfo = getSegmentInformation(adaptationSet); + let segmentInfo = getSegmentInformation(adaptationSet); const representations = findChildren(adaptationSet, 'Representation'); + /** + * We need to go through each representation in the adapation in case there is segment + * information such as: + * + * + * + * + * + * + * + * + */ + representations.forEach(representation => { + segmentInfo = shallowMerge(segmentInfo, getSegmentInformation(representation)) + }); + + console.log(segmentInfo.base); + return flatten( representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, segmentInfo))); }; diff --git a/src/segmentBase.js b/src/segmentBase.js new file mode 100644 index 00000000..ae321be3 --- /dev/null +++ b/src/segmentBase.js @@ -0,0 +1,26 @@ +import resolveUrl from './resolveUrl'; +export const segmentsFromBase = (attributes) => { + const { + timescale = 1, + duration, + periodIndex = 0, + startNumber = 1, + baseUrl = '', + initialization = '' + } = attributes; + const parsedTimescale = parseInt(timescale, 10); + const start = parseInt(startNumber, 10); + + const segment = { + map: { + uri: initialization, + resolvedUri: resolveUrl(attributes.baseUrl || '', initialization) + }, + resolvedUri: resolveUrl(attributes.baseUrl || '', initialization), + uri: attributes.baseUrl + }; + + console.log(segment.map); + + return [segment]; +}; \ No newline at end of file diff --git a/src/segmentList.js b/src/segmentList.js new file mode 100644 index 00000000..51ae0167 --- /dev/null +++ b/src/segmentList.js @@ -0,0 +1,78 @@ +import { range } from './utils/list'; +import resolveUrl from './resolveUrl'; +import { parseByDuration, parseSegmentTimeline } from './segmentTimeParser'; + +const URLTypeToSegment = (attributes, segmentUrl) => { + const initUri = attributes.initialization || ''; + + const segment = { + map: { + uri: initUri, + resolvedUri: resolveUrl(attributes.baseUrl || '', initUri) + }, + resolvedUri: resolveUrl(attributes.baseUrl || '', segmentUrl.media), + uri: segmentUrl.media + } + + // Follows byte-range-spec per RFC2616 + // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 + if (segmentUrl.mediaRange) { + const ranges = segmentUrl.mediaRange.split('-'); + const startRange = parseInt(ranges[0]); + const endRange = parseInt(ranges[1]); + + segment.byterange = { + length: endRange - startRange, + offset: startRange + } + } + + return segment; +}; + +export const segmentsFromList = (attributes) => { + const { + timescale = 1, + duration, + segmentUrls, + segmentTimeline, + periodIndex = 0, + startNumber = 1 + } = attributes; + const parsedTimescale = parseInt(timescale, 10); + const start = parseInt(startNumber, 10); + let segmentTimeInfo; + + const segmentUrlMap = segmentUrls.map(segmentUrlObject => URLTypeToSegment(attributes, segmentUrlObject)); + + + if (duration) { + const parsedDuration = parseInt(duration, 10); + const segmentDuration = (parsedDuration / parsedTimescale); + + segmentTimeInfo = parseByDuration(start, + attributes.periodIndex, + parsedTimescale, + parsedDuration, + attributes.sourceDuration); + } else if (segmentTimeline) { + segmentTimeInfo = parseSegmentTimeline(start, + attributes.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; + } + + return {}; + }); + + return segments; +}; \ No newline at end of file diff --git a/src/segmentTemplate.js b/src/segmentTemplate.js index 9c793b57..b480ba26 100644 --- a/src/segmentTemplate.js +++ b/src/segmentTemplate.js @@ -1,5 +1,6 @@ import { range } from './utils/list'; import resolveUrl from './resolveUrl'; +import { parseByDuration, parseSegmentTimeline } from './segmentTimeParser'; const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; @@ -90,128 +91,6 @@ export const identifierReplacement = (values) => (match, identifier, format, wid export const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values)); -/** - * Uses information provided by SegmentTemplate@duration attribute to determine segment - * timing and duration - * - * @param {number} start - * The start number for the first segment of this period - * @param {number} timeline - * The timeline (period index) for the first segment of this period - * @param {number} timescale - * The timescale for the timestamps contained within the media content - * @param {number} duration - * Duration of each segment - * @param {number} sourceDuration - * Duration of the entire Media Presentation - * @return {{number: number, duration: number, time: number, timeline: number}[]} - * List of Objects with segment timing and duration info - */ -export const parseByDuration = (start, timeline, timescale, duration, sourceDuration) => { - const count = Math.ceil(sourceDuration / (duration / timescale)); - - return range(start, start + count).map((number, index) => { - const segment = { number, duration: duration / timescale, timeline }; - - if (index === count - 1) { - // final segment may be less than duration - segment.duration = sourceDuration - (segment.duration * index); - } - - segment.time = (segment.number - start) * duration; - - return segment; - }); -}; - -/** - * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment - * timing and duration - * - * @param {number} start - * The start number for the first segment of this period - * @param {number} timeline - * The timeline (period index) for the first segment of this period - * @param {number} timescale - * The timescale for the timestamps contained within the media content - * @param {Object[]} segmentTimeline - * List of objects representing the attributes of each S element contained within - * @param {number} sourceDuration - * Duration of the entire Media Presentation - * @return {{number: number, duration: number, time: number, timeline: number}[]} - * List of Objects with segment timing and duration info - */ -export const parseByTimeline = -(start, timeline, timescale, segmentTimeline, sourceDuration) => { - const segments = []; - let time = -1; - - for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { - const S = segmentTimeline[sIndex]; - const duration = parseInt(S.d, 10); - const repeat = parseInt(S.r || 0, 10); - const segmentTime = parseInt(S.t || 0, 10); - - if (time < 0) { - // first segment - time = segmentTime; - } - - if (segmentTime && segmentTime > time) { - // discontinuity - - // TODO: How to handle this type of discontinuity - // timeline++ here would treat it like HLS discontuity and content would - // get appended without gap - // E.G. - // - // - // - // - // would have $Time$ values of [0, 1, 2, 5] - // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) - // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) - // does the value of sourceDuration consider this when calculating arbitrary - // negative @r repeat value? - // E.G. Same elements as above with this added at the end - // - // with a sourceDuration of 10 - // Would the 2 gaps be included in the time duration calculations resulting in - // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments - // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? - - time = segmentTime; - } - - let count; - - if (repeat < 0) { - const nextS = sIndex + 1; - - if (nextS === segmentTimeline.length) { - // last segment - // TODO: This may be incorrect depending on conclusion of TODO above - count = ((sourceDuration * timescale) - time) / duration; - } else { - count = (parseInt(segmentTimeline[nextS].t, 10) - time) / duration; - } - } else { - count = repeat + 1; - } - - const end = start + segments.length + count; - let number = start + segments.length; - - while (number < end) { - segments.push({ number, duration: duration / timescale, time, timeline }); - time += duration; - number++; - } - } - - return segments; -}; - /** * Generates a list of objects containing timing and duration information about each * segment needed to generate segment uris and the complete segment object diff --git a/src/segmentTimeParser.js b/src/segmentTimeParser.js new file mode 100644 index 00000000..f26bed40 --- /dev/null +++ b/src/segmentTimeParser.js @@ -0,0 +1,106 @@ + +import { range } from './utils/list'; +/** + * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment + * timing and duration + * + * @param {number} start + * The start number for the first segment of this period + * @param {number} timeline + * The timeline (period index) for the first segment of this period + * @param {number} timescale + * The timescale for the timestamps contained within the media content + * @param {Object[]} segmentTimeline + * List of objects representing the attributes of each S element contained within + * @param {number} sourceDuration + * Duration of the entire Media Presentation + * @return {{number: number, duration: number, time: number, timeline: number}[]} + * List of Objects with segment timing and duration info + */ +export const parseSegmentTimeline = +(start, timeline, timescale, segmentTimeline, sourceDuration) => { + const segments = []; + let time = -1; + + for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { + const S = segmentTimeline[sIndex]; + const duration = parseInt(S.d, 10); + const repeat = parseInt(S.r || 0, 10); + const segmentTime = parseInt(S.t || 0, 10); + + if (time < 0) { + // first segment + time = segmentTime; + } + + if (segmentTime && segmentTime > time) { + // discontinuity + + // TODO: How to handle this type of discontinuity + // timeline++ here would treat it like HLS discontuity and content would + // get appended without gap + // E.G. + // + // + // + // + // would have $Time$ values of [0, 1, 2, 5] + // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) + // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) + // does the value of sourceDuration consider this when calculating arbitrary + // negative @r repeat value? + // E.G. Same elements as above with this added at the end + // + // with a sourceDuration of 10 + // Would the 2 gaps be included in the time duration calculations resulting in + // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments + // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? + + time = segmentTime; + } + + let count; + + if (repeat < 0) { + const nextS = sIndex + 1; + + if (nextS === segmentTimeline.length) { + // last segment + // TODO: This may be incorrect depending on conclusion of TODO above + count = ((sourceDuration * timescale) - time) / duration; + } else { + count = (parseInt(segmentTimeline[nextS].t, 10) - time) / duration; + } + } else { + count = repeat + 1; + } + + const end = start + segments.length + count; + let number = start + segments.length; + + while (number < end) { + segments.push({ number, duration: duration / timescale, time, timeline }); + time += duration; + number++; + } + } + + return segments; +}; + +export const parseByDuration = (start, timeline, timescale, duration, sourceDuration) => { + const count = Math.ceil(sourceDuration / (duration / timescale)); + + return range(start, start + count).map((number, index) => { + const segment = { number, duration: duration / timescale, timeline }; + + if (index === count - 1) { + // final segment may be less than duration + segment.duration = sourceDuration - (segment.duration * index); + } + + segment.time = (segment.number - start) * duration; + + return segment; + }); +}; \ No newline at end of file diff --git a/src/toPlaylists.js b/src/toPlaylists.js index 4370826c..a4404442 100644 --- a/src/toPlaylists.js +++ b/src/toPlaylists.js @@ -2,11 +2,7 @@ import { shallowMerge } from './utils/object'; import errors from './errors'; import { segmentsFromTemplate } from './segmentTemplate'; import { segmentsFromList } from './segmentList'; - -// TODO -export const segmentsFromBase = x => [{ uri: '' }]; - -// TODO +import { segmentsFromBase } from './segmentBase'; export const generateSegments = (segmentInfo, attributes) => { if (segmentInfo.template) { @@ -18,9 +14,7 @@ export const generateSegments = (segmentInfo, attributes) => { // TODO if (segmentInfo.base) { - throw new Error(errors.UNSUPPORTED_SEGMENTATION_TYPE); - - // return segmentsFromBase(attributes); + return segmentsFromBase(shallowMerge(segmentInfo.base, attributes)); } if (segmentInfo.list) { From 0fa09fc05cfe7d06a8e60dcc0a0bee3bc7b66d15 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Thu, 25 Jan 2018 17:57:53 -0500 Subject: [PATCH 03/12] Add some tests, modify some logic to accomodate spec, shuffle some code around --- package.json | 1 - src/errors.js | 5 +- src/inheritAttributes.js | 28 +- src/segment/segmentBase.js | 32 ++ src/{ => segment}/segmentList.js | 24 +- src/{ => segment}/segmentTemplate.js | 6 +- .../timeParser.js} | 5 +- src/segmentBase.js | 26 -- src/toPlaylists.js | 7 +- src/{ => utils}/resolveUrl.js | 0 test/index.test.js | 17 +- test/inheritAttributes.test.js | 11 +- test/manifests/segmentBase.js | 45 +++ test/manifests/segmentBase.mpd | 14 + test/manifests/segmentList.js | 216 +++++++++++ test/manifests/segmentList.mpd | 45 +++ test/segmentBase.test.js | 28 ++ test/segmentList.test.js | 354 ++++++++++++++++++ test/segmentTemplate.test.js | 2 +- test/toPlaylists.test.js | 2 + 20 files changed, 794 insertions(+), 74 deletions(-) create mode 100644 src/segment/segmentBase.js rename src/{ => segment}/segmentList.js (75%) rename src/{ => segment}/segmentTemplate.js (97%) rename src/{segmentTimeParser.js => segment/timeParser.js} (97%) delete mode 100644 src/segmentBase.js rename src/{ => utils}/resolveUrl.js (100%) create mode 100644 test/manifests/segmentBase.js create mode 100644 test/manifests/segmentBase.mpd create mode 100644 test/manifests/segmentList.js create mode 100644 test/manifests/segmentList.mpd create mode 100644 test/segmentBase.test.js create mode 100644 test/segmentList.test.js diff --git a/package.json b/package.json index 39a50679..20cc8403 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "prestart": "npm run build", "start": "run-p start:server test:server watch:js", "start:server": "static -a 0.0.0.0 -p 9999 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}' .", - "pretest": "npm run lint", "test": "karma start test/karma.conf.js", "test:server": "karma start test/karma.conf.js --singleRun=false --auto-watch --no-browsers", "watch:js": "rollup -c scripts/rollup.config.js -w", diff --git a/src/errors.js b/src/errors.js index 04f6b649..f8d511d5 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,5 +2,8 @@ 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' + UNSUPPORTED_SEGMENTATION_TYPE: 'UNSUPPORTED_SEGMENTATION_TYPE', + NO_BASE_URL: 'NO_BASE_URL', + MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', + SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED' }; diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index a533476d..27c7726a 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -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'; /** @@ -59,8 +59,7 @@ export const getSegmentInformation = (adaptationSet) => { const segmentListTimeline = segmentList && findChildren(segmentList, 'SegmentTimeline')[0]; - console.log(adaptationSet); - console.log(segmentBase); + const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL'); return { template: segmentTemplate && getAttributes(segmentTemplate), @@ -69,9 +68,10 @@ export const getSegmentInformation = (adaptationSet) => { list: segmentList && shallowMerge(getAttributes(segmentList), { - segmentUrls: findChildren(segmentList, 'SegmentURL').map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), + segmentUrls: segmentUrls && + segmentUrls.map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), segmentTimeline: segmentListTimeline && - findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)), + findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)) }), base: segmentBase && getAttributes(segmentBase) }; @@ -161,24 +161,6 @@ export const toRepresentations = let segmentInfo = getSegmentInformation(adaptationSet); const representations = findChildren(adaptationSet, 'Representation'); - /** - * We need to go through each representation in the adapation in case there is segment - * information such as: - * - * - * - * - * - * - * - * - */ - representations.forEach(representation => { - segmentInfo = shallowMerge(segmentInfo, getSegmentInformation(representation)) - }); - - console.log(segmentInfo.base); - return flatten( representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, segmentInfo))); }; diff --git a/src/segment/segmentBase.js b/src/segment/segmentBase.js new file mode 100644 index 00000000..91244e8a --- /dev/null +++ b/src/segment/segmentBase.js @@ -0,0 +1,32 @@ +import resolveUrl from '../utils/resolveUrl'; +import errors from '../errors'; + +export const segmentsFromBase = (attributes) => { + const { + baseUrl, + initialization = '', + sourceDuration + } = attributes; + const parsedTimescale = parseInt(timescale, 10); + const start = parseInt(startNumber, 10); + + // 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 segment = { + map: { + uri: initialization, + resolvedUri: resolveUrl(attributes.baseUrl || '', initialization) + }, + resolvedUri: resolveUrl(attributes.baseUrl || '', ''), + uri: attributes.baseUrl + }; + + if (sourceDuration) { + segment.duration = sourceDuration; + } + + return [segment]; +}; \ No newline at end of file diff --git a/src/segmentList.js b/src/segment/segmentList.js similarity index 75% rename from src/segmentList.js rename to src/segment/segmentList.js index 51ae0167..385d3b34 100644 --- a/src/segmentList.js +++ b/src/segment/segmentList.js @@ -1,6 +1,7 @@ -import { range } from './utils/list'; -import resolveUrl from './resolveUrl'; -import { parseByDuration, parseSegmentTimeline } from './segmentTimeParser'; +import { range } from '../utils/list'; +import resolveUrl from '../utils/resolveUrl'; +import { parseByDuration, parseByTimeline } from './timeParser'; +import errors from '../errors'; const URLTypeToSegment = (attributes, segmentUrl) => { const initUri = attributes.initialization || ''; @@ -34,17 +35,24 @@ export const segmentsFromList = (attributes) => { const { timescale = 1, duration, - segmentUrls, + segmentUrls = [], segmentTimeline, periodIndex = 0, startNumber = 1 } = attributes; + + // Per spec (5.3.9.2.1) if there is more than one segment, but no way + // to determine segment duration OR if both SegmentTimeline and @duration + // are defined, it is outside of spec. + if ((segmentUrls.length > 1 && !duration && !segmentTimeline) || + duration && segmentTimeline) { + throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); + } + const parsedTimescale = parseInt(timescale, 10); const start = parseInt(startNumber, 10); - let segmentTimeInfo; - const segmentUrlMap = segmentUrls.map(segmentUrlObject => URLTypeToSegment(attributes, segmentUrlObject)); - + let segmentTimeInfo; if (duration) { const parsedDuration = parseInt(duration, 10); @@ -56,7 +64,7 @@ export const segmentsFromList = (attributes) => { parsedDuration, attributes.sourceDuration); } else if (segmentTimeline) { - segmentTimeInfo = parseSegmentTimeline(start, + segmentTimeInfo = parseByTimeline(start, attributes.periodIndex, parsedTimescale, segmentTimeline, diff --git a/src/segmentTemplate.js b/src/segment/segmentTemplate.js similarity index 97% rename from src/segmentTemplate.js rename to src/segment/segmentTemplate.js index b480ba26..a4af4348 100644 --- a/src/segmentTemplate.js +++ b/src/segment/segmentTemplate.js @@ -1,6 +1,6 @@ -import { range } from './utils/list'; -import resolveUrl from './resolveUrl'; -import { parseByDuration, parseSegmentTimeline } from './segmentTimeParser'; +import { range } from '../utils/list'; +import resolveUrl from '../utils/resolveUrl'; +import { parseByDuration, parseByTimeline } from './timeParser'; const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; diff --git a/src/segmentTimeParser.js b/src/segment/timeParser.js similarity index 97% rename from src/segmentTimeParser.js rename to src/segment/timeParser.js index f26bed40..6f4bcf98 100644 --- a/src/segmentTimeParser.js +++ b/src/segment/timeParser.js @@ -1,5 +1,4 @@ - -import { range } from './utils/list'; +import { range } from '../utils/list'; /** * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment * timing and duration @@ -17,7 +16,7 @@ import { range } from './utils/list'; * @return {{number: number, duration: number, time: number, timeline: number}[]} * List of Objects with segment timing and duration info */ -export const parseSegmentTimeline = +export const parseByTimeline = (start, timeline, timescale, segmentTimeline, sourceDuration) => { const segments = []; let time = -1; diff --git a/src/segmentBase.js b/src/segmentBase.js deleted file mode 100644 index ae321be3..00000000 --- a/src/segmentBase.js +++ /dev/null @@ -1,26 +0,0 @@ -import resolveUrl from './resolveUrl'; -export const segmentsFromBase = (attributes) => { - const { - timescale = 1, - duration, - periodIndex = 0, - startNumber = 1, - baseUrl = '', - initialization = '' - } = attributes; - const parsedTimescale = parseInt(timescale, 10); - const start = parseInt(startNumber, 10); - - const segment = { - map: { - uri: initialization, - resolvedUri: resolveUrl(attributes.baseUrl || '', initialization) - }, - resolvedUri: resolveUrl(attributes.baseUrl || '', initialization), - uri: attributes.baseUrl - }; - - console.log(segment.map); - - return [segment]; -}; \ No newline at end of file diff --git a/src/toPlaylists.js b/src/toPlaylists.js index a4404442..9c405382 100644 --- a/src/toPlaylists.js +++ b/src/toPlaylists.js @@ -1,8 +1,7 @@ import { shallowMerge } from './utils/object'; -import errors from './errors'; -import { segmentsFromTemplate } from './segmentTemplate'; -import { segmentsFromList } from './segmentList'; -import { segmentsFromBase } from './segmentBase'; +import { segmentsFromTemplate } from './segment/segmentTemplate'; +import { segmentsFromList } from './segment/segmentList'; +import { segmentsFromBase } from './segment/segmentBase'; export const generateSegments = (segmentInfo, attributes) => { if (segmentInfo.template) { diff --git a/src/resolveUrl.js b/src/utils/resolveUrl.js similarity index 100% rename from src/resolveUrl.js rename to src/utils/resolveUrl.js diff --git a/test/index.test.js b/test/index.test.js index 0dd30c55..0b7fe211 100755 --- a/test/index.test.js +++ b/test/index.test.js @@ -3,10 +3,17 @@ import QUnit from 'qunit'; // manifests import maatVttSegmentTemplate from './manifests/maat_vtt_segmentTemplate.mpd'; +import segmentBaseTemplate from './manifests/segmentBase.mpd'; +import segmentListTemplate from './manifests/segmentList.mpd'; import { parsedManifest as maatVttSegmentTemplateManifest } from './manifests/maat_vtt_segmentTemplate.js'; - +import { + parsedManifest as segmentBaseManifest +} from './manifests/segmentBase.js'; +import { + parsedManifest as segmentListManifest +} from './manifests/segmentList.js'; QUnit.module('mpd-parser'); QUnit.test('has VERSION', function(assert) { @@ -21,6 +28,14 @@ QUnit.test('has parse', function(assert) { name: 'maat_vtt_segmentTemplate', input: maatVttSegmentTemplate, expected: maatVttSegmentTemplateManifest +},{ + name: 'segmentBase', + input: segmentBaseTemplate, + expected: segmentBaseManifest +},{ + name: 'segmentList', + input: segmentListTemplate, + expected: segmentListManifest }].forEach(({ name, input, expected }) => { QUnit.test(`${name} test manifest`, function(assert) { const actual = parse(input); diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index 888b3a87..234ccb6d 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -106,14 +106,19 @@ QUnit.test('gets SegmentList attributes', function(assert) { const adaptationSet = { childNodes: [{ tagName: 'SegmentList', - attributes: [{ name: 'duration', value: '10' }] + attributes: [{ name: 'duration', value: '10' }], + childNodes: [] }] }; const expected = { template: void 0, timeline: void 0, - list: { duration: '10' }, - base: void 0 + list: { + duration: '10', + segmentTimeline: void 0, + segmentUrls: [] + }, + base: void 0, }; assert.deepEqual(getSegmentInformation(adaptationSet), expected, diff --git a/test/manifests/segmentBase.js b/test/manifests/segmentBase.js new file mode 100644 index 00000000..88b6f6a2 --- /dev/null +++ b/test/manifests/segmentBase.js @@ -0,0 +1,45 @@ +export const parsedManifest = { + allowCache: true, + discontinuityStarts: [], + duration: 6, + endList: true, + mediaGroups: { + 'AUDIO': {}, + 'CLOSED-CAPTIONS': {}, + 'SUBTITLES': {}, + 'VIDEO': {} + }, + playlists: [ + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 449000, + 'CODECS': 'avc1.420015', + 'NAME': '482', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 270, + width: 482 + }, + 'SUBTITLES': 'subs' + }, + endList: true, + resolvedUri: '', + segments: [ + { + duration: 6, + map: { + uri: '', + resolvedUri: 'https://www.example.com/1080p.ts' + }, + resolvedUri: 'https://www.example.com/1080p.ts', + uri: 'https://www.example.com/1080p.ts' + } + ], + timeline: 0, + uri: '' + } + ], + segments: [], + uri: '' +}; \ No newline at end of file diff --git a/test/manifests/segmentBase.mpd b/test/manifests/segmentBase.mpd new file mode 100644 index 00000000..e95fd1a4 --- /dev/null +++ b/test/manifests/segmentBase.mpd @@ -0,0 +1,14 @@ + + + https://www.example.com/base + + + + + 1080p.ts + + + + + + diff --git a/test/manifests/segmentList.js b/test/manifests/segmentList.js new file mode 100644 index 00000000..ecd1595d --- /dev/null +++ b/test/manifests/segmentList.js @@ -0,0 +1,216 @@ +export const parsedManifest = { + allowCache: true, + discontinuityStarts: [], + duration: 6, + endList: true, + mediaGroups: { + 'AUDIO': {}, + 'CLOSED-CAPTIONS': {}, + 'SUBTITLES': {}, + 'VIDEO': {} + }, + playlists: [ + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 449000, + 'CODECS': 'avc1.420015', + 'NAME': '482', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 270, + width: 482 + }, + 'SUBTITLES': 'subs' + }, + endList: true, + resolvedUri: '', + segments: [ + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-1.ts', + timeline: 0, + uri: 'low/segment-1.ts' + }, + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-2.ts', + timeline: 0, + uri: 'low/segment-2.ts' + }, + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-3.ts', + timeline: 0, + uri: 'low/segment-3.ts' + }, + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-4.ts', + timeline: 0, + uri: 'low/segment-4.ts' + }, + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-5.ts', + timeline: 0, + uri: 'low/segment-5.ts' + }, + { + duration: 1, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/low/segment-6.ts', + timeline: 0, + uri: 'low/segment-6.ts' + } + ], + timeline: 0, + uri: '' + }, + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 3971000, + 'CODECS': 'avc1.420015', + 'NAME': '720', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 404, + width: 720 + }, + 'SUBTITLES': 'subs' + }, + endList: true, + resolvedUri: '', + segments: [ + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-1.ts', + timeline: 0, + uri: 'high/segment-1.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-2.ts', + timeline: 0, + uri: 'high/segment-2.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-3.ts', + timeline: 0, + uri: 'high/segment-3.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-4.ts', + timeline: 0, + uri: 'high/segment-4.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-5.ts', + timeline: 0, + uri: 'high/segment-5.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-6.ts', + timeline: 0, + uri: 'high/segment-6.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-7.ts', + timeline: 0, + uri: 'high/segment-7.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-8.ts', + timeline: 0, + uri: 'high/segment-8.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-9.ts', + timeline: 0, + uri: 'high/segment-9.ts' + }, + { + duration: 60, + map: { + uri: '', + resolvedUri: 'https://www.example.com/base' + }, + resolvedUri: 'https://www.example.com/high/segment-10.ts', + timeline: 0, + uri: 'high/segment-10.ts' + } + ], + timeline: 0, + uri: '' + } + ], + segments: [], + uri: '' +}; \ No newline at end of file diff --git a/test/manifests/segmentList.mpd b/test/manifests/segmentList.mpd new file mode 100644 index 00000000..0977c5a4 --- /dev/null +++ b/test/manifests/segmentList.mpd @@ -0,0 +1,45 @@ + + + https://www.example.com/base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/segmentBase.test.js b/test/segmentBase.test.js new file mode 100644 index 00000000..7fb973fa --- /dev/null +++ b/test/segmentBase.test.js @@ -0,0 +1,28 @@ +import QUnit from 'qunit'; +import { + segmentsFromBase +} from '../src/segment/segmentBase'; +import errors from '../src/errors'; + + +QUnit.module('segmentBase - segmentsFromBase'); + +QUnit.test('sets segment to baseUrl', function(assert) { + const inputAttributes = { + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4' + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('errors if no baseUrl exists', function(assert) { + assert.throws(() => segmentsFromBase({}), new Error(errors.NO_BASE_URL)); +}); diff --git a/test/segmentList.test.js b/test/segmentList.test.js new file mode 100644 index 00000000..50990211 --- /dev/null +++ b/test/segmentList.test.js @@ -0,0 +1,354 @@ +import QUnit from 'qunit'; +import { + segmentsFromList +} from '../src/segment/segmentList'; +import errors from '../src/errors'; + +QUnit.module('segmentList - segmentsFromList'); + +QUnit.test('uses segmentTimeline to set segments', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + },{ + media: '3.fmp4', + },{ + media: '4.fmp4' + },{ + media: '5.fmp4', + } + ], + initialization: 'init.fmp4', + segmentTimeline: [{ + t: 1000, + d: 1000, + r: 4 + }], + periodIndex: 0, + startNumber: 1, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/3.fmp4', + timeline: 0, + uri: '3.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/4.fmp4', + timeline: 0, + uri: '4.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/5.fmp4', + timeline: 0, + uri: '5.fmp4' + }]); +}); + +QUnit.test('uses duration to set segments', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + },{ + media: '3.fmp4', + },{ + media: '4.fmp4' + },{ + media: '5.fmp4', + } + ], + initialization: 'init.fmp4', + duration: 10, + periodIndex: 0, + startNumber: 1, + sourceDuration: 50, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + },{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/3.fmp4', + timeline: 0, + uri: '3.fmp4' + },{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/4.fmp4', + timeline: 0, + uri: '4.fmp4' + },{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/5.fmp4', + timeline: 0, + uri: '5.fmp4' + }]); +}); + +QUnit.test('uses timescale to set segment duration', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + },{ + media: '3.fmp4', + },{ + media: '4.fmp4' + },{ + media: '5.fmp4', + } + ], + initialization: 'init.fmp4', + duration: 10, + timescale: 2, + periodIndex: 0, + startNumber: 1, + sourceDuration: 25, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + },{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/3.fmp4', + timeline: 0, + uri: '3.fmp4' + },{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/4.fmp4', + timeline: 0, + uri: '4.fmp4' + },{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/5.fmp4', + timeline: 0, + uri: '5.fmp4' + }]); +}); + +QUnit.test('timescale sets duration of last segment correctly', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + } + ], + initialization: 'init.fmp4', + duration: 10, + timescale: 1, + periodIndex: 0, + startNumber: 1, + sourceDuration: 15, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 5, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + }]); +}); + +QUnit.test('segmentUrl translates ranges correctly', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + mediaRange: '0-200' + },{ + media: '1.fmp4', + mediaRange: '201-400' + } + ], + initialization: 'init.fmp4', + duration: 10, + timescale: 1, + periodIndex: 0, + startNumber: 1, + sourceDuration: 20, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + byterange: { + length: 200, + offset: 0 + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 10, + byterange: { + length: 199, + offset: 201 + }, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + }]); +}); + +QUnit.test('throws error if more than 1 segment and no duration or timeline', + function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + } + ], + duration: 10, + segmentTimeline: [{ + t: 1000, + d: 1000, + r: 4 + }], + initialization: 'init.fmp4', + timescale: 1, + periodIndex: 0, + startNumber: 1, + sourceDuration: 20, + baseUrl: 'http://example.com/' + }; + + assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); + }); + + + QUnit.test('throws error if timeline and duration are both defined', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + } + ], + initialization: 'init.fmp4', + timescale: 1, + periodIndex: 0, + startNumber: 1, + sourceDuration: 20, + baseUrl: 'http://example.com/' + }; + + assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); +}); + diff --git a/test/segmentTemplate.test.js b/test/segmentTemplate.test.js index d75e96af..5fcebbbc 100644 --- a/test/segmentTemplate.test.js +++ b/test/segmentTemplate.test.js @@ -3,7 +3,7 @@ import { constructTemplateUrl, parseTemplateInfo, segmentsFromTemplate -} from '../src/segmentTemplate'; +} from '../src/segment/segmentTemplate'; QUnit.module('segmentTemplate - constructTemplateUrl'); diff --git a/test/toPlaylists.test.js b/test/toPlaylists.test.js index 1f3726df..709afcce 100644 --- a/test/toPlaylists.test.js +++ b/test/toPlaylists.test.js @@ -35,6 +35,7 @@ QUnit.test('pretty simple', function(assert) { assert.deepEqual(toPlaylists(representations), playlists); }); +/* QUnit.test('segment base', function(assert) { const representations = [{ attributes: {}, @@ -58,3 +59,4 @@ QUnit.test('segment list', function(assert) { assert.throws(() => toPlaylists(representations), new RegExp(errors.UNSUPPORTED_SEGMENTATION_TYPE)); }); +*/ From 2a9f36aab3e91ce93566eaaead0e96dad6a49543 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Fri, 26 Jan 2018 15:59:48 -0500 Subject: [PATCH 04/12] Add more tests and cover more cases, add comments --- src/segment/segmentBase.js | 39 ++++++- src/segment/segmentList.js | 46 ++++++-- test/manifests/segmentBase.js | 1 + test/manifests/segmentBase.mpd | 2 +- test/segment/segmentBase.test.js | 107 +++++++++++++++++ test/{ => segment}/segmentList.test.js | 130 ++++++++++++++++++++- test/{ => segment}/segmentTemplate.test.js | 2 +- test/segmentBase.test.js | 28 ----- 8 files changed, 305 insertions(+), 50 deletions(-) create mode 100644 test/segment/segmentBase.test.js rename test/{ => segment}/segmentList.test.js (71%) rename test/{ => segment}/segmentTemplate.test.js (99%) delete mode 100644 test/segmentBase.test.js diff --git a/src/segment/segmentBase.js b/src/segment/segmentBase.js index 91244e8a..d2ffb609 100644 --- a/src/segment/segmentBase.js +++ b/src/segment/segmentBase.js @@ -1,11 +1,26 @@ import resolveUrl from '../utils/resolveUrl'; import errors from '../errors'; +import { parseByDuration } from './timeParser'; + +/** + * Translates SegmentBase into a set of segments. + * (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each + * node should be translated into segment. + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + */ export const segmentsFromBase = (attributes) => { const { baseUrl, initialization = '', - sourceDuration + sourceDuration, + timescale = 1, + startNumber = 1, + periodIndex = 0, + duration } = attributes; const parsedTimescale = parseInt(timescale, 10); const start = parseInt(startNumber, 10); @@ -24,8 +39,26 @@ export const segmentsFromBase = (attributes) => { uri: attributes.baseUrl }; - if (sourceDuration) { - segment.duration = sourceDuration; + // 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 parsedTimescale = parseInt(timescale, 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]; diff --git a/src/segment/segmentList.js b/src/segment/segmentList.js index 385d3b34..38d2e2c5 100644 --- a/src/segment/segmentList.js +++ b/src/segment/segmentList.js @@ -3,7 +3,17 @@ import resolveUrl from '../utils/resolveUrl'; import { parseByDuration, parseByTimeline } from './timeParser'; import errors from '../errors'; -const URLTypeToSegment = (attributes, segmentUrl) => { +/** + * Converts a (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 + * node to translate into a segment object + */ +const SegmentURLToSegmentObject = (attributes, segmentUrl) => { const initUri = attributes.initialization || ''; const segment = { @@ -31,6 +41,15 @@ const URLTypeToSegment = (attributes, segmentUrl) => { 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 nodes. Each + * node should be translated into segment. + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + */ export const segmentsFromList = (attributes) => { const { timescale = 1, @@ -41,17 +60,17 @@ export const segmentsFromList = (attributes) => { startNumber = 1 } = attributes; - // Per spec (5.3.9.2.1) if there is more than one segment, but no way - // to determine segment duration OR if both SegmentTimeline and @duration - // are defined, it is outside of spec. - if ((segmentUrls.length > 1 && !duration && !segmentTimeline) || + // 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 => URLTypeToSegment(attributes, segmentUrlObject)); + const segmentUrlMap = segmentUrls.map(segmentUrlObject => + SegmentURLToSegmentObject(attributes, segmentUrlObject)); let segmentTimeInfo; if (duration) { @@ -59,13 +78,15 @@ export const segmentsFromList = (attributes) => { const segmentDuration = (parsedDuration / parsedTimescale); segmentTimeInfo = parseByDuration(start, - attributes.periodIndex, + periodIndex, parsedTimescale, parsedDuration, attributes.sourceDuration); - } else if (segmentTimeline) { + } + + if (segmentTimeline) { segmentTimeInfo = parseByTimeline(start, - attributes.periodIndex, + periodIndex, parsedTimescale, segmentTimeline, attributes.sourceDuration); @@ -78,9 +99,10 @@ export const segmentsFromList = (attributes) => { segment.duration = segmentTime.duration; return segment; } - - return {}; - }); + // 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; }; \ No newline at end of file diff --git a/test/manifests/segmentBase.js b/test/manifests/segmentBase.js index 88b6f6a2..a26fb376 100644 --- a/test/manifests/segmentBase.js +++ b/test/manifests/segmentBase.js @@ -28,6 +28,7 @@ export const parsedManifest = { segments: [ { duration: 6, + timeline: 0, map: { uri: '', resolvedUri: 'https://www.example.com/1080p.ts' diff --git a/test/manifests/segmentBase.mpd b/test/manifests/segmentBase.mpd index e95fd1a4..609202a5 100644 --- a/test/manifests/segmentBase.mpd +++ b/test/manifests/segmentBase.mpd @@ -6,7 +6,7 @@ 1080p.ts - + diff --git a/test/segment/segmentBase.test.js b/test/segment/segmentBase.test.js new file mode 100644 index 00000000..7289656e --- /dev/null +++ b/test/segment/segmentBase.test.js @@ -0,0 +1,107 @@ +import QUnit from 'qunit'; +import { + segmentsFromBase +} from '../../src/segment/segmentBase'; +import errors from '../../src/errors'; + +QUnit.module('segmentBase - segmentsFromBase'); + +QUnit.test('sets segment to baseUrl', function(assert) { + const inputAttributes = { + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4' + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('sets duration based on sourceDuration', function(assert) { + const inputAttributes = { + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4', + sourceDuration: 10 + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + duration: 10, + timeline: 0, + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('sets duration based on sourceDuration and @timescale', function(assert) { + const inputAttributes = { + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4', + sourceDuration: 10, + timescale: 2 + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + duration: 5, + timeline: 0, + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('sets duration based on @duration', function(assert) { + const inputAttributes = { + duration: 10, + sourceDuration: 20, + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4' + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + duration: 10, + timeline: 0, + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('sets duration based on @duration and @timescale', function(assert) { + const inputAttributes = { + duration: 10, + sourceDuration: 20, + timescale: 5, + baseUrl: 'http://www.example.com/i.fmp4', + initialization: 'http://www.example.com/init.fmp4' + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + duration: 2, + timeline: 0, + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4' + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + +QUnit.test('errors if no baseUrl exists', function(assert) { + assert.throws(() => segmentsFromBase({}), new Error(errors.NO_BASE_URL)); +}); diff --git a/test/segmentList.test.js b/test/segment/segmentList.test.js similarity index 71% rename from test/segmentList.test.js rename to test/segment/segmentList.test.js index 50990211..8fadbc33 100644 --- a/test/segmentList.test.js +++ b/test/segment/segmentList.test.js @@ -1,8 +1,8 @@ import QUnit from 'qunit'; import { segmentsFromList -} from '../src/segment/segmentList'; -import errors from '../src/errors'; +} from '../../src/segment/segmentList'; +import errors from '../../src/errors'; QUnit.module('segmentList - segmentsFromList'); @@ -79,6 +79,127 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { }]); }); +QUnit.test('truncates if segmentTimeline does not apply for all segments', + function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + },{ + media: '3.fmp4', + },{ + media: '4.fmp4' + },{ + media: '5.fmp4', + } + ], + initialization: 'init.fmp4', + segmentTimeline: [{ + t: 1000, + d: 1000, + r: 1 + }], + periodIndex: 0, + startNumber: 1, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + }]); +}); + +QUnit.test('if segment timeline is too long does not add extra blank segments', + function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4', + },{ + media: '2.fmp4', + },{ + media: '3.fmp4', + },{ + media: '4.fmp4' + },{ + media: '5.fmp4', + } + ], + initialization: 'init.fmp4', + segmentTimeline: [{ + t: 1000, + d: 1000, + r: 10 + }], + periodIndex: 0, + startNumber: 1, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/3.fmp4', + timeline: 0, + uri: '3.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/4.fmp4', + timeline: 0, + uri: '4.fmp4' + },{ + duration: 1000, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }, + resolvedUri: 'http://example.com/5.fmp4', + timeline: 0, + uri: '5.fmp4' + }]); +}); + QUnit.test('uses duration to set segments', function(assert) { const inputAttributes = { segmentUrls: [{ @@ -330,10 +451,9 @@ QUnit.test('throws error if more than 1 segment and no duration or timeline', }; assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); - }); - +}); - QUnit.test('throws error if timeline and duration are both defined', function(assert) { +QUnit.test('throws error if timeline and duration are both defined', function(assert) { const inputAttributes = { segmentUrls: [{ media: '1.fmp4', diff --git a/test/segmentTemplate.test.js b/test/segment/segmentTemplate.test.js similarity index 99% rename from test/segmentTemplate.test.js rename to test/segment/segmentTemplate.test.js index 5fcebbbc..27435b0e 100644 --- a/test/segmentTemplate.test.js +++ b/test/segment/segmentTemplate.test.js @@ -3,7 +3,7 @@ import { constructTemplateUrl, parseTemplateInfo, segmentsFromTemplate -} from '../src/segment/segmentTemplate'; +} from '../../src/segment/segmentTemplate'; QUnit.module('segmentTemplate - constructTemplateUrl'); diff --git a/test/segmentBase.test.js b/test/segmentBase.test.js deleted file mode 100644 index 7fb973fa..00000000 --- a/test/segmentBase.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import QUnit from 'qunit'; -import { - segmentsFromBase -} from '../src/segment/segmentBase'; -import errors from '../src/errors'; - - -QUnit.module('segmentBase - segmentsFromBase'); - -QUnit.test('sets segment to baseUrl', function(assert) { - const inputAttributes = { - baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4' - }; - - assert.deepEqual(segmentsFromBase(inputAttributes), [{ - map: { - resolvedUri: 'http://www.example.com/init.fmp4', - uri: 'http://www.example.com/init.fmp4' - }, - resolvedUri: 'http://www.example.com/i.fmp4', - uri: 'http://www.example.com/i.fmp4' - }]); -}); - -QUnit.test('errors if no baseUrl exists', function(assert) { - assert.throws(() => segmentsFromBase({}), new Error(errors.NO_BASE_URL)); -}); From 1d5b64fb6a378f9bd1d98f9dc52fa03995f1e5f6 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Fri, 26 Jan 2018 17:26:33 -0500 Subject: [PATCH 05/12] uncomment some commented out tests, eslint fixes, package.json fix --- package.json | 1 + src/inheritAttributes.js | 19 +-- src/segment/segmentBase.js | 13 +- src/segment/segmentList.js | 27 ++-- src/segment/segmentTemplate.js | 1 - src/segment/timeParser.js | 2 +- src/toPlaylists.js | 2 +- test/index.test.js | 4 +- test/inheritAttributes.test.js | 3 +- test/manifests/segmentBase.js | 2 +- test/manifests/segmentList.js | 2 +- test/segment/segmentList.test.js | 260 +++++++++++++++---------------- test/toPlaylists.test.js | 64 ++++++-- 13 files changed, 217 insertions(+), 183 deletions(-) diff --git a/package.json b/package.json index 20cc8403..39a50679 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "prestart": "npm run build", "start": "run-p start:server test:server watch:js", "start:server": "static -a 0.0.0.0 -p 9999 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}' .", + "pretest": "npm run lint", "test": "karma start test/karma.conf.js", "test:server": "karma start test/karma.conf.js --singleRun=false --auto-watch --no-browsers", "watch:js": "rollup -c scripts/rollup.config.js -w", diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 27c7726a..0a65d340 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -50,16 +50,12 @@ 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 segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; - - // OSHIN TODO: Handle this cleaner as this is used in both template and list - const segmentListTimeline = - segmentList && findChildren(segmentList, 'SegmentTimeline')[0]; - const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL'); + const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; + const segmentTimelineNode = segmentList ? segmentList : segmentTemplate; + const segmentTimeline = + segmentTimelineNode && findChildren(segmentTimelineNode, 'SegmentTimeline')[0]; return { template: segmentTemplate && getAttributes(segmentTemplate), @@ -69,9 +65,8 @@ export const getSegmentInformation = (adaptationSet) => { shallowMerge(getAttributes(segmentList), { segmentUrls: segmentUrls && - segmentUrls.map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))), - segmentTimeline: segmentListTimeline && - findChildren(segmentListTimeline, 'S').map(s => getAttributes(s)) + segmentUrls.map(s => + shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))) }), base: segmentBase && getAttributes(segmentBase) }; @@ -158,7 +153,7 @@ export const toRepresentations = const attrs = shallowMerge(periodAttributes, adaptationSetAttributes, roleAttributes); - let segmentInfo = getSegmentInformation(adaptationSet); + const segmentInfo = getSegmentInformation(adaptationSet); const representations = findChildren(adaptationSet, 'Representation'); return flatten( diff --git a/src/segment/segmentBase.js b/src/segment/segmentBase.js index d2ffb609..62e0e81c 100644 --- a/src/segment/segmentBase.js +++ b/src/segment/segmentBase.js @@ -2,7 +2,6 @@ import resolveUrl from '../utils/resolveUrl'; import errors from '../errors'; import { parseByDuration } from './timeParser'; - /** * Translates SegmentBase into a set of segments. * (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each @@ -11,6 +10,7 @@ import { parseByDuration } from './timeParser'; * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys + * @return {Object.} list of segments */ export const segmentsFromBase = (attributes) => { const { @@ -22,8 +22,6 @@ export const segmentsFromBase = (attributes) => { periodIndex = 0, duration } = attributes; - const parsedTimescale = parseInt(timescale, 10); - const start = parseInt(startNumber, 10); // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) if (!baseUrl) { @@ -38,13 +36,13 @@ export const segmentsFromBase = (attributes) => { resolvedUri: resolveUrl(attributes.baseUrl || '', ''), uri: attributes.baseUrl }; + 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 parsedTimescale = parseInt(timescale, 10); const segmentTimeInfo = parseByDuration(start, periodIndex, parsedTimescale, @@ -55,11 +53,10 @@ export const segmentsFromBase = (attributes) => { segment.duration = segmentTimeInfo[0].duration; segment.timeline = segmentTimeInfo[0].timeline; } - } - else if (sourceDuration) { - segment.duration = (sourceDuration/ parsedTimescale); + } else if (sourceDuration) { + segment.duration = (sourceDuration / parsedTimescale); segment.timeline = 0; } return [segment]; -}; \ No newline at end of file +}; diff --git a/src/segment/segmentList.js b/src/segment/segmentList.js index 38d2e2c5..62e6fe68 100644 --- a/src/segment/segmentList.js +++ b/src/segment/segmentList.js @@ -1,4 +1,3 @@ -import { range } from '../utils/list'; import resolveUrl from '../utils/resolveUrl'; import { parseByDuration, parseByTimeline } from './timeParser'; import errors from '../errors'; @@ -12,6 +11,7 @@ import errors from '../errors'; * names as keys * @param {Object} segmentUrl * node to translate into a segment object + * @return {Object} translated segment object */ const SegmentURLToSegmentObject = (attributes, segmentUrl) => { const initUri = attributes.initialization || ''; @@ -23,19 +23,19 @@ const SegmentURLToSegmentObject = (attributes, segmentUrl) => { }, resolvedUri: resolveUrl(attributes.baseUrl || '', segmentUrl.media), uri: segmentUrl.media - } + }; // Follows byte-range-spec per RFC2616 // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 if (segmentUrl.mediaRange) { const ranges = segmentUrl.mediaRange.split('-'); - const startRange = parseInt(ranges[0]); - const endRange = parseInt(ranges[1]); + const startRange = parseInt(ranges[0], 10); + const endRange = parseInt(ranges[1], 10); segment.byterange = { length: endRange - startRange, offset: startRange - } + }; } return segment; @@ -49,13 +49,16 @@ const SegmentURLToSegmentObject = (attributes, segmentUrl) => { * @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 segments */ -export const segmentsFromList = (attributes) => { +export const segmentsFromList = (attributes, segmentTimeline) => { const { timescale = 1, duration, segmentUrls = [], - segmentTimeline, periodIndex = 0, startNumber = 1 } = attributes; @@ -75,7 +78,6 @@ export const segmentsFromList = (attributes) => { if (duration) { const parsedDuration = parseInt(duration, 10); - const segmentDuration = (parsedDuration / parsedTimescale); segmentTimeInfo = parseByDuration(start, periodIndex, @@ -95,14 +97,15 @@ export const segmentsFromList = (attributes) => { 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). + // 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; -}; \ No newline at end of file +}; diff --git a/src/segment/segmentTemplate.js b/src/segment/segmentTemplate.js index a4af4348..a2fef420 100644 --- a/src/segment/segmentTemplate.js +++ b/src/segment/segmentTemplate.js @@ -1,4 +1,3 @@ -import { range } from '../utils/list'; import resolveUrl from '../utils/resolveUrl'; import { parseByDuration, parseByTimeline } from './timeParser'; diff --git a/src/segment/timeParser.js b/src/segment/timeParser.js index 6f4bcf98..5f8a1402 100644 --- a/src/segment/timeParser.js +++ b/src/segment/timeParser.js @@ -102,4 +102,4 @@ export const parseByDuration = (start, timeline, timescale, duration, sourceDura return segment; }); -}; \ No newline at end of file +}; diff --git a/src/toPlaylists.js b/src/toPlaylists.js index 9c405382..642231e4 100644 --- a/src/toPlaylists.js +++ b/src/toPlaylists.js @@ -18,7 +18,7 @@ export const generateSegments = (segmentInfo, attributes) => { if (segmentInfo.list) { return segmentsFromList( - shallowMerge(segmentInfo.list, attributes) + shallowMerge(segmentInfo.list, attributes), segmentInfo.timeline ); } }; diff --git a/test/index.test.js b/test/index.test.js index 0b7fe211..a90d9d5b 100755 --- a/test/index.test.js +++ b/test/index.test.js @@ -28,11 +28,11 @@ QUnit.test('has parse', function(assert) { name: 'maat_vtt_segmentTemplate', input: maatVttSegmentTemplate, expected: maatVttSegmentTemplateManifest -},{ +}, { name: 'segmentBase', input: segmentBaseTemplate, expected: segmentBaseManifest -},{ +}, { name: 'segmentList', input: segmentListTemplate, expected: segmentListManifest diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index 234ccb6d..c6d11a83 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -115,10 +115,9 @@ QUnit.test('gets SegmentList attributes', function(assert) { timeline: void 0, list: { duration: '10', - segmentTimeline: void 0, segmentUrls: [] }, - base: void 0, + base: void 0 }; assert.deepEqual(getSegmentInformation(adaptationSet), expected, diff --git a/test/manifests/segmentBase.js b/test/manifests/segmentBase.js index a26fb376..b99f6130 100644 --- a/test/manifests/segmentBase.js +++ b/test/manifests/segmentBase.js @@ -43,4 +43,4 @@ export const parsedManifest = { ], segments: [], uri: '' -}; \ No newline at end of file +}; diff --git a/test/manifests/segmentList.js b/test/manifests/segmentList.js index ecd1595d..787f162b 100644 --- a/test/manifests/segmentList.js +++ b/test/manifests/segmentList.js @@ -213,4 +213,4 @@ export const parsedManifest = { ], segments: [], uri: '' -}; \ No newline at end of file +}; diff --git a/test/segment/segmentList.test.js b/test/segment/segmentList.test.js index 8fadbc33..5babe85d 100644 --- a/test/segment/segmentList.test.js +++ b/test/segment/segmentList.test.js @@ -8,30 +8,30 @@ QUnit.module('segmentList - segmentsFromList'); QUnit.test('uses segmentTimeline to set segments', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - },{ - media: '3.fmp4', - },{ - media: '4.fmp4' - },{ - media: '5.fmp4', - } - ], - initialization: 'init.fmp4', - segmentTimeline: [{ - t: 1000, - d: 1000, - r: 4 + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }, { + media: '3.fmp4' + }, { + media: '4.fmp4' + }, { + media: '5.fmp4' }], + initialization: 'init.fmp4', periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' }; - assert.deepEqual(segmentsFromList(inputAttributes), [{ + const inputTimeline = [{ + t: 1000, + d: 1000, + r: 4 + }]; + + assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{ duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -40,7 +40,7 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -49,7 +49,7 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { resolvedUri: 'http://example.com/2.fmp4', timeline: 0, uri: '2.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -58,7 +58,7 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { resolvedUri: 'http://example.com/3.fmp4', timeline: 0, uri: '3.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -67,7 +67,7 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { resolvedUri: 'http://example.com/4.fmp4', timeline: 0, uri: '4.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -82,30 +82,30 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { QUnit.test('truncates if segmentTimeline does not apply for all segments', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - },{ - media: '3.fmp4', - },{ - media: '4.fmp4' - },{ - media: '5.fmp4', - } - ], - initialization: 'init.fmp4', - segmentTimeline: [{ - t: 1000, - d: 1000, - r: 1 + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }, { + media: '3.fmp4' + }, { + media: '4.fmp4' + }, { + media: '5.fmp4' }], + initialization: 'init.fmp4', periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' }; - assert.deepEqual(segmentsFromList(inputAttributes), [{ + const inputTimeline = [{ + t: 1000, + d: 1000, + r: 1 + }]; + + assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{ duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -114,7 +114,7 @@ QUnit.test('truncates if segmentTimeline does not apply for all segments', resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -124,35 +124,35 @@ QUnit.test('truncates if segmentTimeline does not apply for all segments', timeline: 0, uri: '2.fmp4' }]); -}); + }); QUnit.test('if segment timeline is too long does not add extra blank segments', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - },{ - media: '3.fmp4', - },{ - media: '4.fmp4' - },{ - media: '5.fmp4', - } - ], - initialization: 'init.fmp4', - segmentTimeline: [{ - t: 1000, - d: 1000, - r: 10 + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }, { + media: '3.fmp4' + }, { + media: '4.fmp4' + }, { + media: '5.fmp4' }], + initialization: 'init.fmp4', periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' }; - assert.deepEqual(segmentsFromList(inputAttributes), [{ + const inputTimeline = [{ + t: 1000, + d: 1000, + r: 10 + }]; + + assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{ duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -161,7 +161,7 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -170,7 +170,7 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', resolvedUri: 'http://example.com/2.fmp4', timeline: 0, uri: '2.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -179,7 +179,7 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', resolvedUri: 'http://example.com/3.fmp4', timeline: 0, uri: '3.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -188,7 +188,7 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', resolvedUri: 'http://example.com/4.fmp4', timeline: 0, uri: '4.fmp4' - },{ + }, { duration: 1000, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -198,22 +198,21 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', timeline: 0, uri: '5.fmp4' }]); -}); + }); QUnit.test('uses duration to set segments', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - },{ - media: '3.fmp4', - },{ - media: '4.fmp4' - },{ - media: '5.fmp4', - } - ], + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }, { + media: '3.fmp4' + }, { + media: '4.fmp4' + }, { + media: '5.fmp4' + }], initialization: 'init.fmp4', duration: 10, periodIndex: 0, @@ -231,7 +230,7 @@ QUnit.test('uses duration to set segments', function(assert) { resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 10, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -240,7 +239,7 @@ QUnit.test('uses duration to set segments', function(assert) { resolvedUri: 'http://example.com/2.fmp4', timeline: 0, uri: '2.fmp4' - },{ + }, { duration: 10, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -249,7 +248,7 @@ QUnit.test('uses duration to set segments', function(assert) { resolvedUri: 'http://example.com/3.fmp4', timeline: 0, uri: '3.fmp4' - },{ + }, { duration: 10, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -258,7 +257,7 @@ QUnit.test('uses duration to set segments', function(assert) { resolvedUri: 'http://example.com/4.fmp4', timeline: 0, uri: '4.fmp4' - },{ + }, { duration: 10, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -272,18 +271,17 @@ QUnit.test('uses duration to set segments', function(assert) { QUnit.test('uses timescale to set segment duration', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - },{ - media: '3.fmp4', - },{ - media: '4.fmp4' - },{ - media: '5.fmp4', - } - ], + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }, { + media: '3.fmp4' + }, { + media: '4.fmp4' + }, { + media: '5.fmp4' + }], initialization: 'init.fmp4', duration: 10, timescale: 2, @@ -302,7 +300,7 @@ QUnit.test('uses timescale to set segment duration', function(assert) { resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 5, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -311,7 +309,7 @@ QUnit.test('uses timescale to set segment duration', function(assert) { resolvedUri: 'http://example.com/2.fmp4', timeline: 0, uri: '2.fmp4' - },{ + }, { duration: 5, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -320,7 +318,7 @@ QUnit.test('uses timescale to set segment duration', function(assert) { resolvedUri: 'http://example.com/3.fmp4', timeline: 0, uri: '3.fmp4' - },{ + }, { duration: 5, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -329,7 +327,7 @@ QUnit.test('uses timescale to set segment duration', function(assert) { resolvedUri: 'http://example.com/4.fmp4', timeline: 0, uri: '4.fmp4' - },{ + }, { duration: 5, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -343,12 +341,11 @@ QUnit.test('uses timescale to set segment duration', function(assert) { QUnit.test('timescale sets duration of last segment correctly', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - } - ], + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }], initialization: 'init.fmp4', duration: 10, timescale: 1, @@ -367,7 +364,7 @@ QUnit.test('timescale sets duration of last segment correctly', function(assert) resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 5, map: { resolvedUri: 'http://example.com/init.fmp4', @@ -381,14 +378,13 @@ QUnit.test('timescale sets duration of last segment correctly', function(assert) QUnit.test('segmentUrl translates ranges correctly', function(assert) { const inputAttributes = { - segmentUrls: [{ - media: '1.fmp4', - mediaRange: '0-200' - },{ - media: '1.fmp4', - mediaRange: '201-400' - } - ], + segmentUrls: [{ + media: '1.fmp4', + mediaRange: '0-200' + }, { + media: '1.fmp4', + mediaRange: '201-400' + }], initialization: 'init.fmp4', duration: 10, timescale: 1, @@ -411,7 +407,7 @@ QUnit.test('segmentUrl translates ranges correctly', function(assert) { resolvedUri: 'http://example.com/1.fmp4', timeline: 0, uri: '1.fmp4' - },{ + }, { duration: 10, byterange: { length: 199, @@ -431,17 +427,11 @@ QUnit.test('throws error if more than 1 segment and no duration or timeline', function(assert) { const inputAttributes = { segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - } - ], - duration: 10, - segmentTimeline: [{ - t: 1000, - d: 1000, - r: 4 + media: '1.fmp4' + }, { + media: '2.fmp4' }], + duration: 10, initialization: 'init.fmp4', timescale: 1, periodIndex: 0, @@ -450,17 +440,23 @@ QUnit.test('throws error if more than 1 segment and no duration or timeline', baseUrl: 'http://example.com/' }; - assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); -}); + const inputTimeline = [{ + t: 1000, + d: 1000, + r: 4 + }]; + + assert.throws(() => segmentsFromList(inputAttributes, inputTimeline), + new Error(errors.SEGMENT_TIME_UNSPECIFIED)); + }); QUnit.test('throws error if timeline and duration are both defined', function(assert) { const inputAttributes = { segmentUrls: [{ - media: '1.fmp4', - },{ - media: '2.fmp4', - } - ], + media: '1.fmp4' + }, { + media: '2.fmp4' + }], initialization: 'init.fmp4', timescale: 1, periodIndex: 0, @@ -469,6 +465,6 @@ QUnit.test('throws error if timeline and duration are both defined', function(as baseUrl: 'http://example.com/' }; - assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); + assert.throws(() => segmentsFromList(inputAttributes), + new Error(errors.SEGMENT_TIME_UNSPECIFIED)); }); - diff --git a/test/toPlaylists.test.js b/test/toPlaylists.test.js index 709afcce..5e30f410 100644 --- a/test/toPlaylists.test.js +++ b/test/toPlaylists.test.js @@ -1,7 +1,6 @@ import { toPlaylists } from '../src/toPlaylists'; -import errors from '../src/errors'; import QUnit from 'qunit'; QUnit.module('toPlaylists'); @@ -35,28 +34,73 @@ QUnit.test('pretty simple', function(assert) { assert.deepEqual(toPlaylists(representations), playlists); }); -/* QUnit.test('segment base', function(assert) { const representations = [{ - attributes: {}, + attributes: { baseUrl: 'http://example.com/' }, segmentInfo: { base: true } }]; - assert.throws(() => toPlaylists(representations), - new RegExp(errors.UNSUPPORTED_SEGMENTATION_TYPE)); + const playlists = [{ + attributes: { baseUrl: 'http://example.com/' }, + segments: [{ + map: { + resolvedUri: 'http://example.com/', + uri: '' + }, + resolvedUri: 'http://example.com/', + uri: 'http://example.com/' + }] + }]; + + assert.deepEqual(toPlaylists(representations), playlists); }); QUnit.test('segment list', function(assert) { const representations = [{ - attributes: {}, + attributes: { + baseUrl: 'http://example.com/', + duration: 10, + sourceDuration: 11 + }, segmentInfo: { - list: true + list: { + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '2.fmp4' + }] + } } }]; - assert.throws(() => toPlaylists(representations), - new RegExp(errors.UNSUPPORTED_SEGMENTATION_TYPE)); + const playlists = [{ + attributes: { + baseUrl: 'http://example.com/', + duration: 10, + sourceDuration: 11 + }, + segments: [{ + duration: 10, + map: { + resolvedUri: 'http://example.com/', + uri: '' + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + }, { + duration: 1, + map: { + resolvedUri: 'http://example.com/', + uri: '' + }, + resolvedUri: 'http://example.com/2.fmp4', + timeline: 0, + uri: '2.fmp4' + }] + }]; + + assert.deepEqual(toPlaylists(representations), playlists); }); -*/ From e7469b630b66c7e9e48f0eb7a72f5d9e898bcace Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Fri, 26 Jan 2018 17:32:11 -0500 Subject: [PATCH 06/12] Add parantheses to make this clearer --- src/segment/segmentList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/segment/segmentList.js b/src/segment/segmentList.js index 62e6fe68..f6721a63 100644 --- a/src/segment/segmentList.js +++ b/src/segment/segmentList.js @@ -66,7 +66,7 @@ export const segmentsFromList = (attributes, segmentTimeline) => { // 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) { + (duration && segmentTimeline)) { throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); } From 2e4b4ecd6ccc1244e2a5999e1068b272b6c14a1d Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Mon, 29 Jan 2018 13:58:47 -0500 Subject: [PATCH 07/12] Code review updates --- src/errors.js | 1 - src/toPlaylists.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/errors.js b/src/errors.js index f8d511d5..e6845627 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,7 +2,6 @@ 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' diff --git a/src/toPlaylists.js b/src/toPlaylists.js index 642231e4..6d5a95d8 100644 --- a/src/toPlaylists.js +++ b/src/toPlaylists.js @@ -11,7 +11,6 @@ export const generateSegments = (segmentInfo, attributes) => { ); } - // TODO if (segmentInfo.base) { return segmentsFromBase(shallowMerge(segmentInfo.base, attributes)); } From 4539d9cc3132ebe4f3a2635772c8338ea790627a Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Tue, 30 Jan 2018 15:15:31 -0500 Subject: [PATCH 08/12] Add parsing for Initialization nodes --- src/inheritAttributes.js | 41 ++++++++++++------ src/segment/segmentBase.js | 22 +++++----- src/segment/segmentList.js | 34 ++++++--------- src/segment/segmentTemplate.js | 27 +++++++++--- src/segment/urlType.js | 47 ++++++++++++++++++++ test/inheritAttributes.test.js | 8 ++-- test/segment/segmentBase.test.js | 38 ++++++++++++++--- test/segment/segmentList.test.js | 63 +++++++++++++++++++++++---- test/segment/segmentTemplate.test.js | 64 ++++++++++++++++++++++++++++ 9 files changed, 279 insertions(+), 65 deletions(-) create mode 100644 src/segment/urlType.js diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 0a65d340..00288ec7 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -51,24 +51,41 @@ export const buildBaseUrls = (referenceUrls, baseUrlElements) => { export const getSegmentInformation = (adaptationSet) => { const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; const segmentList = findChildren(adaptationSet, 'SegmentList')[0]; - const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL'); + const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL') + .map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))); const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; - const segmentTimelineNode = segmentList ? segmentList : segmentTemplate; + const segmentTimelineNode = segmentList || segmentTemplate; const segmentTimeline = segmentTimelineNode && findChildren(segmentTimelineNode, 'SegmentTimeline')[0]; + const segmentInitializationNode = segmentList || segmentBase || segmentTemplate; + const segmentInitialization = segmentInitializationNode && + findChildren(segmentInitializationNode, 'Initialization')[0]; + + // SegmentTemplate is handled slightly differently, since it can have both @initialization + // and an node. @initialization can be templated, while the node can have a + // url and range specified. If the has both @initialization and + // an 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 = getAttributes(segmentInitialization); + } return { - template: segmentTemplate && getAttributes(segmentTemplate), + template, timeline: segmentTimeline && - findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), - list: segmentList && - shallowMerge(getAttributes(segmentList), - { - segmentUrls: segmentUrls && - segmentUrls.map(s => - shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))) - }), - 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) + }) }; }; diff --git a/src/segment/segmentBase.js b/src/segment/segmentBase.js index 62e0e81c..556b79cf 100644 --- a/src/segment/segmentBase.js +++ b/src/segment/segmentBase.js @@ -1,5 +1,5 @@ -import resolveUrl from '../utils/resolveUrl'; import errors from '../errors'; +import urlTypeConverter from './urlType'; import { parseByDuration } from './timeParser'; /** @@ -15,11 +15,12 @@ import { parseByDuration } from './timeParser'; export const segmentsFromBase = (attributes) => { const { baseUrl, - initialization = '', + initialization = {}, sourceDuration, timescale = 1, startNumber = 1, periodIndex = 0, + indexRange = '', duration } = attributes; @@ -28,14 +29,15 @@ export const segmentsFromBase = (attributes) => { throw new Error(errors.NO_BASE_URL); } - const segment = { - map: { - uri: initialization, - resolvedUri: resolveUrl(attributes.baseUrl || '', initialization) - }, - resolvedUri: resolveUrl(attributes.baseUrl || '', ''), - uri: attributes.baseUrl - }; + 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 diff --git a/src/segment/segmentList.js b/src/segment/segmentList.js index f6721a63..f892c25a 100644 --- a/src/segment/segmentList.js +++ b/src/segment/segmentList.js @@ -1,5 +1,5 @@ -import resolveUrl from '../utils/resolveUrl'; import { parseByDuration, parseByTimeline } from './timeParser'; +import urlTypeConverter from './urlType'; import errors from '../errors'; /** @@ -14,29 +14,21 @@ import errors from '../errors'; * @return {Object} translated segment object */ const SegmentURLToSegmentObject = (attributes, segmentUrl) => { - const initUri = attributes.initialization || ''; + const { baseUrl, initialization = {} } = attributes; - const segment = { - map: { - uri: initUri, - resolvedUri: resolveUrl(attributes.baseUrl || '', initUri) - }, - resolvedUri: resolveUrl(attributes.baseUrl || '', segmentUrl.media), - uri: segmentUrl.media - }; + const initSegment = urlTypeConverter({ + baseUrl, + source: initialization.sourceURL, + range: initialization.range + }); - // Follows byte-range-spec per RFC2616 - // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 - if (segmentUrl.mediaRange) { - const ranges = segmentUrl.mediaRange.split('-'); - const startRange = parseInt(ranges[0], 10); - const endRange = parseInt(ranges[1], 10); + const segment = urlTypeConverter({ + baseUrl, + source: segmentUrl.media, + range: segmentUrl.mediaRange + }); - segment.byterange = { - length: endRange - startRange, - offset: startRange - }; - } + segment.map = initSegment; return segment; }; diff --git a/src/segment/segmentTemplate.js b/src/segment/segmentTemplate.js index a2fef420..dcd90fd1 100644 --- a/src/segment/segmentTemplate.js +++ b/src/segment/segmentTemplate.js @@ -1,4 +1,5 @@ import resolveUrl from '../utils/resolveUrl'; +import urlTypeToSegment from './urlType'; import { parseByDuration, parseByTimeline } from './timeParser'; const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; @@ -151,7 +152,26 @@ export const segmentsFromTemplate = (attributes, segmentTimeline) => { RepresentationID: attributes.id, Bandwidth: parseInt(attributes.bandwidth || 0, 10) }; - const mapUri = constructTemplateUrl(attributes.initialization || '', templateValues); + + let mapSegment = { uri: '', resolvedUri: resolveUrl(attributes.baseUrl || '', '') }; + + if (attributes.initialization && typeof attributes.initialization === 'string') { + const mapUri = constructTemplateUrl(attributes.initialization || '', templateValues); + + mapSegment = { + uri: mapUri, + resolvedUri: resolveUrl(attributes.baseUrl || '', mapUri) + }; + } + + if (attributes.initialization && typeof attributes.initialization === 'object') { + mapSegment = urlTypeToSegment({ + baseUrl: attributes.baseUrl, + source: attributes.initialization.sourceURL, + range: attributes.initialization.range + }); + } + const segments = parseTemplateInfo(attributes, segmentTimeline); return segments.map(segment => { @@ -165,10 +185,7 @@ export const segmentsFromTemplate = (attributes, segmentTimeline) => { timeline: segment.timeline, duration: segment.duration, resolvedUri: resolveUrl(attributes.baseUrl || '', uri), - map: { - uri: mapUri, - resolvedUri: resolveUrl(attributes.baseUrl || '', mapUri) - } + map: mapSegment }; }); }; diff --git a/src/segment/urlType.js b/src/segment/urlType.js new file mode 100644 index 00000000..85725754 --- /dev/null +++ b/src/segment/urlType.js @@ -0,0 +1,47 @@ +import resolveUrl from '../utils/resolveUrl'; + +/** + * @typedef {Object} SingleUri + * @property {string} uri - relative location of segment + * @property {string} resolvedUri - resolved location of segment + * @property {Object} byterange - Object containing information on how to make byte range + * requests following byte-range-spec per RFC2616. + * @property {String} byterange.length - length of range request + * @property {String} byterange.offset - byte offset of range request + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 + */ + +/** + * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object + * that conforms to how m3u8-parser is structured + * + * @see https://github.com/videojs/m3u8-parser + * + * @param {string} baseUrl - baseUrl provided by nodes + * @param {string} source - source url for segment + * @param {string} range - optional range used for range calls, follows + * @return {SingleUri} full segment information transformed into a format similar + * to m3u8-parser + */ +export const urlTypeToSegment = ({ baseUrl = '', source = '', range = '' }) => { + const init = { + uri: source, + resolvedUri: resolveUrl(baseUrl || '', source) + }; + + if (source && range) { + const ranges = range.split('-'); + const startRange = parseInt(ranges[0], 10); + const endRange = parseInt(ranges[1], 10); + + init.byterange = { + length: endRange - startRange, + offset: startRange + }; + } + + return init; +}; + +export default urlTypeToSegment; diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index c6d11a83..aa04067f 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -115,7 +115,8 @@ QUnit.test('gets SegmentList attributes', function(assert) { timeline: void 0, list: { duration: '10', - segmentUrls: [] + segmentUrls: [], + initialization: {} }, base: void 0 }; @@ -128,14 +129,15 @@ QUnit.test('gets SegmentBase attributes', function(assert) { const adaptationSet = { childNodes: [{ tagName: 'SegmentBase', - attributes: [{ name: 'duration', value: '10' }] + attributes: [{ name: 'duration', value: '10' }], + childNodes: [] }] }; const expected = { template: void 0, timeline: void 0, list: void 0, - base: { duration: '10' } + base: { duration: '10', initialization: {} } }; assert.deepEqual(getSegmentInformation(adaptationSet), expected, diff --git a/test/segment/segmentBase.test.js b/test/segment/segmentBase.test.js index 7289656e..5050f844 100644 --- a/test/segment/segmentBase.test.js +++ b/test/segment/segmentBase.test.js @@ -9,7 +9,7 @@ QUnit.module('segmentBase - segmentsFromBase'); QUnit.test('sets segment to baseUrl', function(assert) { const inputAttributes = { baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4' + initialization: { sourceURL: 'http://www.example.com/init.fmp4' } }; assert.deepEqual(segmentsFromBase(inputAttributes), [{ @@ -25,7 +25,7 @@ QUnit.test('sets segment to baseUrl', function(assert) { QUnit.test('sets duration based on sourceDuration', function(assert) { const inputAttributes = { baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4', + initialization: { sourceURL: 'http://www.example.com/init.fmp4' }, sourceDuration: 10 }; @@ -44,7 +44,7 @@ QUnit.test('sets duration based on sourceDuration', function(assert) { QUnit.test('sets duration based on sourceDuration and @timescale', function(assert) { const inputAttributes = { baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4', + initialization: { sourceURL: 'http://www.example.com/init.fmp4' }, sourceDuration: 10, timescale: 2 }; @@ -66,7 +66,7 @@ QUnit.test('sets duration based on @duration', function(assert) { duration: 10, sourceDuration: 20, baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4' + initialization: { sourceURL: 'http://www.example.com/init.fmp4' } }; assert.deepEqual(segmentsFromBase(inputAttributes), [{ @@ -87,7 +87,7 @@ QUnit.test('sets duration based on @duration and @timescale', function(assert) { sourceDuration: 20, timescale: 5, baseUrl: 'http://www.example.com/i.fmp4', - initialization: 'http://www.example.com/init.fmp4' + initialization: { sourceURL: 'http://www.example.com/init.fmp4' } }; assert.deepEqual(segmentsFromBase(inputAttributes), [{ @@ -102,6 +102,34 @@ QUnit.test('sets duration based on @duration and @timescale', function(assert) { }]); }); +QUnit.test('translates ranges in node', function(assert) { + const inputAttributes = { + duration: 10, + sourceDuration: 20, + timescale: 5, + baseUrl: 'http://www.example.com/i.fmp4', + initialization: { + sourceURL: 'http://www.example.com/init.fmp4', + range: '121-125' + } + }; + + assert.deepEqual(segmentsFromBase(inputAttributes), [{ + duration: 2, + timeline: 0, + map: { + resolvedUri: 'http://www.example.com/init.fmp4', + uri: 'http://www.example.com/init.fmp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'http://www.example.com/i.fmp4', + uri: 'http://www.example.com/i.fmp4' + }]); +}); + QUnit.test('errors if no baseUrl exists', function(assert) { assert.throws(() => segmentsFromBase({}), new Error(errors.NO_BASE_URL)); }); diff --git a/test/segment/segmentList.test.js b/test/segment/segmentList.test.js index 5babe85d..af2128ca 100644 --- a/test/segment/segmentList.test.js +++ b/test/segment/segmentList.test.js @@ -19,7 +19,7 @@ QUnit.test('uses segmentTimeline to set segments', function(assert) { }, { media: '5.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' @@ -93,7 +93,7 @@ QUnit.test('truncates if segmentTimeline does not apply for all segments', }, { media: '5.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' @@ -140,7 +140,7 @@ QUnit.test('if segment timeline is too long does not add extra blank segments', }, { media: '5.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, periodIndex: 0, startNumber: 1, baseUrl: 'http://example.com/' @@ -213,7 +213,7 @@ QUnit.test('uses duration to set segments', function(assert) { }, { media: '5.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, duration: 10, periodIndex: 0, startNumber: 1, @@ -282,7 +282,7 @@ QUnit.test('uses timescale to set segment duration', function(assert) { }, { media: '5.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, duration: 10, timescale: 2, periodIndex: 0, @@ -346,7 +346,7 @@ QUnit.test('timescale sets duration of last segment correctly', function(assert) }, { media: '2.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, duration: 10, timescale: 1, periodIndex: 0, @@ -385,7 +385,7 @@ QUnit.test('segmentUrl translates ranges correctly', function(assert) { media: '1.fmp4', mediaRange: '201-400' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, duration: 10, timescale: 1, periodIndex: 0, @@ -432,7 +432,7 @@ QUnit.test('throws error if more than 1 segment and no duration or timeline', media: '2.fmp4' }], duration: 10, - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, timescale: 1, periodIndex: 0, startNumber: 1, @@ -457,7 +457,7 @@ QUnit.test('throws error if timeline and duration are both defined', function(as }, { media: '2.fmp4' }], - initialization: 'init.fmp4', + initialization: { sourceURL: 'init.fmp4' }, timescale: 1, periodIndex: 0, startNumber: 1, @@ -468,3 +468,48 @@ QUnit.test('throws error if timeline and duration are both defined', function(as assert.throws(() => segmentsFromList(inputAttributes), new Error(errors.SEGMENT_TIME_UNSPECIFIED)); }); + +QUnit.test('translates ranges in node', function(assert) { + const inputAttributes = { + segmentUrls: [{ + media: '1.fmp4' + }, { + media: '1.fmp4' + }], + initialization: { sourceURL: 'init.fmp4', range: '121-125' }, + duration: 10, + timescale: 1, + periodIndex: 0, + startNumber: 1, + sourceDuration: 20, + baseUrl: 'http://example.com/' + }; + + assert.deepEqual(segmentsFromList(inputAttributes), [{ + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + }, { + duration: 10, + map: { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'http://example.com/1.fmp4', + timeline: 0, + uri: '1.fmp4' + }]); +}); diff --git a/test/segment/segmentTemplate.test.js b/test/segment/segmentTemplate.test.js index 27435b0e..36310853 100644 --- a/test/segment/segmentTemplate.test.js +++ b/test/segment/segmentTemplate.test.js @@ -740,3 +740,67 @@ QUnit.test('constructs simple segment list and resolves uris', function(assert) 'creates segments from template'); }); +QUnit.test('constructs simple segment list and with node', function(assert) { + const attributes = { + startNumber: '0', + duration: '6000', + sourceDuration: 16, + timescale: '1000', + bandwidth: '100', + id: 'Rep1', + initialization: { + sourceURL: 'init.mp4', + range: '121-125' + }, + media: '$RepresentationID$/$Bandwidth$/$Number%03d$-$Time%05d$.mp4', + periodIndex: 1, + baseUrl: 'https://example.com/' + }; + const segments = [ + { + duration: 6, + map: { + resolvedUri: 'https://example.com/init.mp4', + uri: 'init.mp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'https://example.com/Rep1/100/000-00000.mp4', + timeline: 1, + uri: 'Rep1/100/000-00000.mp4' + }, + { + duration: 6, + map: { + resolvedUri: 'https://example.com/init.mp4', + uri: 'init.mp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'https://example.com/Rep1/100/001-06000.mp4', + timeline: 1, + uri: 'Rep1/100/001-06000.mp4' + }, + { + duration: 4, + map: { + resolvedUri: 'https://example.com/init.mp4', + uri: 'init.mp4', + byterange: { + length: 4, + offset: 121 + } + }, + resolvedUri: 'https://example.com/Rep1/100/002-12000.mp4', + timeline: 1, + uri: 'Rep1/100/002-12000.mp4' + } + ]; + + assert.deepEqual(segmentsFromTemplate(attributes, void 0), segments, + 'creates segments from template'); +}); From 9b1aad78b3adb726750e98a2609b8996dfc159ff Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Tue, 30 Jan 2018 18:27:18 -0500 Subject: [PATCH 09/12] Code review updates and add tests for urlType --- src/inheritAttributes.js | 6 +++- src/segment/segmentTemplate.js | 23 ++++--------- src/segment/urlType.js | 2 +- test/segment/segmentTemplate.test.js | 4 ++- test/segment/urlType.test.js | 51 ++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 test/segment/urlType.test.js diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 00288ec7..cd7f809c 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -69,7 +69,11 @@ export const getSegmentInformation = (adaptationSet) => { const template = segmentTemplate && getAttributes(segmentTemplate); if (template && segmentInitialization) { - template.initialization = getAttributes(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 + template.initialization = { sourceURL: template.initialization }; } return { diff --git a/src/segment/segmentTemplate.js b/src/segment/segmentTemplate.js index dcd90fd1..c22559b1 100644 --- a/src/segment/segmentTemplate.js +++ b/src/segment/segmentTemplate.js @@ -153,24 +153,13 @@ export const segmentsFromTemplate = (attributes, segmentTimeline) => { Bandwidth: parseInt(attributes.bandwidth || 0, 10) }; - let mapSegment = { uri: '', resolvedUri: resolveUrl(attributes.baseUrl || '', '') }; + const { initialization = { sourceURL: '', range: '' } } = attributes; - if (attributes.initialization && typeof attributes.initialization === 'string') { - const mapUri = constructTemplateUrl(attributes.initialization || '', templateValues); - - mapSegment = { - uri: mapUri, - resolvedUri: resolveUrl(attributes.baseUrl || '', mapUri) - }; - } - - if (attributes.initialization && typeof attributes.initialization === 'object') { - mapSegment = urlTypeToSegment({ - baseUrl: attributes.baseUrl, - source: attributes.initialization.sourceURL, - range: attributes.initialization.range - }); - } + const mapSegment = urlTypeToSegment({ + baseUrl: attributes.baseUrl, + source: constructTemplateUrl(initialization.sourceURL, templateValues), + range: initialization.range + }); const segments = parseTemplateInfo(attributes, segmentTimeline); diff --git a/src/segment/urlType.js b/src/segment/urlType.js index 85725754..83df358c 100644 --- a/src/segment/urlType.js +++ b/src/segment/urlType.js @@ -30,7 +30,7 @@ export const urlTypeToSegment = ({ baseUrl = '', source = '', range = '' }) => { resolvedUri: resolveUrl(baseUrl || '', source) }; - if (source && range) { + if (range) { const ranges = range.split('-'); const startRange = parseInt(ranges[0], 10); const endRange = parseInt(ranges[1], 10); diff --git a/test/segment/segmentTemplate.test.js b/test/segment/segmentTemplate.test.js index 36310853..a6df52aa 100644 --- a/test/segment/segmentTemplate.test.js +++ b/test/segment/segmentTemplate.test.js @@ -698,7 +698,9 @@ QUnit.test('constructs simple segment list and resolves uris', function(assert) timescale: '1000', bandwidth: '100', id: 'Rep1', - initialization: '$RepresentationID$/$Bandwidth$/init.mp4', + initialization: { + sourceURL: '$RepresentationID$/$Bandwidth$/init.mp4' + }, media: '$RepresentationID$/$Bandwidth$/$Number%03d$-$Time%05d$.mp4', periodIndex: 1, baseUrl: 'https://example.com/' diff --git a/test/segment/urlType.test.js b/test/segment/urlType.test.js new file mode 100644 index 00000000..8923378d --- /dev/null +++ b/test/segment/urlType.test.js @@ -0,0 +1,51 @@ +import QUnit from 'qunit'; +import urlTypeConverter from '../../src/segment/urlType' + + +QUnit.module('urlType - urlTypeConverter'); + +QUnit.test('returns correct object if given baseUrl only', function(assert) { + assert.deepEqual(urlTypeConverter({ baseUrl: 'http://example.com' }), { + resolvedUri: 'http://example.com', + uri: '' + }); +}); + +QUnit.test('returns correct object if given baseUrl and source', function(assert) { + assert.deepEqual(urlTypeConverter({ + baseUrl: 'http://example.com', + source: 'init.fmp4' + }), { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4' + }); +}); + +QUnit.test('returns correct object if given baseUrl, source and range', function(assert) { + assert.deepEqual(urlTypeConverter({ + baseUrl: 'http://example.com', + source: 'init.fmp4', + range: '101-105' + }), { + resolvedUri: 'http://example.com/init.fmp4', + uri: 'init.fmp4', + byterange: { + offset: 101, + length: 4 + } + }); +}); + +QUnit.test('returns correct object if given baseUrl and range', function(assert) { + assert.deepEqual(urlTypeConverter({ + baseUrl: 'http://example.com', + range: '101-105' + }), { + resolvedUri: 'http://example.com', + uri: '', + byterange: { + offset: 101, + length: 4 + } + }); +}); From 8bb14131d7190df6ec966dffc4ded59bd7915935 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Tue, 30 Jan 2018 18:37:08 -0500 Subject: [PATCH 10/12] Fix lint errors --- src/inheritAttributes.js | 18 ++++++++++-------- test/segment/urlType.test.js | 3 +-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index cd7f809c..4cc1205a 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -61,18 +61,20 @@ export const getSegmentInformation = (adaptationSet) => { const segmentInitialization = segmentInitializationNode && findChildren(segmentInitializationNode, 'Initialization')[0]; - // SegmentTemplate is handled slightly differently, since it can have both @initialization - // and an node. @initialization can be templated, while the node can have a - // url and range specified. If the has both @initialization and - // an subelement we opt to override with the node, - // as this interaction is not defined in the spec. + // SegmentTemplate is handled slightly differently, since it can have both + // @initialization and an node. @initialization can be templated, + // while the node can have a url and range specified. If the has + // both @initialization and an 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)); + 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 + // 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 template.initialization = { sourceURL: template.initialization }; } diff --git a/test/segment/urlType.test.js b/test/segment/urlType.test.js index 8923378d..d3a92ecb 100644 --- a/test/segment/urlType.test.js +++ b/test/segment/urlType.test.js @@ -1,6 +1,5 @@ import QUnit from 'qunit'; -import urlTypeConverter from '../../src/segment/urlType' - +import urlTypeConverter from '../../src/segment/urlType'; QUnit.module('urlType - urlTypeConverter'); From b9f69ad5b6b04afb3a23fb5ab40bd5e04db94328 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Wed, 31 Jan 2018 11:46:19 -0500 Subject: [PATCH 11/12] Update variable names to be cleareR --- src/inheritAttributes.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 4cc1205a..9c0b8da5 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -54,12 +54,12 @@ export const getSegmentInformation = (adaptationSet) => { const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL') .map(s => shallowMerge({ tag: 'SegmentURL' }, getAttributes(s))); const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; - const segmentTimelineNode = segmentList || segmentTemplate; - const segmentTimeline = - segmentTimelineNode && findChildren(segmentTimelineNode, 'SegmentTimeline')[0]; - const segmentInitializationNode = segmentList || segmentBase || segmentTemplate; - const segmentInitialization = segmentInitializationNode && - findChildren(segmentInitializationNode, 'Initialization')[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 node. @initialization can be templated, From 06cd527aaa3215c6c57aeaf3cc772e21a4a3aa86 Mon Sep 17 00:00:00 2001 From: Oshin Karamian Date: Wed, 31 Jan 2018 13:15:25 -0500 Subject: [PATCH 12/12] Fix indentation --- src/inheritAttributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 9c0b8da5..285427f6 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -81,7 +81,7 @@ export const getSegmentInformation = (adaptationSet) => { return { template, timeline: segmentTimeline && - findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), + findChildren(segmentTimeline, 'S').map(s => getAttributes(s)), list: segmentList && shallowMerge( getAttributes(segmentList), {