Skip to content

Commit

Permalink
feat: add support for #EXT-X-I-FRAME-STREAM-INF
Browse files Browse the repository at this point in the history
Exposes I-frame playlists through the `iFramePlaylists` property, providing a basis for the creation of trick-play functionality.

**parse-stream.js**

- add match statement for parsing the `EXT-X-I-FRAME-STREAM-INF` tag
  - apply type conversions as indicated in the specification for attributes `BANDWIDTH`, `AVERAGE-BANDWIDTH`, `FRAME-RATE`
  - overwrite the `RESOLUTION` attribute with an object representing the resolution
- extract a function to parse the `RESOLUTION`
- add test case

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

**parser.js**

- add an array property `iFramePlaylists` to the `manifest` if it doesn't already exist
- add each `i-frame playlist` to `iFramePlaylists`
- trigger a `warn` event if the `BANDWIDTH` or `URI` attributes are missing, as required by the specification
- add test case

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

- update `master-fmp4.js` to add `iFramePlaylists`
- update `README.md` documentation
  • Loading branch information
amtins committed Jul 23, 2023
1 parent a673efc commit dfca649
Show file tree
Hide file tree
Showing 8 changed files with 565 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Manifest {

* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
* [EXT-X-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.2)
* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3)

### Experimental Tags

Expand Down Expand Up @@ -241,7 +242,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-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3)
* [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
57 changes: 57 additions & 0 deletions src/parse-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ const parseAttributes = function(attributes) {
return result;
};

/**
* Converts a string into a resolution object
*
* @param {string} resolution a string such as 3840x2160
*
* @return {Object} An object representing the resolution
*
*/
const parseResolution = (resolution) => {
const split = resolution.split('x');
const result = {};

if (split[0]) {
result.width = parseInt(split[0], 10);
}

if (split[1]) {
result.height = parseInt(split[1], 10);
}

return result;
};

/**
* A line-level M3U8 parser event stream. It expects to receive input one
* line at a time and performs a context-free parse of its contents. A stream
Expand Down Expand Up @@ -624,6 +647,40 @@ export default class ParseStream extends Stream {
return;
}

match = (/^#EXT-X-I-FRAME-STREAM-INF:(.*)$/).exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'i-frame-playlist'
};

event.attributes = parseAttributes(match[1]);

if (event.attributes.URI) {
event.uri = event.attributes.URI;
}

if (event.attributes.BANDWIDTH) {
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
}

if (event.attributes.RESOLUTION) {
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
}

if (event.attributes['AVERAGE-BANDWIDTH']) {
event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10);
}

if (event.attributes['FRAME-RATE']) {
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
}

this.trigger('data', event);

return;
}

// unknown tag type
this.trigger('data', {
type: 'tag',
Expand Down
17 changes: 17 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,23 @@ export default class Parser extends Stream {
},
'independent-segments'() {
this.manifest.independentSegments = true;
},
'i-frame-playlist'() {
if (!this.manifest.iFramePlaylists) {
this.manifest.iFramePlaylists = [];
}

this.manifest.iFramePlaylists.push({
attributes: entry.attributes,
uri: entry.uri,
timeline: currentTimeline
});

this.warnOnMissingAttributes_(
'#EXT-X-I-FRAME-STREAM-INF',
entry.attributes,
['BANDWIDTH', 'URI']
);
}
})[entry.tagType] || noop).call(self);
},
Expand Down
288 changes: 288 additions & 0 deletions test/fixtures/integration/iFramePlaylist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [
{
attributes: {
'AVERAGE-BANDWIDTH': 248586,
'BANDWIDTH': 593626,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'sdr_720/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 399790,
'BANDWIDTH': 956552,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'sdr_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 826971,
'BANDWIDTH': 1941397,
'CODECS': 'hvc1.2.4.L150.B0',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'sdr_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_2160/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 232253,
'BANDWIDTH': 573073,
'CODECS': 'dvh1.05.01',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'dolby_720/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 365337,
'BANDWIDTH': 905037,
'CODECS': 'dvh1.05.03',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'dolby_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 739114,
'BANDWIDTH': 1893236,
'CODECS': 'dvh1.05.06',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'dolby_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_2160/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 232511,
'BANDWIDTH': 572673,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'hdr10_720/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 364552,
'BANDWIDTH': 905053,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'hdr10_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 739757,
'BANDWIDTH': 1895477,
'CODECS': 'hvc1.2.4.L150.B0',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'hdr10_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_2160/iframe_index.m3u8'
}
],
independentSegments: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 3971374,
'AVERAGE-BANDWIDTH': '2778321'
},
uri: 'sdr_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 10022043,
'AVERAGE-BANDWIDTH': '6759875'
},
uri: 'sdr_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'hvc1.2.4.L150.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 28058971,
'AVERAGE-BANDWIDTH': '20985770'
},
uri: 'sdr_2160/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'dvh1.05.01',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 5327059,
'AVERAGE-BANDWIDTH': '3385450'
},
uri: 'dolby_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'dvh1.05.03',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 12876596,
'AVERAGE-BANDWIDTH': '7999361'
},
uri: 'dolby_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'dvh1.05.06',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 30041698,
'AVERAGE-BANDWIDTH': '24975091'
},
uri: 'dolby_2160/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 5280654,
'AVERAGE-BANDWIDTH': '3320040'
},
uri: 'hdr10_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 12886714,
'AVERAGE-BANDWIDTH': '7964551'
},
uri: 'hdr10_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'hvc1.2.4.L150.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 29983769,
'AVERAGE-BANDWIDTH': '24833402'
},
uri: 'hdr10_2160/prog_index.m3u8',
timeline: 0
}
],
segments: [],
version: 7
};
Loading

0 comments on commit dfca649

Please sign in to comment.