Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

hls-parser

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hls-parser - npm Package Compare versions

Comparing version 0.5.1 to 0.6.0

3

package.json
{
"name": "hls-parser",
"version": "0.5.1",
"version": "0.6.0",
"description": "A simple library to read/write HLS playlists",

@@ -56,2 +56,3 @@ "main": "index.js",

"rules": {
"ava/no-ignored-test-files": 0,
"camelcase": 0,

@@ -58,0 +59,0 @@ "capitalized-comments": 0,

@@ -12,3 +12,5 @@ const utils = require('./utils');

MediaPlaylist,
Segment
Segment,
PartialSegment,
RenditionReport
} = require('./types');

@@ -39,2 +41,4 @@

case 'EXT-X-SCTE35':
case 'EXT-X-PART':
case 'EXT-X-PRELOAD-HINT':
return 'Segment';

@@ -47,2 +51,6 @@ case 'EXT-X-TARGETDURATION':

case 'EXT-X-I-FRAMES-ONLY':
case 'EXT-X-SERVER-CONTROL':
case 'EXT-X-PART-INF':
case 'EXT-X-RENDITION-REPORT':
case 'EXT-X-SKIP':
return 'MediaPlaylist';

@@ -133,2 +141,5 @@ case 'EXT-X-MEDIA':

case 'PRECISE':
case 'CAN-BLOCK-RELOAD':
case 'INDEPENDENT':
case 'GAP':
attributes[key] = val === 'YES';

@@ -142,2 +153,11 @@ break;

case 'TIME-OFFSET':
case 'CAN-SKIP-UNTIL':
case 'HOLD-BACK':
case 'PART-HOLD-BACK':
case 'PART-TARGET':
case 'BYTERANGE-START':
case 'BYTERANGE-LENGTH':
case 'LAST-MSN':
case 'LAST-PART':
case 'SKIPPED-SEGMENTS':
attributes[key] = utils.toNumber(val);

@@ -182,2 +202,8 @@ break;

case 'EXT-X-START':
case 'EXT-X-SERVER-CONTROL':
case 'EXT-X-PART-INF':
case 'EXT-X-PART':
case 'EXT-X-PRELOAD-HINT':
case 'EXT-X-RENDITION-REPORT':
case 'EXT-X-SKIP':
return [null, parseAttributeList(param)];

@@ -406,2 +432,4 @@ case 'EXTINF':

const segment = new Segment({uri, mediaSequenceNumber, discontinuitySequence});
let mapHint = false;
let partHint = false;
for (let i = start; i <= end; i++) {

@@ -424,4 +452,10 @@ const {name, value, attributes} = lines[i];

} else if (name === 'EXT-X-DISCONTINUITY') {
if (segment.parts.length > 0) {
utils.INVALIDPLAYLIST('EXT-X-DISCONTINUITY must appear before the first EXT-X-PART tag of the Parent Segment.');
}
segment.discontinuity = true;
} else if (name === 'EXT-X-KEY') {
if (segment.parts.length > 0) {
utils.INVALIDPLAYLIST('EXT-X-KEY must appear before the first EXT-X-PART tag of the Parent Segment.');
}
setCompatibleVersionOfKey(params, attributes);

@@ -436,2 +470,5 @@ segment.key = new Key({

} else if (name === 'EXT-X-MAP') {
if (segment.parts.length > 0) {
utils.INVALIDPLAYLIST('EXT-X-MAP must appear before the first EXT-X-PART tag of the Parent Segment.');
}
if (params.compatibleVersion < 5) {

@@ -485,2 +522,35 @@ params.compatibleVersion = 5;

}));
} else if (name === 'EXT-X-PRELOAD-HINT' && !attributes['TYPE']) {
utils.INVALIDPLAYLIST('EXT-X-PRELOAD-HINT: TYPE attribute is mandatory');
} else if (name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'PART' && partHint) {
utils.INVALIDPLAYLIST('Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist.');
} else if ((name === 'EXT-X-PART' || name === 'EXT-X-PRELOAD-HINT') && !attributes['URI']) {
utils.INVALIDPLAYLIST('EXT-X-PART / EXT-X-PRELOAD-HINT: URI attribute is mandatory');
} else if (name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'MAP') {
if (mapHint) {
utils.INVALIDPLAYLIST('Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist.');
}
mapHint = true;
params.hasMap = true;
segment.map = new MediaInitializationSection({
hint: true,
uri: attributes['URI'],
byterange: {length: attributes['BYTERANGE-LENGTH'], offset: attributes['BYTERANGE-START'] || 0}
});
} else if (name === 'EXT-X-PART' || (name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'PART')) {
if (name === 'EXT-X-PART' && !attributes['DURATION']) {
utils.INVALIDPLAYLIST('EXT-X-PART: DURATION attribute is mandatory');
}
if (name === 'EXT-X-PRELOAD-HINT') {
partHint = true;
}
const partialSegment = new PartialSegment({
hint: (name === 'EXT-X-PRELOAD-HINT'),
uri: attributes['URI'],
byterange: (name === 'EXT-X-PART' ? attributes['BYTERANGE'] : {length: attributes['BYTERANGE-LENGTH'], offset: attributes['BYTERANGE-START'] || 0}),
duration: attributes['DURATION'],
independent: attributes['INDEPENDENT'],
gap: attributes['GAP']
});
segment.parts.push(partialSegment);
}

@@ -499,2 +569,3 @@ }

let currentMap = null;
let containsParts = false;
for (const [index, line] of lines.entries()) {

@@ -554,2 +625,38 @@ const {name, value, attributes, category} = line;

playlist.start = {offset: attributes['TIME-OFFSET'], precise: attributes['PRECISE'] || false};
} else if (name === 'EXT-X-SERVER-CONTROL') {
if (!attributes['CAN-BLOCK-RELOAD']) {
utils.INVALIDPLAYLIST('EXT-X-SERVER-CONTROL: CAN-BLOCK-RELOAD=YES is mandatory for Low-Latency HLS');
}
playlist.lowLatencyCompatibility = {
canBlockReload: attributes['CAN-BLOCK-RELOAD'],
canSkipUntil: attributes['CAN-SKIP-UNTIL'],
holdBack: attributes['HOLD-BACK'],
partHoldBack: attributes['PART-HOLD-BACK']
};
} else if (name === 'EXT-X-PART-INF') {
if (!attributes['PART-TARGET']) {
utils.INVALIDPLAYLIST('EXT-X-PART-INF: PART-TARGET attribute is mandatory');
}
playlist.partTargetDuration = attributes['PART-TARGET'];
} else if (name === 'EXT-X-RENDITION-REPORT') {
if (!attributes['URI']) {
utils.INVALIDPLAYLIST('EXT-X-RENDITION-REPORT: URI attribute is mandatory');
}
if (attributes['URI'].search(/^[a-z]+:/) === 0) {
utils.INVALIDPLAYLIST('EXT-X-RENDITION-REPORT: URI must be relative to the playlist uri');
}
playlist.renditionReports.push(new RenditionReport({
uri: attributes['URI'],
lastMSN: attributes['LAST-MSN'],
lastPart: attributes['LAST-PART']
}));
} else if (name === 'EXT-X-SKIP') {
if (!attributes['SKIPPED-SEGMENTS']) {
utils.INVALIDPLAYLIST('EXT-X-SKIP: SKIPPED-SEGMENTS attribute is mandatory');
}
if (params.compatibleVersion < 9) {
params.compatibleVersion = 9;
}
playlist.skip = attributes['SKIPPED-SEGMENTS'];
mediaSequence += playlist.skip;
} else if (typeof line === 'string') {

@@ -565,30 +672,6 @@ // uri

if (segment) {
const {discontinuity, key, map, byterange, uri} = segment;
if (discontinuity) {
segment.discontinuitySequence = ++discontinuitySequence;
[discontinuitySequence, currentKey, currentMap] = addSegment(playlist, segment, discontinuitySequence, currentKey, currentMap);
if (!containsParts && segment.parts.length > 0) {
containsParts = true;
}
if (key) {
currentKey = key;
} else if (currentKey) {
segment.key = currentKey;
}
if (map) {
currentMap = map;
} else if (currentMap) {
segment.map = currentMap;
}
if (byterange && byterange.offset === -1) {
const {segments} = playlist;
if (segments.length > 0) {
const prevSegment = segments[segments.length - 1];
if (prevSegment.byterange && prevSegment.uri === uri) {
byterange.offset = prevSegment.byterange.offset + prevSegment.byterange.length;
} else {
utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST be a sub-range of the same media resource');
}
} else {
utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST appear in the Playlist file');
}
}
playlist.segments.push(segment);
}

@@ -598,6 +681,50 @@ segmentStart = -1;

}
if (segmentStart !== -1) {
const segment = parseSegment(lines, '', segmentStart, lines.length - 1, mediaSequence++, discontinuitySequence, params);
if (segment) {
const {parts} = segment;
if (parts.length > 0 && !playlist.endlist && !parts[parts.length - 1].hint) {
utils.INVALIDPLAYLIST('If the Playlist contains EXT-X-PART tags and does not contain an EXT-X-ENDLIST tag, the Playlist must contain an EXT-X-PRELOAD-HINT tag with a TYPE=PART attribute');
}
addSegment(playlist, segment, currentKey, currentMap);
if (!containsParts && segment.parts.length > 0) {
containsParts = true;
}
}
}
checkDateRange(playlist.segments);
if (playlist.lowLatencyCompatibility) {
checkLowLatencyCompatibility(playlist, containsParts);
}
return playlist;
}
function addSegment(playlist, segment, discontinuitySequence, currentKey, currentMap) {
const {discontinuity, key, map, byterange, uri} = segment;
if (discontinuity) {
segment.discontinuitySequence = discontinuitySequence + 1;
}
if (!key) {
segment.key = currentKey;
}
if (!map) {
segment.map = currentMap;
}
if (byterange && byterange.offset === -1) {
const {segments} = playlist;
if (segments.length > 0) {
const prevSegment = segments[segments.length - 1];
if (prevSegment.byterange && prevSegment.uri === uri) {
byterange.offset = prevSegment.byterange.offset + prevSegment.byterange.length;
} else {
utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST be a sub-range of the same media resource');
}
} else {
utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST appear in the Playlist file');
}
}
playlist.segments.push(segment);
return [segment.discontinuitySequence, segment.key, segment.map];
}
function checkDateRange(segments) {

@@ -648,2 +775,49 @@ const earliestDates = new Map();

function checkLowLatencyCompatibility({lowLatencyCompatibility, targetDuration, partTargetDuration, segments, renditionReports}, containsParts) {
const {canSkipUntil, holdBack, partHoldBack} = lowLatencyCompatibility;
if (canSkipUntil < targetDuration * 6) {
utils.INVALIDPLAYLIST('The Skip Boundary must be at least six times the EXT-X-TARGETDURATION.');
}
// Its value is a floating-point number of seconds and .
if (holdBack < targetDuration * 3) {
utils.INVALIDPLAYLIST('HOLD-BACK must be at least three times the EXT-X-TARGETDURATION.');
}
if (containsParts) {
if (partTargetDuration === undefined) {
utils.INVALIDPLAYLIST('EXT-X-PART-INF is required if a Playlist contains one or more EXT-X-PART tags');
}
if (partHoldBack === undefined) {
utils.INVALIDPLAYLIST('EXT-X-PART: PART-HOLD-BACK attribute is mandatory');
}
if (partHoldBack < partTargetDuration) {
utils.INVALIDPLAYLIST('PART-HOLD-BACK must be at least PART-TARGET');
}
for (const [segmentIndex, {parts}] of segments.entries()) {
if (parts.length > 0 && segmentIndex < segments.length - 3) {
utils.INVALIDPLAYLIST('Remove EXT-X-PART tags from the Playlist after they are greater than three target durations from the end of the Playlist.');
}
for (const [partIndex, {duration}] of parts.entries()) {
if (duration === undefined) {
continue;
}
if (duration > partTargetDuration) {
utils.INVALIDPLAYLIST('PART-TARGET is the maximum duration of any Partial Segment');
}
if (partIndex < parts.length - 1 && duration < partTargetDuration * 0.85) {
utils.INVALIDPLAYLIST('All Partial Segments except the last part of a segment must have a duration of at least 85% of PART-TARGET');
}
}
}
}
for (const report of renditionReports) {
const lastSegment = segments[segments.length - 1];
if (!report.lastMSN) {
report.lastMSN = lastSegment.mediaSequenceNumber;
}
if (!report.lastPart && lastSegment.parts.length > 0) {
report.lastPart = lastSegment.parts.length - 1;
}
}
}
function CHECKTAGCATEGORY(category, params) {

@@ -679,3 +853,3 @@ if (category === 'Segment' || category === 'MediaPlaylist') {

}
if (category === 'MediaPlaylist') {
if (category === 'MediaPlaylist' && name !== 'EXT-X-RENDITION-REPORT') {
if (params.hash[name]) {

@@ -682,0 +856,0 @@ utils.INVALIDPLAYLIST('There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist');

@@ -12,3 +12,3 @@ [![Build Status](https://travis-ci.org/kuu/hls-parser.svg?branch=master)](https://travis-ci.org/kuu/hls-parser)

Provides synchronous functions to read/write HLS playlists (conforms to [the HLS spec rev.23](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23))
Provides synchronous functions to read/write HLS playlists (conforms to [the HLS spec rev.23](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23) and [the Apple Low-Latency Spec rev. 2020/02/05](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification))

@@ -39,16 +39,23 @@ ## Install

new Segment({
uri: 'low/1.m3u8'
duration: 9,
mediaSequenceNumber: 0,
discontinuitySequence: 0
uri: 'low/1.m3u8',
duration: 9
})
]
}));
});
// Convert the object into a text
const text = HLS.stringify(obj);
HLS.stringify(obj);
/*
#EXTM3U
#EXT-X-TARGETDURATION:9
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9,
low/1.m3u8
*/
```
## API
### `HLS.parse(str)`
Converts a text playlist into a structured JS object
#### params

@@ -58,2 +65,3 @@ | Name | Type | Required | Default | Description |

| str | string | Yes | N/A | A text data that conforms to [the HLS playlist spec](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.1) |
#### return value

@@ -64,2 +72,3 @@ An instance of either `MasterPlaylist` or `MediaPlaylist` (See **Data format** below.)

Converts a JS object into a plain text playlist
#### params

@@ -69,2 +78,3 @@ | Name | Type | Required | Default | Description |

| obj | `MasterPlaylist` or `MediaPlaylist` (See **Data format** below.) | Yes | N/A | An object returned by `HLS.parse()` or a manually created object |
#### return value

@@ -75,2 +85,3 @@ A text data that conforms to [the HLS playlist spec](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.1)

Updates the option values
#### params

@@ -80,2 +91,3 @@ | Name | Type | Required | Default | Description |

| obj | Object | Yes | {} | An object holding option values which will be used to overwrite the internal option values. |
##### supported options

@@ -89,2 +101,3 @@ | Name | Type | Default | Description |

Retrieves the current option values
#### return value

@@ -105,3 +118,3 @@ A cloned object containing the current option values

| ---------------- | ------------- | -------- | ------- | ------------- |
| `type` | string | Yes | N/A | Either `playlist` or `segment`} |
| `type` | string | Yes | N/A | Either `playlist` or `segment` or `part`} |

@@ -177,2 +190,6 @@ ### `Playlist` (extends `Data`)

| `segments` | [`Segment`] | No | [] | A list of available segments |
| `lowLatencyCompatibility` | object ({canBlockReload: boolean, canSkipUntil: number, holdBack: number, partHoldBack: number}) | No | undefined | See `CAN-BLOCK-RELOAD`, `CAN-SKIP-UNTIL`, `HOLD-BACK`, and `PART-HOLD-BACK` attributes in [EXT-X-SERVER-CONTROL](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3281374) |
| `partTargetDuration` | number | No* | undefined | *Required if the playlist contains one or more `EXT-X-PART` tags. See [EXT-X-PART-INF](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282434) |
| `renditionReports` | [`RenditionReport`] | No | [] | Update status of the associated renditions |
| `skip` | number | No | 0 | See [EXT-X-SKIP](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282433) |

@@ -182,9 +199,9 @@ ### `Segment` (extends `Data`)

| ----------------- | -------- | -------- | --------- | ------------- |
| `uri` | string | Yes | N/A | URI of the media segment |
| `duration` | number | Yes | N/A | See [EXTINF](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.1) |
| `uri` | string | Yes* | N/A | URI of the media segment. *Not required if the segment contains `EXT-X-PRELOAD-HINT` tag |
| `duration` | number | Yes* | N/A | See [EXTINF](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.1) *Not required if the segment contains `EXT-X-PRELOAD-HINT` tag |
| `title` | string | No | undefined | See [EXTINF](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.1) |
| `byterange` | object ({length: number, offset: number}) | No | undefined | See [EXT-X-BYTERANGE](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2) |
| `discontinuity` | boolean | No | undefined | See [EXT-X-DISCONTINUITY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.3) |
| `mediaSequenceNumber` | number | Yes | N/A | See the description about 'Media Sequence Number' in [3. Media Segments](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#page-5) |
| `discontinuitySequence` | number | Yes | N/A | See the description about 'Discontinuity Sequence Number' in [6.2.1. General Server Responsibilities](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.1) |
| `mediaSequenceNumber` | number | No | 0 | See the description about 'Media Sequence Number' in [3. Media Segments](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#page-5) |
| `discontinuitySequence` | number | No | 0 | See the description about 'Discontinuity Sequence Number' in [6.2.1. General Server Responsibilities](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.1) |
| `key` | `Key` | No | undefined | See [EXT-X-KEY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.4) |

@@ -194,4 +211,16 @@ | `map` | `MediaInitializationSection` | No | undefined | See [EXT-X-MAP](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5) |

| `dateRange` | `DateRange` | No | undefined | See [EXT-X-DATERANGE](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.7) |
| `markers` | [`SpliceInfo`] | No | undefined | SCTE-35 messages associated with this segment|
| `markers` | [`SpliceInfo`] | No | [] | SCTE-35 messages associated with this segment|
| `parts` | [`PartialSegment`] | No | [] | Partial Segments that constitute this segment |
### `PartialSegment` (extends `Data`)
| Property | Type | Required | Default | Description |
| ----------------- | -------- | -------- | --------- | ------------- |
| `hint` | boolean | No | false | `true` indicates a hinted resource (`TYPE=PART`) See [EXT-X-PRELOAD-HINT](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3526694) |
| `uri` | string | Yes | N/A | See `URI` attribute in [EXT-X-PART](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282436) |
| `duration` | number | Yes* | N/A | See `DURATION` attribute in [EXT-X-PART](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282436) *Not required if `hint` is `true`|
| `independent` | boolean | No | undefined | See `INDEPENDENT` attribute in [EXT-X-PART](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282436) |
| `byterange` | object ({length: number, offset: number}) | No | undefined | See `BYTERANGE` attribute in [EXT-X-PART](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282436) |
| `gap` | boolean | No | undefined | See `GAP` attribute in [EXT-X-PART](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282436) |
### `Key`

@@ -209,2 +238,3 @@ | Property | Type | Required | Default | Description |

| ----------------- | -------- | -------- | --------- | ------------- |
| `hint` | boolean | No | false | `true` indicates a hinted resource (`TYPE=MAP`) See [EXT-X-PRELOAD-HINT](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3526694) |
| `uri` | string | Yes | N/A | See URI attribute in [EXT-X-MAP](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5) |

@@ -234,1 +264,8 @@ | `byterange` | object ({length: number, offset: number}) | No | undefined | See BYTERANGE attribute in [EXT-X-MAP](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5) |

| `value` | string | No | undefined | Holds a raw (string) value for the unsupported tag. |
### `RenditionReport`
| Property | Type | Required | Default | Description |
| ----------------- | -------- | -------- | --------- | ------------- |
| `uri` | string | Yes | N/A | See `URI` attribute in [EXT-X-RENDITION-REPORT](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282435) |
| `lastMSN` | number | No | undefined | See `LAST-MSN` attribute in [EXT-X-RENDITION-REPORT](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282435) |
| `lastPart` | number | No | undefined | See `LAST-PART` attribute in [EXT-X-RENDITION-REPORT](https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification#3282435) |

@@ -48,6 +48,19 @@ const utils = require('./utils');

function buildDecimalFloatingNumber(num, fixed) {
const rounded = Math.round(num * 1000) / 1000;
let roundFactor = 1000;
if (fixed) {
roundFactor = 10 ** fixed;
}
const rounded = Math.round(num * roundFactor) / roundFactor;
return fixed ? rounded.toFixed(fixed) : rounded;
}
function getNumberOfDecimalPlaces(num) {
const str = num.toString(10);
const index = str.indexOf('.');
if (index === -1) {
return 0;
}
return str.length - index - 1;
}
function buildMasterPlaylist(lines, playlist) {

@@ -192,2 +205,20 @@ for (const sessionData of playlist.sessionDataList) {

}
if (playlist.lowLatencyCompatibility) {
const {canBlockReload, canSkipUntil, holdBack, partHoldBack} = playlist.lowLatencyCompatibility;
const params = [];
params.push(`CAN-BLOCK-RELOAD=${canBlockReload ? 'YES' : 'NO'}`);
if (canSkipUntil !== undefined) {
params.push(`CAN-SKIP-UNTIL=${canSkipUntil}`);
}
if (holdBack !== undefined) {
params.push(`HOLD-BACK=${holdBack}`);
}
if (partHoldBack !== undefined) {
params.push(`PART-HOLD-BACK=${partHoldBack}`);
}
lines.push(`#EXT-X-SERVER-CONTROL:${params.join(',')}`);
}
if (playlist.partTargetDuration) {
lines.push(`#EXT-X-PART-INF:PART-TARGET=${playlist.partTargetDuration}`);
}
if (playlist.mediaSequenceBase) {

@@ -205,2 +236,5 @@ lines.push(`#EXT-X-MEDIA-SEQUENCE:${playlist.mediaSequenceBase}`);

}
if (playlist.skip > 0) {
lines.push(`#EXT-X-SKIP:SKIPPED-SEGMENTS=${playlist.skip}`);
}
for (const segment of playlist.segments) {

@@ -212,5 +246,15 @@ buildSegment(lines, segment, playlist.version);

}
for (const report of playlist.renditionReports) {
const params = [];
params.push(`URI="${report.uri}"`);
params.push(`LAST-MSN=${report.lastMSN}`);
if (report.lastPart !== undefined) {
params.push(`LAST-PART=${report.lastPart}`);
}
lines.push(`#EXT-X-RENDITION-REPORT:${params.join(',')}`);
}
}
function buildSegment(lines, segment, version = 1) {
let hint = false;
if (segment.byterange) {

@@ -237,3 +281,9 @@ lines.push(`#EXT-X-BYTERANGE:${buildByteRange(segment.byterange)}`);

}
const duration = version < 3 ? Math.round(segment.duration) : buildDecimalFloatingNumber(segment.duration);
if (segment.parts.length > 0) {
hint = buildParts(lines, segment.parts);
}
if (hint) {
return;
}
const duration = version < 3 ? Math.round(segment.duration) : buildDecimalFloatingNumber(segment.duration, getNumberOfDecimalPlaces(segment.duration));
lines.push(`#EXTINF:${duration},${unescape(encodeURIComponent(segment.title || ''))}`);

@@ -251,4 +301,4 @@ Array.prototype.push.call(lines, `${segment.uri}`); // URIs could be redundant when EXT-X-BYTERANGE is used

function buildByteRange(byterange) {
return `${byterange.length}@${byterange.offset}`;
function buildByteRange({offset, length}) {
return `${length}@${offset}`;
}

@@ -303,2 +353,37 @@

function buildParts(lines, parts) {
let hint = false;
for (const part of parts) {
if (part.hint) {
const params = [];
params.push('TYPE=PART');
params.push(`URI="${part.uri}"`);
if (part.byterange) {
const {offset, length} = part.byterange;
params.push(`BYTERANGE-START=${offset}`);
if (length) {
params.push(`BYTERANGE-LENGTH=${length}`);
}
}
lines.push(`#EXT-X-PRELOAD-HINT:${params.join(',')}`);
hint = true;
} else {
const params = [];
params.push(`DURATION=${part.duration}`);
params.push(`URI="${part.uri}"`);
if (part.byterange) {
params.push(`BYTERANGE=${buildByteRange(part.byterange)}`);
}
if (part.independent) {
params.push('INDEPENDENT=YES');
}
if (part.gap) {
params.push('GAP=YES');
}
lines.push(`#EXT-X-PART:${params.join(',')}`);
}
}
return hint;
}
function stringify(playlist) {

@@ -305,0 +390,0 @@ utils.PARAMCHECK(playlist);

@@ -19,3 +19,3 @@ const utils = require('./utils');

utils.PARAMCHECK(type, groupId, name);
utils.CONDITIONALASSERT([type === 'SUBTITLES', uri], [type === 'CLOSED-CAPTIONS', instreamId], [type === 'CLOSED-CAPTIONS', !uri], [forced, type === 'CLOSED-CAPTIONS']);
utils.CONDITIONALASSERT([type === 'SUBTITLES', uri], [type === 'CLOSED-CAPTIONS', instreamId], [type === 'CLOSED-CAPTIONS', !uri], [forced, type === 'SUBTITLES']);
this.type = type;

@@ -107,2 +107,3 @@ this.uri = uri;

constructor({
hint = false,
uri, // required

@@ -113,2 +114,3 @@ mimeType,

utils.PARAMCHECK(uri);
this.hint = hint;
this.uri = uri;

@@ -218,2 +220,6 @@ this.mimeType = mimeType;

segments = [],
lowLatencyCompatibility,
partTargetDuration,
renditionReports = [],
skip = 0,
hash

@@ -228,2 +234,6 @@ } = params;

this.segments = segments;
this.lowLatencyCompatibility = lowLatencyCompatibility;
this.partTargetDuration = partTargetDuration;
this.renditionReports = renditionReports;
this.skip = skip;
this.hash = hash;

@@ -235,3 +245,3 @@ }

constructor({
uri, // required
uri,
mimeType,

@@ -243,4 +253,4 @@ data,

discontinuity,
mediaSequenceNumber,
discontinuitySequence,
mediaSequenceNumber = 0,
discontinuitySequence = 0,
key,

@@ -250,6 +260,7 @@ map,

dateRange,
markers = []
markers = [],
parts = []
}) {
super('segment');
utils.PARAMCHECK(uri, mediaSequenceNumber, discontinuitySequence);
// utils.PARAMCHECK(uri, mediaSequenceNumber, discontinuitySequence);
this.uri = uri;

@@ -269,5 +280,40 @@ this.mimeType = mimeType;

this.markers = markers;
this.parts = parts;
}
}
class PartialSegment extends Data {
constructor({
hint = false,
uri, // required
duration,
independent,
byterange,
gap
}) {
super('part');
utils.PARAMCHECK(uri);
this.hint = hint;
this.uri = uri;
this.duration = duration;
this.independent = independent;
this.duration = duration;
this.byterange = byterange;
this.gap = gap;
}
}
class RenditionReport {
constructor({
uri, // required
lastMSN,
lastPart
}) {
utils.PARAMCHECK(uri);
this.uri = uri;
this.lastMSN = lastMSN;
this.lastPart = lastPart;
}
}
module.exports = {

@@ -284,3 +330,5 @@ Rendition,

MediaPlaylist,
Segment
Segment,
PartialSegment,
RenditionReport
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc