Skip to content

Commit

Permalink
feat: add support for #EXT-X-I-FRAME-STREAM-INF (#171)
Browse files Browse the repository at this point in the history
* feat: add support for #EXT-X-I-FRAME-STREAM-INF

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`
- 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

* test: update fixtures to take iFramePlaylists property into account

* refactor(stream-inf): uses the parseResolution function to extract a resolution object

---------

Co-authored-by: mister-ben <1676039+mister-ben@users.noreply.github.com>
  • Loading branch information
amtins and mister-ben authored Jul 6, 2024
1 parent f8c9817 commit 990c6ce
Show file tree
Hide file tree
Showing 65 changed files with 620 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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)
* [EXT-X-CONTENT-STEERING](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6)

### Experimental 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-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
68 changes: 58 additions & 10 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 @@ -296,16 +319,7 @@ export default class ParseStream extends Stream {
event.attributes = parseAttributes(match[1]);

if (event.attributes.RESOLUTION) {
const split = event.attributes.RESOLUTION.split('x');
const resolution = {};

if (split[0]) {
resolution.width = parseInt(split[0], 10);
}
if (split[1]) {
resolution.height = parseInt(split[1], 10);
}
event.attributes.RESOLUTION = resolution;
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
}
if (event.attributes.BANDWIDTH) {
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
Expand Down Expand Up @@ -635,6 +649,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
14 changes: 14 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export default class Parser extends Stream {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
segments: []
};
// keep track of the last seen segment's byte range end, as segments are not required
Expand Down Expand Up @@ -730,6 +731,19 @@ export default class Parser extends Stream {
entry.attributes,
['SERVER-URI']
);
},
'i-frame-playlist'() {
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
1 change: 1 addition & 0 deletions test/fixtures/integration/absoluteUris.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/allowCache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
mediaSequence: 0,
dateRanges: [],
playlistType: 'VOD',
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/allowCacheInvalid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
mediaSequence: 0,
dateRanges: [],
playlistType: 'VOD',
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/alternateAudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaGroups: {
// TYPE
'AUDIO': {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/alternateVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaGroups: {
'AUDIO': {
aac: {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/brightcove.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
dateRanges: [],
playlists: [
{
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/byteRange.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/dateTime.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
allowCache: false,
iFramePlaylists: [],
mediaSequence: 0,
dateRanges: [],
playlistType: 'VOD',
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/diff-init-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
discontinuitySequence: 0,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 7794,
segments: [
{
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/disallowCache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: false,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/disc-sequence.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
discontinuitySequence: 3,
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/discontinuity.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
discontinuitySequence: 0,
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/domainUris.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/empty.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [],
segments: []
};
1 change: 1 addition & 0 deletions test/fixtures/integration/emptyAllowCache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/emptyMediaSequence.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/emptyPlaylistType.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/emptyTargetDuration.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
playlists: [
{
attributes: {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/encrypted.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 7794,
discontinuitySequence: 0,
discontinuityStarts: [],
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/event.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'EVENT',
segments: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 1,
segments: [
{
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/extinf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/fmp4.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 1,
playlistType: 'VOD',
targetDuration: 6,
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/integration/headerOnly.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [],
segments: []
};
Loading

0 comments on commit 990c6ce

Please sign in to comment.