diff --git a/README.md b/README.md index c144a0c..b78815b 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ Manifest { * [EXT-X-MAP](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.5) * [EXT-X-PROGRAM-DATE-TIME](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.6) * [EXT-X-DATERANGE](https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.2.7) +* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6) ### Media Playlist Tags @@ -244,7 +245,6 @@ Example media playlist using `EXT-X-CUE-` tags. ### Not Yet Supported -* [EXT-X-I-FRAMES-ONLY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.6) * [EXT-X-SESSION-DATA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.4) * [EXT-X-SESSION-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.5) diff --git a/src/parse-stream.js b/src/parse-stream.js index 20f1ff9..f7fccdd 100644 --- a/src/parse-stream.js +++ b/src/parse-stream.js @@ -638,6 +638,16 @@ export default class ParseStream extends Stream { }); return; } + + match = (/^#EXT-X-I-FRAMES-ONLY/).exec(newLine); + if (match) { + this.trigger('data', { + type: 'tag', + tagType: 'i-frames-only' + }); + return; + } + match = (/^#EXT-X-CONTENT-STEERING:(.*)$/).exec(newLine); if (match) { event = { @@ -646,6 +656,7 @@ export default class ParseStream extends Stream { }; event.attributes = parseAttributes(match[1]); this.trigger('data', event); + return; } diff --git a/src/parser.js b/src/parser.js index 650208b..bec4b7d 100644 --- a/src/parser.js +++ b/src/parser.js @@ -724,6 +724,11 @@ export default class Parser extends Stream { 'independent-segments'() { this.manifest.independentSegments = true; }, + 'i-frames-only'() { + this.manifest.iFramesOnly = true; + + this.requiredCompatibilityversion(this.manifest.version, 4); + }, 'content-steering'() { this.manifest.contentSteering = camelCaseKeys(entry.attributes); this.warnOnMissingAttributes_( @@ -798,6 +803,14 @@ export default class Parser extends Stream { }); } + requiredCompatibilityversion(currentVersion, targetVersion) { + if (currentVersion < targetVersion || !currentVersion) { + this.trigger('warn', { + message: `manifest must be at least version ${targetVersion}` + }); + } + } + warnOnMissingAttributes_(identifier, attributes, required) { const missing = []; diff --git a/test/fixtures/integration/iFramesOnly.js b/test/fixtures/integration/iFramesOnly.js new file mode 100644 index 0000000..a2eb51a --- /dev/null +++ b/test/fixtures/integration/iFramesOnly.js @@ -0,0 +1,45 @@ +module.exports = { + allowCache: true, + dateRanges: [], + iFramePlaylists: [], + iFramesOnly: true, + mediaSequence: 0, + playlistType: 'VOD', + segments: [ + { + duration: 2.002, + timeline: 0, + uri: '001.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '002.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '003.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '004.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '005.ts' + }, + { + duration: 2.002, + timeline: 0, + uri: '006.ts' + } + ], + targetDuration: 3, + endList: true, + discontinuitySequence: 0, + discontinuityStarts: [], + version: 4 +}; diff --git a/test/fixtures/integration/iFramesOnly.m3u8 b/test/fixtures/integration/iFramesOnly.m3u8 new file mode 100644 index 0000000..05927dc --- /dev/null +++ b/test/fixtures/integration/iFramesOnly.m3u8 @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-VERSION:4 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-TARGETDURATION:3 +#EXT-X-I-FRAMES-ONLY +#EXTINF:2.002, +001.ts +#EXTINF:2.002, +002.ts +#EXTINF:2.002, +003.ts +#EXTINF:2.002, +004.ts +#EXTINF:2.002, +005.ts +#EXTINF:2.002, +006.ts +#EXT-X-ENDLIST diff --git a/test/parse-stream.test.js b/test/parse-stream.test.js index ba02471..421912d 100644 --- a/test/parse-stream.test.js +++ b/test/parse-stream.test.js @@ -1020,3 +1020,17 @@ QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) { assert.strictEqual(element.attributes['HDCP-LEVEL'], 'TYPE-1', 'the HDCP level is parsed'); assert.strictEqual(element.attributes.URI, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed'); }); + +QUnit.test('parses #EXT-X-I-FRAMES-ONLY', function(assert) { + const manifest = '#EXT-X-I-FRAMES-ONLY\n'; + let element; + + this.parseStream.on('data', function(elem) { + element = elem; + }); + this.lineStream.push(manifest); + + assert.ok(element, 'an event was triggered'); + assert.strictEqual(element.type, 'tag', 'the line type is tag'); + assert.strictEqual(element.tagType, 'i-frames-only', 'the tag type is i-frames-only'); +}); diff --git a/test/parser.test.js b/test/parser.test.js index e1d90a0..4993e69 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -1158,6 +1158,75 @@ QUnit.module('m3u8s', function(hooks) { assert.equal(this.parser.manifest.independentSegments, true); }); + QUnit.test('warns when #EXT-X-I-FRAMES-ONLY the minimum version required is not supported', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-VERSION:3', + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-MEDIA-SEQUENCE:0', + '#EXT-X-TARGETDURATION:3', + '#EXT-X-I-FRAMES-ONLY', + '#EXTINF:2.002,', + '001.ts', + '#EXTINF:2.002,', + '002.ts', + '#EXTINF:2.002,', + '003.ts', + '#EXTINF:2.002,', + '004.ts', + '#EXTINF:2.002,', + '005.ts', + '#EXTINF:2.002,', + '006.ts', + '#EXT-X-ENDLIST' + ].join('\n')); + this.parser.end(); + + const warnings = [ + 'manifest must be at least version 4' + ]; + + assert.deepEqual( + this.warnings, + warnings, + 'warnings as expected' + ); + }); + + QUnit.test('warns when #EXT-X-I-FRAMES-ONLY does not contain a version number', function(assert) { + this.parser.push([ + '#EXTM3U', + '#EXT-X-PLAYLIST-TYPE:VOD', + '#EXT-X-MEDIA-SEQUENCE:0', + '#EXT-X-TARGETDURATION:3', + '#EXT-X-I-FRAMES-ONLY', + '#EXTINF:2.002,', + '001.ts', + '#EXTINF:2.002,', + '002.ts', + '#EXTINF:2.002,', + '003.ts', + '#EXTINF:2.002,', + '004.ts', + '#EXTINF:2.002,', + '005.ts', + '#EXTINF:2.002,', + '006.ts', + '#EXT-X-ENDLIST' + ].join('\n')); + this.parser.end(); + + const warnings = [ + 'manifest must be at least version 4' + ]; + + assert.deepEqual( + this.warnings, + warnings, + 'warnings as expected' + ); + }); + QUnit.test('parses #EXT-X-CONTENT-STEERING', function(assert) { const expectedContentSteeringObject = { serverUri: '/foo?bar=00012',