Skip to content

Commit

Permalink
feat: add support for #EXT-X-I-FRAMES-ONLY (#173)
Browse files Browse the repository at this point in the history
* feat: add support for #EXT-X-I-FRAMES-ONLY

Handles I-frames-only `segments`, providing a basis for the creation of trick-play functionality.

**parse-stream.js**

- add match statement for parsing the `EXT-X-I-FRAMES-ONLY` tag
- add test case

**parser.js**

- add a property `iFramesOnly` to the `manifest`
- add a function to validate the minimum version required
- trigger a `warn` event if the minimum version required is not supported or undefined, as required by the specification
- add test case

https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6

- update `README.md` documentation

* Update src/parse-stream.js

* Update test for #171 changes

---------

Co-authored-by: mister-ben <1676039+mister-ben@users.noreply.github.com>
  • Loading branch information
amtins and mister-ben authored Aug 16, 2024
1 parent 3f49bb4 commit e5dbdb6
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
11 changes: 11 additions & 0 deletions src/parse-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -646,6 +656,7 @@ export default class ParseStream extends Stream {
};
event.attributes = parseAttributes(match[1]);
this.trigger('data', event);

return;
}

Expand Down
13 changes: 13 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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_(
Expand Down Expand Up @@ -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 = [];

Expand Down
45 changes: 45 additions & 0 deletions test/fixtures/integration/iFramesOnly.js
Original file line number Diff line number Diff line change
@@ -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
};
19 changes: 19 additions & 0 deletions test/fixtures/integration/iFramesOnly.m3u8
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions test/parse-stream.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
69 changes: 69 additions & 0 deletions test/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit e5dbdb6

Please sign in to comment.