diff --git a/src/parse-stream.js b/src/parse-stream.js index ee63cc9..bee4b33 100644 --- a/src/parse-stream.js +++ b/src/parse-stream.js @@ -535,6 +535,8 @@ export default class ParseStream extends Stream { event.attributes.byterange = event.attributes.byterange || {}; event.attributes.byterange[subkey] = event.attributes[key]; + // only keep the parsed byterange object. + delete event.attributes[key]; } }); diff --git a/src/parser.js b/src/parser.js index 221ee6e..9b857c5 100644 --- a/src/parser.js +++ b/src/parser.js @@ -6,6 +6,20 @@ import decodeB64ToUint8Array from '@videojs/vhs-utils/es/decode-b64-to-uint8-arr import LineStream from './line-stream'; import ParseStream from './parse-stream'; +const camelCase = (str) => str + .toLowerCase() + .replace(/-(\w)/g, (a) => a[1].toUpperCase()); + +const camelCaseKeys = function(attributes) { + const result = {}; + + Object.keys(attributes).forEach(function(key) { + result[camelCase(key)] = attributes[key]; + }); + + return result; +}; + // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration // we need this helper because defaults are based upon targetDuration and // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before @@ -18,21 +32,21 @@ const setHoldBack = function(manifest) { } const tag = '#EXT-X-SERVER-CONTROL'; - const hb = 'HOLD-BACK'; - const phb = 'PART-HOLD-BACK'; + const hb = 'holdBack'; + const phb = 'partHoldBack'; const minTargetDuration = targetDuration && targetDuration * 3; const minPartDuration = partTargetDuration && partTargetDuration * 2; if (targetDuration && !serverControl.hasOwnProperty(hb)) { serverControl[hb] = minTargetDuration; this.trigger('info', { - message: `${tag} defaulting ${hb} to targetDuration * 3 (${minTargetDuration}).` + message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).` }); } if (minTargetDuration && serverControl[hb] < minTargetDuration) { this.trigger('warn', { - message: `${tag} clamping ${hb} (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})` + message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})` }); serverControl[hb] = minTargetDuration; } @@ -41,14 +55,14 @@ const setHoldBack = function(manifest) { if (partTargetDuration && !serverControl.hasOwnProperty(phb)) { serverControl[phb] = partTargetDuration * 3; this.trigger('info', { - message: `${tag} defaulting ${phb} to partTargetDuration * 3 (${serverControl[phb]}).` + message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).` }); } // if part hold back is too small default it to part target duration * 2 if (partTargetDuration && serverControl[phb] < (minPartDuration)) { this.trigger('warn', { - message: `${tag} clamping ${phb} (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).` + message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).` }); serverControl[phb] = minPartDuration; @@ -456,50 +470,41 @@ export default class Parser extends Stream { currentUri.cueIn = entry.data; }, 'skip'() { - this.manifest.skip = entry.attributes; + this.manifest.skip = camelCaseKeys(entry.attributes); - if (!entry.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) { - this.trigger('warn', { - message: '#EXT-X-SKIP lacks required attribute: SKIPPED-SEGMENTS' - }); - } + this.warnOnMissingAttributes_( + '#EXT-X-SKIP', + entry.attributes, + ['SKIPPED-SEGMENTS'] + ); }, 'part'() { hasParts = true; // parts are always specifed before a segment const segmentIndex = this.manifest.segments.length; + const part = camelCaseKeys(entry.attributes); currentUri.parts = currentUri.parts || []; - currentUri.parts.push(entry.attributes); - - if (entry.attributes.byterange) { - const byterange = entry.attributes.byterange; + currentUri.parts.push(part); - if (!byterange.hasOwnProperty('offset')) { - byterange.offset = lastPartByterangeEnd; + if (part.byterange) { + if (!part.byterange.hasOwnProperty('offset')) { + part.byterange.offset = lastPartByterangeEnd; } - lastPartByterangeEnd = byterange.offset + byterange.length; + lastPartByterangeEnd = part.byterange.offset + part.byterange.length; } - const missingAttributes = []; - - ['URI', 'DURATION'].forEach(function(k) { - if (!entry.attributes.hasOwnProperty(k)) { - missingAttributes.push(k); - } - }); - - if (missingAttributes.length) { - const partIndex = currentUri.parts.length - 1; + const partIndex = currentUri.parts.length - 1; - this.trigger('warn', { - message: `#EXT-X-PART #${partIndex} for segment #${segmentIndex} lacks required attribute(s): ${missingAttributes.join(', ')}` - }); - } + this.warnOnMissingAttributes_( + `#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, + entry.attributes, + ['URI', 'DURATION'] + ); if (this.manifest.renditionReports) { this.manifest.renditionReports.forEach((r, i) => { - if (!r.hasOwnProperty('LAST-PART')) { + if (!r.hasOwnProperty('lastPart')) { this.trigger('warn', { message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART` }); @@ -508,105 +513,96 @@ export default class Parser extends Stream { } }, 'server-control'() { - const attrs = entry.attributes; + const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes); - this.manifest.serverControl = attrs; - if (!attrs.hasOwnProperty('CAN-BLOCK-RELOAD')) { - this.manifest.serverControl['CAN-BLOCK-RELOAD'] = false; + if (!attrs.hasOwnProperty('canBlockReload')) { + attrs.canBlockReload = false; this.trigger('info', { message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false' }); } setHoldBack.call(this, this.manifest); - if (attrs['CAN-SKIP-DATERANGES'] && !attrs.hasOwnProperty('CAN-SKIP-UNTIL')) { + if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) { this.trigger('warn', { message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set' }); - } }, 'preload-hint'() { // parts are always specifed before a segment const segmentIndex = this.manifest.segments.length; + const hint = camelCaseKeys(entry.attributes); + const isPart = hint.type && hint.type === 'PART'; currentUri.preloadHints = currentUri.preloadHints || []; - currentUri.preloadHints.push(entry.attributes); - - if (entry.attributes.byterange) { - const byterange = entry.attributes.byterange; - - if (!byterange.hasOwnProperty('offset')) { - byterange.offset = 0; - if (entry.attributes.TYPE && entry.attributes.TYPE === 'PART') { - // use last segment byterange end if we are the first part. - // otherwise use lastPartByterangeEnd - byterange.offset = lastPartByterangeEnd; - lastPartByterangeEnd = byterange.offset + byterange.length; - } - } - } + currentUri.preloadHints.push(hint); - const missingAttributes = []; + if (hint.byterange) { - ['TYPE', 'URI'].forEach(function(k) { - if (!entry.attributes.hasOwnProperty(k)) { - missingAttributes.push(k); + if (!hint.byterange.hasOwnProperty('offset')) { + // use last part byterange end or zero if not a part. + hint.byterange.offset = isPart ? lastPartByterangeEnd : 0; + if (isPart) { + lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length; + } } - }); + } const index = currentUri.preloadHints.length - 1; - if (missingAttributes.length) { + this.warnOnMissingAttributes_( + `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, + entry.attributes, + ['TYPE', 'URI'] + ); - this.trigger('warn', { - message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} lacks required attribute(s): ${missingAttributes.join(', ')}` - }); + if (!hint.type) { + return; } + // search through all preload hints except for the current one for + // a duplicate type. + for (let i = 0; i < currentUri.preloadHints.length - 1; i++) { + const otherHint = currentUri.preloadHints[i]; - if (entry.attributes.TYPE) { - // search through all preload hints except for the current one for - // a duplicate type. - for (let i = 0; i < currentUri.preloadHints.length - 1; i++) { - const hint = currentUri.preloadHints[i]; + if (!otherHint.type) { + continue; + } - if (hint.TYPE && hint.TYPE === entry.attributes.TYPE) { - this.trigger('warn', { - message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${entry.attributes.TYPE} as preload hint #${i}` - }); - } + if (otherHint.type === hint.type) { + this.trigger('warn', { + message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}` + }); } } }, 'rendition-report'() { + const report = camelCaseKeys(entry.attributes); + this.manifest.renditionReports = this.manifest.renditionReports || []; - this.manifest.renditionReports.push(entry.attributes); + this.manifest.renditionReports.push(report); const index = this.manifest.renditionReports.length - 1; - const missingAttributes = []; - const warning = `#EXT-X-RENDITION-REPORT #${index} lacks required attribute(s):`; - - ['LAST-MSN', 'URI'].forEach(function(k) { - if (!entry.attributes.hasOwnProperty(k)) { - missingAttributes.push(k); - } - }); + const required = ['LAST-MSN', 'URI']; - if (hasParts && !entry.attributes['LAST-PART']) { - missingAttributes.push('LAST-PART'); + if (hasParts) { + required.push('LAST-PART'); } - if (missingAttributes.length) { - this.trigger('warn', {message: `${warning} ${missingAttributes.join(', ')}`}); - } + this.warnOnMissingAttributes_( + `#EXT-X-RENDITION-REPORT #${index}`, + entry.attributes, + required + ); }, 'part-inf'() { - this.manifest.partInf = entry.attributes; + this.manifest.partInf = camelCaseKeys(entry.attributes); - if (!entry.attributes.hasOwnProperty('PART-TARGET')) { - this.trigger('warn', { - message: '#EXT-X-PART-INF lacks required attribute: PART-TARGET' - }); - } else { - this.manifest.partTargetDuration = entry.attributes['PART-TARGET']; + this.warnOnMissingAttributes_( + '#EXT-X-PART-INF', + entry.attributes, + ['PART-TARGET'] + ); + if (this.manifest.partInf.partTarget) { + this.manifest.partTargetDuration = this.manifest.partInf.partTarget; } setHoldBack.call(this, this.manifest); @@ -658,6 +654,20 @@ export default class Parser extends Stream { }); } + warnOnMissingAttributes_(identifier, attributes, required) { + const missing = []; + + required.forEach(function(key) { + if (!attributes.hasOwnProperty(key)) { + missing.push(key); + } + }); + + if (missing.length) { + this.trigger('warn', {message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`}); + } + } + /** * Parse the input string and update the manifest object. * diff --git a/test/fixtures/integration/llhls-byte-range.js b/test/fixtures/integration/llhls-byte-range.js index b36edb2..52e6e02 100644 --- a/test/fixtures/integration/llhls-byte-range.js +++ b/test/fixtures/integration/llhls-byte-range.js @@ -7,29 +7,25 @@ module.exports = { preloadSegment: { preloadHints: [ { - 'BYTERANGE-LENGTH': 2000, - 'TYPE': 'PART', - 'URI': 'filePart273.1.mp4', - 'byterange': { + type: 'PART', + uri: 'filePart273.1.mp4', + byterange: { length: 2000, offset: 0 } }, { - 'BYTERANGE-LENGTH': 5000, - 'BYTERANGE-START': 8355216, - 'TYPE': 'MAP', - 'URI': 'file-init.mp4', - 'byterange': { + type: 'MAP', + uri: 'file-init.mp4', + byterange: { length: 5000, offset: 8355216 } }, { - 'BYTERANGE-LENGTH': 5000, - 'TYPE': 'FOO', - 'URI': 'foo.mp4', - 'byterange': { + type: 'FOO', + uri: 'foo.mp4', + byterange: { length: 5000, offset: 0 } @@ -181,36 +177,32 @@ module.exports = { duration: 10, parts: [ { - BYTERANGE: '45553', - DURATION: 0.33334, - URI: 'hls_450k_video.part.ts', + duration: 0.33334, + uri: 'hls_450k_video.part.ts', byterange: { length: 45553, offset: 0 } }, { - BYTERANGE: '28823@7622329', - DURATION: 0.33334, - URI: 'hls_450k_video.part.ts', + duration: 0.33334, + uri: 'hls_450k_video.part.ts', byterange: { length: 28823, offset: 7622329 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.part.ts', + duration: 0.33334, + uri: 'hls_450k_video.part.ts', byterange: { length: 22444, offset: 7651152 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.part.ts', + duration: 0.33334, + uri: 'hls_450k_video.part.ts', byterange: { length: 22444, offset: 7673596 @@ -228,27 +220,24 @@ module.exports = { duration: 1.4167, parts: [ { - BYTERANGE: '45553@8021772', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 45553, offset: 8021772 } }, { - BYTERANGE: '28823', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 28823, offset: 8067325 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 22444, offset: 8096148 diff --git a/test/fixtures/integration/llhls-delta-byte-range.js b/test/fixtures/integration/llhls-delta-byte-range.js index c01e3aa..908f4ca 100644 --- a/test/fixtures/integration/llhls-delta-byte-range.js +++ b/test/fixtures/integration/llhls-delta-byte-range.js @@ -7,9 +7,8 @@ module.exports = { preloadSegment: { parts: [ { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 22444, offset: 0 @@ -18,29 +17,25 @@ module.exports = { ], preloadHints: [ { - 'BYTERANGE-LENGTH': 2000, - 'TYPE': 'PART', - 'URI': 'filePart273.1.mp4', - 'byterange': { + type: 'PART', + uri: 'filePart273.1.mp4', + byterange: { length: 2000, offset: 22444 } }, { - 'BYTERANGE-LENGTH': 5000, - 'BYTERANGE-START': 8377660, - 'TYPE': 'MAP', - 'URI': 'file-init.mp4', - 'byterange': { + type: 'MAP', + uri: 'file-init.mp4', + byterange: { length: 5000, offset: 8377660 } }, { - 'BYTERANGE-LENGTH': 5000, - 'TYPE': 'FOO', - 'URI': 'foo.mp4', - 'byterange': { + type: 'FOO', + uri: 'foo.mp4', + byterange: { length: 5000, offset: 0 } @@ -75,36 +70,32 @@ module.exports = { duration: 10, parts: [ { - BYTERANGE: '45553', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 45553, offset: 0 } }, { - BYTERANGE: '28823@7622329', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 28823, offset: 7622329 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 22444, offset: 7651152 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 22444, offset: 7673596 @@ -122,27 +113,24 @@ module.exports = { duration: 1.4167, parts: [ { - BYTERANGE: '45553@8021772', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 45553, offset: 8021772 } }, { - BYTERANGE: '28823', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 28823, offset: 8067325 } }, { - BYTERANGE: '22444', - DURATION: 0.33334, - URI: 'hls_450k_video.ts', + duration: 0.33334, + uri: 'hls_450k_video.ts', byterange: { length: 22444, offset: 8096148 @@ -154,7 +142,7 @@ module.exports = { } ], skip: { - 'SKIPPED-SEGMENTS': 3 + skippedSegments: 3 }, targetDuration: 10, version: 3 diff --git a/test/fixtures/integration/llhls.js b/test/fixtures/integration/llhls.js index 8e82c32..5358830 100644 --- a/test/fixtures/integration/llhls.js +++ b/test/fixtures/integration/llhls.js @@ -9,31 +9,31 @@ module.exports = { map: {uri: 'init.mp4'}, parts: [ { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart273.0.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart273.0.mp4' }, { - DURATION: 0.33334, - URI: 'filePart273.1.mp4' + duration: 0.33334, + uri: 'filePart273.1.mp4' }, { - DURATION: 0.33334, - URI: 'filePart273.2.mp4' + duration: 0.33334, + uri: 'filePart273.2.mp4' } ], preloadHints: [ - {TYPE: 'PART', URI: 'filePart273.3.mp4'}, - {TYPE: 'MAP', URI: 'file-init.mp4'} + {type: 'PART', uri: 'filePart273.3.mp4'}, + {type: 'MAP', uri: 'file-init.mp4'} ], timeline: 0 }, renditionReports: [ - {'LAST-MSN': 273, 'LAST-PART': 2, 'URI': '../1M/waitForMSN.php'}, - {'LAST-MSN': 273, 'LAST-PART': 1, 'URI': '../4M/waitForMSN.php'} + {lastMsn: 273, lastPart: 2, uri: '../1M/waitForMSN.php'}, + {lastMsn: 273, lastPart: 1, uri: '../4M/waitForMSN.php'} ], partInf: { - 'PART-TARGET': 0.33334 + partTarget: 0.33334 }, partTargetDuration: 0.33334, segments: [ @@ -88,54 +88,54 @@ module.exports = { uri: 'fileSequence271.mp4', parts: [ { - DURATION: 0.33334, - URI: 'filePart271.0.mp4' + duration: 0.33334, + uri: 'filePart271.0.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.1.mp4' + duration: 0.33334, + uri: 'filePart271.1.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.2.mp4' + duration: 0.33334, + uri: 'filePart271.2.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.3.mp4' + duration: 0.33334, + uri: 'filePart271.3.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart271.4.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart271.4.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.5.mp4' + duration: 0.33334, + uri: 'filePart271.5.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.6.mp4' + duration: 0.33334, + uri: 'filePart271.6.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.7.mp4' + duration: 0.33334, + uri: 'filePart271.7.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart271.8.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart271.8.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.9.mp4' + duration: 0.33334, + uri: 'filePart271.9.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.10.mp4' + duration: 0.33334, + uri: 'filePart271.10.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.11.mp4' + duration: 0.33334, + uri: 'filePart271.11.mp4' } ] }, @@ -150,64 +150,64 @@ module.exports = { uri: 'fileSequence272.mp4', parts: [ { - DURATION: 0.33334, - GAP: true, - URI: 'filePart272.a.mp4' + duration: 0.33334, + gap: true, + uri: 'filePart272.a.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.b.mp4' + duration: 0.33334, + uri: 'filePart272.b.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.c.mp4' + duration: 0.33334, + uri: 'filePart272.c.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.d.mp4' + duration: 0.33334, + uri: 'filePart272.d.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.e.mp4' + duration: 0.33334, + uri: 'filePart272.e.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart272.f.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart272.f.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.g.mp4' + duration: 0.33334, + uri: 'filePart272.g.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.h.mp4' + duration: 0.33334, + uri: 'filePart272.h.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.i.mp4' + duration: 0.33334, + uri: 'filePart272.i.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.j.mp4' + duration: 0.33334, + uri: 'filePart272.j.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.k.mp4' + duration: 0.33334, + uri: 'filePart272.k.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.l.mp4' + duration: 0.33334, + uri: 'filePart272.l.mp4' } ] } ], serverControl: { - 'CAN-SKIP-DATERANGES': true, - 'CAN-BLOCK-RELOAD': true, - 'CAN-SKIP-UNTIL': 12, - 'PART-HOLD-BACK': 1, - 'HOLD-BACK': 12 + canSkipDateranges: true, + canBlockReload: true, + canSkipUntil: 12, + partHoldBack: 1, + holdBack: 12 }, targetDuration: 4, version: 6 diff --git a/test/fixtures/integration/llhlsDelta.js b/test/fixtures/integration/llhlsDelta.js index 888cf1e..089b723 100644 --- a/test/fixtures/integration/llhlsDelta.js +++ b/test/fixtures/integration/llhlsDelta.js @@ -8,35 +8,35 @@ module.exports = { preloadSegment: { timeline: 0, preloadHints: [ - {TYPE: 'PART', URI: 'filePart273.4.mp4'}, - {TYPE: 'MAP', URI: 'file-init.mp4'} + {type: 'PART', uri: 'filePart273.4.mp4'}, + {type: 'MAP', uri: 'file-init.mp4'} ], parts: [ { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart273.0.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart273.0.mp4' }, { - DURATION: 0.33334, - URI: 'filePart273.1.mp4' + duration: 0.33334, + uri: 'filePart273.1.mp4' }, { - DURATION: 0.33334, - URI: 'filePart273.2.mp4' + duration: 0.33334, + uri: 'filePart273.2.mp4' }, { - DURATION: 0.33334, - URI: 'filePart273.3.mp4' + duration: 0.33334, + uri: 'filePart273.3.mp4' } ] }, renditionReports: [ - {'LAST-MSN': 273, 'LAST-PART': 3, 'URI': '../1M/waitForMSN.php'}, - {'LAST-MSN': 273, 'LAST-PART': 3, 'URI': '../4M/waitForMSN.php'} + {lastMsn: 273, lastPart: 3, uri: '../1M/waitForMSN.php'}, + {lastMsn: 273, lastPart: 3, uri: '../4M/waitForMSN.php'} ], partInf: { - 'PART-TARGET': 0.33334 + partTarget: 0.33334 }, partTargetDuration: 0.33334, segments: [ @@ -56,54 +56,54 @@ module.exports = { uri: 'fileSequence271.mp4', parts: [ { - DURATION: 0.33334, - URI: 'filePart271.0.mp4' + duration: 0.33334, + uri: 'filePart271.0.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.1.mp4' + duration: 0.33334, + uri: 'filePart271.1.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.2.mp4' + duration: 0.33334, + uri: 'filePart271.2.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.3.mp4' + duration: 0.33334, + uri: 'filePart271.3.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart271.4.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart271.4.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.5.mp4' + duration: 0.33334, + uri: 'filePart271.5.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.6.mp4' + duration: 0.33334, + uri: 'filePart271.6.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.7.mp4' + duration: 0.33334, + uri: 'filePart271.7.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart271.8.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart271.8.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.9.mp4' + duration: 0.33334, + uri: 'filePart271.9.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.10.mp4' + duration: 0.33334, + uri: 'filePart271.10.mp4' }, { - DURATION: 0.33334, - URI: 'filePart271.11.mp4' + duration: 0.33334, + uri: 'filePart271.11.mp4' } ] }, @@ -115,71 +115,71 @@ module.exports = { uri: 'fileSequence272.mp4', parts: [ { - DURATION: 0.33334, - GAP: true, - URI: 'filePart272.a.mp4' + duration: 0.33334, + gap: true, + uri: 'filePart272.a.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.b.mp4' + duration: 0.33334, + uri: 'filePart272.b.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.c.mp4' + duration: 0.33334, + uri: 'filePart272.c.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.d.mp4' + duration: 0.33334, + uri: 'filePart272.d.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.e.mp4' + duration: 0.33334, + uri: 'filePart272.e.mp4' }, { - DURATION: 0.33334, - INDEPENDENT: true, - URI: 'filePart272.f.mp4' + duration: 0.33334, + independent: true, + uri: 'filePart272.f.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.g.mp4' + duration: 0.33334, + uri: 'filePart272.g.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.h.mp4' + duration: 0.33334, + uri: 'filePart272.h.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.i.mp4' + duration: 0.33334, + uri: 'filePart272.i.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.j.mp4' + duration: 0.33334, + uri: 'filePart272.j.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.k.mp4' + duration: 0.33334, + uri: 'filePart272.k.mp4' }, { - DURATION: 0.33334, - URI: 'filePart272.l.mp4' + duration: 0.33334, + uri: 'filePart272.l.mp4' } ] } ], skip: { - 'SKIPPED-SEGMENTS': 3, - 'RECENTLY-REMOVED-DATERANGES': [ + skippedSegments: 3, + recentlyRemovedDateranges: [ 'foo', 'bar' ] }, serverControl: { - 'CAN-SKIP-DATERANGES': true, - 'CAN-BLOCK-RELOAD': true, - 'CAN-SKIP-UNTIL': 12, - 'PART-HOLD-BACK': 1, - 'HOLD-BACK': 12 + canSkipDateranges: true, + canBlockReload: true, + canSkipUntil: 12, + partHoldBack: 1, + holdBack: 12 }, targetDuration: 4, version: 9 diff --git a/test/parser.test.js b/test/parser.test.js index 3977785..ab86dbd 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -466,7 +466,7 @@ QUnit.module('m3u8s', function(hooks) { assert.deepEqual( this.warnings, - ['#EXT-X-SKIP lacks required attribute: SKIPPED-SEGMENTS'], + ['#EXT-X-SKIP lacks required attribute(s): SKIPPED-SEGMENTS'], 'warnings as expected' ); @@ -657,7 +657,7 @@ QUnit.module('m3u8s', function(hooks) { this.parser.end(); const warnings = [ - '#EXT-X-PART-INF lacks required attribute: PART-TARGET' + '#EXT-X-PART-INF lacks required attribute(s): PART-TARGET' ]; assert.deepEqual(