mirror of
https://github.com/videojs/m3u8-parser.git
synced 2026-06-06 08:22:33 +00:00
Compare commits
31 commits
remove-ver
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26d880375e | ||
|
|
c060bc7338 |
||
|
|
ba6e7cbafe |
||
|
|
e5dbdb6288 |
||
|
|
3f49bb4331 |
||
|
|
990c6ced71 |
||
|
|
f8c9817a95 |
||
|
|
b2d44f2042 | ||
|
|
42472c5979 |
||
|
|
73d934ce58 |
||
|
|
6944bb1b2f |
||
|
|
72da994469 |
||
|
|
a673efcae1 | ||
|
|
4d3e6ce140 |
||
|
|
e7c683f5f6 |
||
|
|
4adaa2c600 |
||
|
|
516ab67d17 |
||
|
|
ad1f11f17d | ||
|
|
8c47d81a6c |
||
|
|
055d7b760e | ||
|
|
cf744dbcf4 |
||
|
|
fc746e7d7f | ||
|
|
d35b7acaf1 | ||
|
|
33b24c4cb5 | ||
|
|
1124e08af5 | ||
|
|
9e4c3ad0ba | ||
|
|
5a76ec0716 | ||
|
|
8059d61a73 | ||
|
|
f38d60de6c | ||
|
|
8d56f30c4d |
||
|
|
6fe98eeea4 |
70 changed files with 2010 additions and 133 deletions
73
CHANGELOG.md
73
CHANGELOG.md
|
|
@ -1,3 +1,76 @@
|
||||||
|
<a name="7.2.0"></a>
|
||||||
|
# [7.2.0](https://github.com/videojs/m3u8-parser/compare/v7.1.0...v7.2.0) (2024-08-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add support for #EXT-X-DEFINE ([#185](https://github.com/videojs/m3u8-parser/issues/185)) ([ba6e7cb](https://github.com/videojs/m3u8-parser/commit/ba6e7cb))
|
||||||
|
* add support for #EXT-X-I-FRAME-STREAM-INF ([#171](https://github.com/videojs/m3u8-parser/issues/171)) ([990c6ce](https://github.com/videojs/m3u8-parser/commit/990c6ce)), closes [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4) [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4)
|
||||||
|
* add support for #EXT-X-I-FRAMES-ONLY ([#173](https://github.com/videojs/m3u8-parser/issues/173)) ([e5dbdb6](https://github.com/videojs/m3u8-parser/commit/e5dbdb6)), closes [/datatracker.ietf.org/doc/html/rfc8216#section-4](https://github.com//datatracker.ietf.org/doc/html/rfc8216/issues/section-4) [#171](https://github.com/videojs/m3u8-parser/issues/171)
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
|
||||||
|
* add content-steering tag to readme ([#177](https://github.com/videojs/m3u8-parser/issues/177)) ([f8c9817](https://github.com/videojs/m3u8-parser/commit/f8c9817))
|
||||||
|
* update vhs-utils dependency ([#182](https://github.com/videojs/m3u8-parser/issues/182)) ([c060bc7](https://github.com/videojs/m3u8-parser/commit/c060bc7))
|
||||||
|
|
||||||
|
<a name="7.1.0"></a>
|
||||||
|
# [7.1.0](https://github.com/videojs/m3u8-parser/compare/v7.0.0...v7.1.0) (2023-08-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* parse content steering tags and attributes ([#176](https://github.com/videojs/m3u8-parser/issues/176)) ([42472c5](https://github.com/videojs/m3u8-parser/commit/42472c5))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add dateTimeObject and dateTimeString for backward compatibility ([#174](https://github.com/videojs/m3u8-parser/issues/174)) ([6944bb1](https://github.com/videojs/m3u8-parser/commit/6944bb1))
|
||||||
|
* merge dateRange tags with same IDs and no conflicting attributes ([#175](https://github.com/videojs/m3u8-parser/issues/175)) ([73d934c](https://github.com/videojs/m3u8-parser/commit/73d934c))
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
|
||||||
|
* update v7.0.0 documentation ([#172](https://github.com/videojs/m3u8-parser/issues/172)) ([72da994](https://github.com/videojs/m3u8-parser/commit/72da994))
|
||||||
|
|
||||||
|
<a name="7.0.0"></a>
|
||||||
|
# [7.0.0](https://github.com/videojs/m3u8-parser/compare/v6.2.0...v7.0.0) (2023-07-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add PDT to each segment ([#168](https://github.com/videojs/m3u8-parser/issues/168)) ([e7c683f](https://github.com/videojs/m3u8-parser/commit/e7c683f))
|
||||||
|
* output segment title from EXTINF ([#158](https://github.com/videojs/m3u8-parser/issues/158)) ([4adaa2c](https://github.com/videojs/m3u8-parser/commit/4adaa2c))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* correct `customType` option name ([#147](https://github.com/videojs/m3u8-parser/issues/147)) ([4d3e6ce](https://github.com/videojs/m3u8-parser/commit/4d3e6ce))
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* rename `daterange` to `dateRanges`
|
||||||
|
* remove `dateTimeObject` and `dateTimeString` from parsed segment and replaces it with `programDateTime` which represents the timestamp in milliseconds
|
||||||
|
|
||||||
|
<a name="6.2.0"></a>
|
||||||
|
# [6.2.0](https://github.com/videojs/m3u8-parser/compare/v6.1.0...v6.2.0) (2023-05-25)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add independent-segments support ([#165](https://github.com/videojs/m3u8-parser/issues/165)) ([8c47d81](https://github.com/videojs/m3u8-parser/commit/8c47d81))
|
||||||
|
|
||||||
|
<a name="6.1.0"></a>
|
||||||
|
# [6.1.0](https://github.com/videojs/m3u8-parser/compare/v6.0.0...v6.1.0) (2023-05-12)
|
||||||
|
|
||||||
|
<a name="6.0.0"></a>
|
||||||
|
# [6.0.0](https://github.com/videojs/m3u8-parser/compare/v5.0.0...v6.0.0) (2022-09-27)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* non standard tag match ([#156](https://github.com/videojs/m3u8-parser/issues/156)) ([8d56f30](https://github.com/videojs/m3u8-parser/commit/8d56f30)), closes [#22](https://github.com/videojs/m3u8-parser/issues/22)
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
|
||||||
|
* don't run tests on version ([b84575f](https://github.com/videojs/m3u8-parser/commit/b84575f))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* Missing colon (:) tag delimiters are no longer supported
|
||||||
|
|
||||||
<a name="5.0.0"></a>
|
<a name="5.0.0"></a>
|
||||||
# [5.0.0](https://github.com/videojs/m3u8-parser/compare/v4.7.1...v5.0.0) (2022-08-19)
|
# [5.0.0](https://github.com/videojs/m3u8-parser/compare/v4.7.1...v5.0.0) (2022-08-19)
|
||||||
|
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -13,12 +13,13 @@ m3u8 parser
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
|
- [Constructor Options](#constructor-options)
|
||||||
- [Parsed Output](#parsed-output)
|
- [Parsed Output](#parsed-output)
|
||||||
- [Supported Tags](#supported-tags)
|
- [Supported Tags](#supported-tags)
|
||||||
- [Basic Playlist Tags](#basic-playlist-tags)
|
- [Basic Playlist Tags](#basic-playlist-tags)
|
||||||
- [Media Segment Tags](#media-segment-tags)
|
- [Media Segment Tags](#media-segment-tags)
|
||||||
- [Media Playlist Tags](#media-playlist-tags)
|
- [Media Playlist Tags](#media-playlist-tags)
|
||||||
- [Master Playlist Tags](#master-playlist-tags)
|
- [Main Playlist Tags](#main-playlist-tags)
|
||||||
- [Experimental Tags](#experimental-tags)
|
- [Experimental Tags](#experimental-tags)
|
||||||
- [EXT-X-CUE-OUT](#ext-x-cue-out)
|
- [EXT-X-CUE-OUT](#ext-x-cue-out)
|
||||||
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
|
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
|
||||||
|
|
@ -57,6 +58,7 @@ var manifest = [
|
||||||
'0.ts',
|
'0.ts',
|
||||||
'#EXTINF:6,',
|
'#EXTINF:6,',
|
||||||
'1.ts',
|
'1.ts',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z'
|
||||||
'#EXTINF:6,',
|
'#EXTINF:6,',
|
||||||
'2.ts',
|
'2.ts',
|
||||||
'#EXT-X-ENDLIST'
|
'#EXT-X-ENDLIST'
|
||||||
|
|
@ -69,6 +71,21 @@ parser.end();
|
||||||
|
|
||||||
var parsedManifest = parser.manifest;
|
var parsedManifest = parser.manifest;
|
||||||
```
|
```
|
||||||
|
### Constructor Options
|
||||||
|
|
||||||
|
The constructor optinally takes an options object with two properties. These are needed when using `#EXT-X-DEFINE` for variable replacement.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var parser = new m3u8Parser.Parser({
|
||||||
|
url: 'https://exmaple.com/video.m3u8?param_a=34¶m_b=abc',
|
||||||
|
mainDefinitions: {
|
||||||
|
param_c: 'def'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
* `options.url` _string_ The URL from which the playlist was fetched. If the request was redirected this should be the final URL. This is required if using `QUERYSTRING` rules with `#EXT-X-DEFINE`.
|
||||||
|
* `options.mainDefinitions` _object_ An object of definitions from the main playlist. This is required if using `IMPORT` rules with `#EXT-X-DEFINE`.
|
||||||
|
|
||||||
### Parsed Output
|
### Parsed Output
|
||||||
|
|
||||||
|
|
@ -79,6 +96,7 @@ Manifest {
|
||||||
allowCache: boolean,
|
allowCache: boolean,
|
||||||
endList: boolean,
|
endList: boolean,
|
||||||
mediaSequence: number,
|
mediaSequence: number,
|
||||||
|
dateRanges: [],
|
||||||
discontinuitySequence: number,
|
discontinuitySequence: number,
|
||||||
playlistType: string,
|
playlistType: string,
|
||||||
custom: {},
|
custom: {},
|
||||||
|
|
@ -113,11 +131,13 @@ Manifest {
|
||||||
discontinuityStarts: [number],
|
discontinuityStarts: [number],
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
title: string,
|
||||||
byterange: {
|
byterange: {
|
||||||
length: number,
|
length: number,
|
||||||
offset: number
|
offset: number
|
||||||
},
|
},
|
||||||
duration: number,
|
duration: number,
|
||||||
|
programDateTime: number,
|
||||||
attributes: {},
|
attributes: {},
|
||||||
discontinuity: number,
|
discontinuity: number,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
|
@ -158,6 +178,8 @@ Manifest {
|
||||||
* [EXT-X-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.4)
|
* [EXT-X-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.4)
|
||||||
* [EXT-X-MAP](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.5)
|
* [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-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
|
### Media Playlist Tags
|
||||||
|
|
||||||
|
|
@ -167,11 +189,16 @@ Manifest {
|
||||||
* [EXT-X-ENDLIST](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.4)
|
* [EXT-X-ENDLIST](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.4)
|
||||||
* [EXT-X-PLAYLIST-TYPE](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.5)
|
* [EXT-X-PLAYLIST-TYPE](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.5)
|
||||||
* [EXT-X-START](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.2)
|
* [EXT-X-START](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.2)
|
||||||
|
* [EXT-X-INDEPENDENT-SEGMENTS](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.1)
|
||||||
|
* [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3)
|
||||||
|
|
||||||
### Master Playlist Tags
|
### Main Playlist Tags
|
||||||
|
|
||||||
* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
|
* [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-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)
|
||||||
|
* [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3)
|
||||||
|
|
||||||
### Experimental Tags
|
### Experimental Tags
|
||||||
|
|
||||||
|
|
@ -236,12 +263,8 @@ Example media playlist using `EXT-X-CUE-` tags.
|
||||||
|
|
||||||
### Not Yet Supported
|
### Not Yet Supported
|
||||||
|
|
||||||
* [EXT-X-DATERANGE](http://tools.ietf.org/html/draft-pantos-http-live-streaming#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)
|
|
||||||
* [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-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)
|
* [EXT-X-SESSION-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.5)
|
||||||
* [EXT-X-INDEPENDENT-SEGMENTS](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.5.1)
|
|
||||||
|
|
||||||
### Custom Parsers
|
### Custom Parsers
|
||||||
|
|
||||||
|
|
|
||||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "m3u8-parser",
|
"name": "m3u8-parser",
|
||||||
"version": "5.0.0",
|
"version": "7.2.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1579,13 +1579,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@videojs/vhs-utils": {
|
"@videojs/vhs-utils": {
|
||||||
"version": "3.0.5",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
|
||||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
"integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0"
|
||||||
"url-toolkit": "^2.2.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"JSONStream": {
|
"JSONStream": {
|
||||||
|
|
@ -8813,11 +8812,6 @@
|
||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"url-toolkit": {
|
|
||||||
"version": "2.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
|
||||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
|
|
||||||
},
|
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "m3u8-parser",
|
"name": "m3u8-parser",
|
||||||
"version": "5.0.0",
|
"version": "7.2.0",
|
||||||
"description": "m3u8 parser",
|
"description": "m3u8 parser",
|
||||||
"main": "dist/m3u8-parser.cjs.js",
|
"main": "dist/m3u8-parser.cjs.js",
|
||||||
"module": "dist/m3u8-parser.es.js",
|
"module": "dist/m3u8-parser.es.js",
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@videojs/vhs-utils": "^3.0.5",
|
"@videojs/vhs-utils": "^4.1.1",
|
||||||
"global": "^4.4.0"
|
"global": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,14 @@ const attributeSeparator = function() {
|
||||||
* @param {string} attributes the attribute line to parse
|
* @param {string} attributes the attribute line to parse
|
||||||
*/
|
*/
|
||||||
const parseAttributes = function(attributes) {
|
const parseAttributes = function(attributes) {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
if (!attributes) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// split the string using attributes as the separator
|
// split the string using attributes as the separator
|
||||||
const attrs = attributes.split(attributeSeparator());
|
const attrs = attributes.split(attributeSeparator());
|
||||||
const result = {};
|
|
||||||
let i = attrs.length;
|
let i = attrs.length;
|
||||||
let attr;
|
let attr;
|
||||||
|
|
||||||
|
|
@ -66,6 +71,29 @@ const parseAttributes = function(attributes) {
|
||||||
return result;
|
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
|
* 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
|
* line at a time and performs a context-free parse of its contents. A stream
|
||||||
|
|
@ -164,7 +192,7 @@ export default class ParseStream extends Stream {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXTINF:?([0-9\.]*)?,?(.*)?$/).exec(newLine);
|
match = (/^#EXTINF:([0-9\.]*)?,?(.*)?$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -179,7 +207,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-TARGETDURATION:?([0-9.]*)?/).exec(newLine);
|
match = (/^#EXT-X-TARGETDURATION:([0-9.]*)?/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -191,7 +219,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-VERSION:?([0-9.]*)?/).exec(newLine);
|
match = (/^#EXT-X-VERSION:([0-9.]*)?/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -203,7 +231,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/).exec(newLine);
|
match = (/^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -215,7 +243,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/).exec(newLine);
|
match = (/^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -227,7 +255,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-PLAYLIST-TYPE:?(.*)?$/).exec(newLine);
|
match = (/^#EXT-X-PLAYLIST-TYPE:(.*)?$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -239,7 +267,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-BYTERANGE:?(.*)?$/).exec(newLine);
|
match = (/^#EXT-X-BYTERANGE:(.*)?$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = Object.assign(parseByterange(match[1]), {
|
event = Object.assign(parseByterange(match[1]), {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -248,7 +276,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-ALLOW-CACHE:?(YES|NO)?/).exec(newLine);
|
match = (/^#EXT-X-ALLOW-CACHE:(YES|NO)?/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -260,7 +288,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-MAP:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-MAP:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -281,7 +309,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-STREAM-INF:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-STREAM-INF:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -291,16 +319,7 @@ export default class ParseStream extends Stream {
|
||||||
event.attributes = parseAttributes(match[1]);
|
event.attributes = parseAttributes(match[1]);
|
||||||
|
|
||||||
if (event.attributes.RESOLUTION) {
|
if (event.attributes.RESOLUTION) {
|
||||||
const split = event.attributes.RESOLUTION.split('x');
|
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
|
||||||
const resolution = {};
|
|
||||||
|
|
||||||
if (split[0]) {
|
|
||||||
resolution.width = parseInt(split[0], 10);
|
|
||||||
}
|
|
||||||
if (split[1]) {
|
|
||||||
resolution.height = parseInt(split[1], 10);
|
|
||||||
}
|
|
||||||
event.attributes.RESOLUTION = resolution;
|
|
||||||
}
|
}
|
||||||
if (event.attributes.BANDWIDTH) {
|
if (event.attributes.BANDWIDTH) {
|
||||||
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
||||||
|
|
@ -315,7 +334,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-MEDIA:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-MEDIA:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -343,7 +362,7 @@ export default class ParseStream extends Stream {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-PROGRAM-DATE-TIME:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -356,7 +375,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-KEY:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-KEY:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -381,7 +400,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-START:?(.*)$/).exec(newLine);
|
match = (/^#EXT-X-START:(.*)$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -396,7 +415,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-CUE-OUT-CONT:?(.*)?$/).exec(newLine);
|
match = (/^#EXT-X-CUE-OUT-CONT:(.*)?$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -410,7 +429,7 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match = (/^#EXT-X-CUE-OUT:?(.*)?$/).exec(newLine);
|
match = (/^#EXT-X-CUE-OUT:(.*)?$/).exec(newLine);
|
||||||
if (match) {
|
if (match) {
|
||||||
event = {
|
event = {
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
|
|
@ -562,6 +581,128 @@ export default class ParseStream extends Stream {
|
||||||
this.trigger('data', event);
|
this.trigger('data', event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
match = (/^#EXT-X-DATERANGE:(.*)$/).exec(newLine);
|
||||||
|
if (match && match[1]) {
|
||||||
|
event = {
|
||||||
|
type: 'tag',
|
||||||
|
tagType: 'daterange'
|
||||||
|
};
|
||||||
|
event.attributes = parseAttributes(match[1]);
|
||||||
|
['ID', 'CLASS'].forEach(function(key) {
|
||||||
|
if (event.attributes.hasOwnProperty(key)) {
|
||||||
|
event.attributes[key] = String(event.attributes[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
['START-DATE', 'END-DATE'].forEach(function(key) {
|
||||||
|
if (event.attributes.hasOwnProperty(key)) {
|
||||||
|
event.attributes[key] = new Date(event.attributes[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
['DURATION', 'PLANNED-DURATION'].forEach(function(key) {
|
||||||
|
if (event.attributes.hasOwnProperty(key)) {
|
||||||
|
event.attributes[key] = parseFloat(event.attributes[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
['END-ON-NEXT'].forEach(function(key) {
|
||||||
|
if (event.attributes.hasOwnProperty(key)) {
|
||||||
|
event.attributes[key] = (/YES/i).test(event.attributes[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function(key) {
|
||||||
|
if (event.attributes.hasOwnProperty(key)) {
|
||||||
|
event.attributes[key] = event.attributes[key].toString(16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
|
||||||
|
|
||||||
|
for (const key in event.attributes) {
|
||||||
|
if (!clientAttributePattern.test(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const isHexaDecimal = (/[0-9A-Fa-f]{6}/g).test(event.attributes[key]);
|
||||||
|
const isDecimalFloating = (/^\d+(\.\d+)?$/).test(event.attributes[key]);
|
||||||
|
|
||||||
|
event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trigger('data', event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match = (/^#EXT-X-INDEPENDENT-SEGMENTS/).exec(newLine);
|
||||||
|
if (match) {
|
||||||
|
this.trigger('data', {
|
||||||
|
type: 'tag',
|
||||||
|
tagType: 'independent-segments'
|
||||||
|
});
|
||||||
|
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 = {
|
||||||
|
type: 'tag',
|
||||||
|
tagType: 'content-steering'
|
||||||
|
};
|
||||||
|
event.attributes = parseAttributes(match[1]);
|
||||||
|
this.trigger('data', event);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
match = (/^#EXT-X-DEFINE:(.*)$/).exec(newLine);
|
||||||
|
if (match) {
|
||||||
|
event = {
|
||||||
|
type: 'tag',
|
||||||
|
tagType: 'define'
|
||||||
|
};
|
||||||
|
event.attributes = parseAttributes(match[1]);
|
||||||
|
this.trigger('data', event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// unknown tag type
|
// unknown tag type
|
||||||
this.trigger('data', {
|
this.trigger('data', {
|
||||||
|
|
|
||||||
259
src/parser.js
259
src/parser.js
|
|
@ -88,14 +88,20 @@ const setHoldBack = function(manifest) {
|
||||||
* requires some property of the manifest object to be defaulted.
|
* requires some property of the manifest object to be defaulted.
|
||||||
*
|
*
|
||||||
* @class Parser
|
* @class Parser
|
||||||
|
* @param {Object} [opts] Options for the constructor, needed for substitutions
|
||||||
|
* @param {string} [opts.uri] URL to check for query params
|
||||||
|
* @param {Object} [opts.mainDefinitions] Definitions on main playlist that can be imported
|
||||||
* @extends Stream
|
* @extends Stream
|
||||||
*/
|
*/
|
||||||
export default class Parser extends Stream {
|
export default class Parser extends Stream {
|
||||||
constructor() {
|
constructor(opts = {}) {
|
||||||
super();
|
super();
|
||||||
this.lineStream = new LineStream();
|
this.lineStream = new LineStream();
|
||||||
this.parseStream = new ParseStream();
|
this.parseStream = new ParseStream();
|
||||||
this.lineStream.pipe(this.parseStream);
|
this.lineStream.pipe(this.parseStream);
|
||||||
|
this.mainDefinitions = opts.mainDefinitions || {};
|
||||||
|
this.params = new URL(opts.uri, 'https://a.com').searchParams;
|
||||||
|
this.lastProgramDateTime = null;
|
||||||
|
|
||||||
/* eslint-disable consistent-this */
|
/* eslint-disable consistent-this */
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
@ -124,6 +130,8 @@ export default class Parser extends Stream {
|
||||||
this.manifest = {
|
this.manifest = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
segments: []
|
segments: []
|
||||||
};
|
};
|
||||||
// keep track of the last seen segment's byte range end, as segments are not required
|
// keep track of the last seen segment's byte range end, as segments are not required
|
||||||
|
|
@ -132,6 +140,7 @@ export default class Parser extends Stream {
|
||||||
let lastByterangeEnd = 0;
|
let lastByterangeEnd = 0;
|
||||||
// keep track of the last seen part's byte range end.
|
// keep track of the last seen part's byte range end.
|
||||||
let lastPartByterangeEnd = 0;
|
let lastPartByterangeEnd = 0;
|
||||||
|
const dateRangeTags = {};
|
||||||
|
|
||||||
this.on('end', () => {
|
this.on('end', () => {
|
||||||
// only add preloadSegment if we don't yet have a uri for it.
|
// only add preloadSegment if we don't yet have a uri for it.
|
||||||
|
|
@ -159,6 +168,22 @@ export default class Parser extends Stream {
|
||||||
let mediaGroup;
|
let mediaGroup;
|
||||||
let rendition;
|
let rendition;
|
||||||
|
|
||||||
|
// Replace variables in uris and attributes as defined in #EXT-X-DEFINE tags
|
||||||
|
if (self.manifest.definitions) {
|
||||||
|
for (const def in self.manifest.definitions) {
|
||||||
|
if (entry.uri) {
|
||||||
|
entry.uri = entry.uri.replace(`{$${def}}`, self.manifest.definitions[def]);
|
||||||
|
}
|
||||||
|
if (entry.attributes) {
|
||||||
|
for (const attr in entry.attributes) {
|
||||||
|
if (typeof entry.attributes[attr] === 'string') {
|
||||||
|
entry.attributes[attr] = entry.attributes[attr].replace(`{$${def}}`, self.manifest.definitions[def]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
({
|
({
|
||||||
tag() {
|
tag() {
|
||||||
// switch based on the tag type
|
// switch based on the tag type
|
||||||
|
|
@ -220,6 +245,11 @@ export default class Parser extends Stream {
|
||||||
message: 'defaulting discontinuity sequence to zero'
|
message: 'defaulting discontinuity sequence to zero'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry.title) {
|
||||||
|
currentUri.title = entry.title;
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.duration > 0) {
|
if (entry.duration > 0) {
|
||||||
currentUri.duration = entry.duration;
|
currentUri.duration = entry.duration;
|
||||||
}
|
}
|
||||||
|
|
@ -458,9 +488,24 @@ export default class Parser extends Stream {
|
||||||
this.manifest.dateTimeString = entry.dateTimeString;
|
this.manifest.dateTimeString = entry.dateTimeString;
|
||||||
this.manifest.dateTimeObject = entry.dateTimeObject;
|
this.manifest.dateTimeObject = entry.dateTimeObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUri.dateTimeString = entry.dateTimeString;
|
currentUri.dateTimeString = entry.dateTimeString;
|
||||||
currentUri.dateTimeObject = entry.dateTimeObject;
|
currentUri.dateTimeObject = entry.dateTimeObject;
|
||||||
|
|
||||||
|
const { lastProgramDateTime } = this;
|
||||||
|
|
||||||
|
this.lastProgramDateTime = new Date(entry.dateTimeString).getTime();
|
||||||
|
|
||||||
|
// We should extrapolate Program Date Time backward only during first program date time occurrence.
|
||||||
|
// Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
|
||||||
|
if (lastProgramDateTime === null) {
|
||||||
|
// Extrapolate Program Date Time backward
|
||||||
|
// Since it is first program date time occurrence we're assuming that
|
||||||
|
// all this.manifest.segments have no program date time info
|
||||||
|
this.manifest.segments.reduceRight((programDateTime, segment) => {
|
||||||
|
segment.programDateTime = programDateTime - (segment.duration * 1000);
|
||||||
|
return segment.programDateTime;
|
||||||
|
}, this.lastProgramDateTime);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
targetduration() {
|
targetduration() {
|
||||||
if (!isFinite(entry.duration) || entry.duration < 0) {
|
if (!isFinite(entry.duration) || entry.duration < 0) {
|
||||||
|
|
@ -631,7 +676,195 @@ export default class Parser extends Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
setHoldBack.call(this, this.manifest);
|
setHoldBack.call(this, this.manifest);
|
||||||
|
},
|
||||||
|
'daterange'() {
|
||||||
|
this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
|
||||||
|
const index = this.manifest.dateRanges.length - 1;
|
||||||
|
|
||||||
|
this.warnOnMissingAttributes_(
|
||||||
|
`#EXT-X-DATERANGE #${index}`,
|
||||||
|
entry.attributes,
|
||||||
|
['ID', 'START-DATE']
|
||||||
|
);
|
||||||
|
const dateRange = this.manifest.dateRanges[index];
|
||||||
|
|
||||||
|
if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dateRange.duration && dateRange.duration < 0) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE DURATION must not be negative'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const endOnNextYes = !!dateRange.endOnNext;
|
||||||
|
|
||||||
|
if (endOnNextYes && !dateRange.class) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dateRange.duration && dateRange.endDate) {
|
||||||
|
const startDate = dateRange.startDate;
|
||||||
|
const newDateInSeconds = startDate.getTime() + (dateRange.duration * 1000);
|
||||||
|
|
||||||
|
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
|
||||||
|
}
|
||||||
|
if (!dateRangeTags[dateRange.id]) {
|
||||||
|
dateRangeTags[dateRange.id] = dateRange;
|
||||||
|
} else {
|
||||||
|
for (const attribute in dateRangeTags[dateRange.id]) {
|
||||||
|
if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if tags with the same ID do not have conflicting attributes, merge them
|
||||||
|
const dateRangeWithSameId = this.manifest.dateRanges.findIndex((dateRangeToFind) => dateRangeToFind.id === dateRange.id);
|
||||||
|
|
||||||
|
this.manifest.dateRanges[dateRangeWithSameId] = Object.assign(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
|
||||||
|
dateRangeTags[dateRange.id] = Object.assign(dateRangeTags[dateRange.id], dateRange);
|
||||||
|
// after merging, delete the duplicate dateRange that was added last
|
||||||
|
this.manifest.dateRanges.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'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_(
|
||||||
|
'#EXT-X-CONTENT-STEERING',
|
||||||
|
entry.attributes,
|
||||||
|
['SERVER-URI']
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** @this {Parser} */
|
||||||
|
define() {
|
||||||
|
this.manifest.definitions = this.manifest.definitions || { };
|
||||||
|
|
||||||
|
const addDef = (n, v) => {
|
||||||
|
if (n in this.manifest.definitions) {
|
||||||
|
// An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other
|
||||||
|
// EXT-X-DEFINE tag in the same Playlist. Parsers that encounter duplicate
|
||||||
|
// Variable Name declarations MUST fail to parse the Playlist.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: `EXT-X-DEFINE: Duplicate name ${n}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.manifest.definitions[n] = v;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('QUERYPARAM' in entry.attributes) {
|
||||||
|
if ('NAME' in entry.attributes || 'IMPORT' in entry.attributes) {
|
||||||
|
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
|
||||||
|
// QUERYPARAM attribute, but only one of the three. Otherwise, the
|
||||||
|
// client MUST fail to parse the Playlist.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: 'EXT-X-DEFINE: Invalid attributes'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const val = this.params.get(entry.attributes.QUERYPARAM);
|
||||||
|
|
||||||
|
if (!val) {
|
||||||
|
// If the QUERYPARAM attribute value does not match any query parameter in
|
||||||
|
// the URI or the matching parameter has no associated value, the parser
|
||||||
|
// MUST fail to parse the Playlist. If more than one parameter matches,
|
||||||
|
// any of the associated values MAY be used.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: `EXT-X-DEFINE: No query param ${entry.attributes.QUERYPARAM}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDef(entry.attributes.QUERYPARAM, decodeURIComponent(val));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('NAME' in entry.attributes) {
|
||||||
|
if ('IMPORT' in entry.attributes) {
|
||||||
|
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
|
||||||
|
// QUERYPARAM attribute, but only one of the three. Otherwise, the
|
||||||
|
// client MUST fail to parse the Playlist.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: 'EXT-X-DEFINE: Invalid attributes'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!('VALUE' in entry.attributes) || typeof entry.attributes.VALUE !== 'string') {
|
||||||
|
// This attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute.
|
||||||
|
// The quoted-string MAY be empty.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: `EXT-X-DEFINE: No value for ${entry.attributes.NAME}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDef(entry.attributes.NAME, entry.attributes.VALUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('IMPORT' in entry.attributes) {
|
||||||
|
if (!this.mainDefinitions[entry.attributes.IMPORT]) {
|
||||||
|
// Covers two conditions, as mainDefinitions will always be empty on main
|
||||||
|
//
|
||||||
|
// EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in
|
||||||
|
// Multivariant Playlists; they are only allowed in Media Playlists.
|
||||||
|
//
|
||||||
|
// If the IMPORT attribute value does not match any Variable Name in the
|
||||||
|
// Multivariant Playlist, or if the Media Playlist loaded from a
|
||||||
|
// Multivariant Playlist, the parser MUST fail the Playlist.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: `EXT-X-DEFINE: No value ${entry.attributes.IMPORT} to import, or IMPORT used on main playlist`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDef(entry.attributes.IMPORT, this.mainDefinitions[entry.attributes.IMPORT]);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM
|
||||||
|
// attribute, but only one of the three. Otherwise, the client MUST fail to
|
||||||
|
// parse the Playlist.
|
||||||
|
this.trigger('error', {
|
||||||
|
message: 'EXT-X-DEFINE: No attribute'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
'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);
|
})[entry.tagType] || noop).call(self);
|
||||||
},
|
},
|
||||||
uri() {
|
uri() {
|
||||||
|
|
@ -658,6 +891,12 @@ export default class Parser extends Stream {
|
||||||
// reset the last byterange end as it needs to be 0 between parts
|
// reset the last byterange end as it needs to be 0 between parts
|
||||||
lastPartByterangeEnd = 0;
|
lastPartByterangeEnd = 0;
|
||||||
|
|
||||||
|
// Once we have at least one program date time we can always extrapolate it forward
|
||||||
|
if (this.lastProgramDateTime !== null) {
|
||||||
|
currentUri.programDateTime = this.lastProgramDateTime;
|
||||||
|
this.lastProgramDateTime += currentUri.duration * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
// prepare for the next URI
|
// prepare for the next URI
|
||||||
currentUri = {};
|
currentUri = {};
|
||||||
},
|
},
|
||||||
|
|
@ -679,6 +918,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) {
|
warnOnMissingAttributes_(identifier, attributes, required) {
|
||||||
const missing = [];
|
const missing = [];
|
||||||
|
|
||||||
|
|
@ -710,7 +957,13 @@ export default class Parser extends Stream {
|
||||||
end() {
|
end() {
|
||||||
// flush any buffered input
|
// flush any buffered input
|
||||||
this.lineStream.push('\n');
|
this.lineStream.push('\n');
|
||||||
|
if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
|
||||||
|
this.trigger('warn', {
|
||||||
|
message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastProgramDateTime = null;
|
||||||
this.trigger('end');
|
this.trigger('end');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -718,7 +971,7 @@ export default class Parser extends Stream {
|
||||||
*
|
*
|
||||||
* @param {Object} options a map of options for the added parser
|
* @param {Object} options a map of options for the added parser
|
||||||
* @param {RegExp} options.expression a regular expression to match the custom header
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
||||||
* @param {string} options.type the type to register to the output
|
* @param {string} options.customType the custom type to register to the output
|
||||||
* @param {Function} [options.dataParser] function to parse the line into an object
|
* @param {Function} [options.dataParser] function to parse the line into an object
|
||||||
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
2
test/fixtures/integration/absoluteUris.js
vendored
2
test/fixtures/integration/absoluteUris.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
2
test/fixtures/integration/allowCache.js
vendored
2
test/fixtures/integration/allowCache.js
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
|
dateRanges: [],
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
|
dateRanges: [],
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
2
test/fixtures/integration/alternateAudio.js
vendored
2
test/fixtures/integration/alternateAudio.js
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
// TYPE
|
// TYPE
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
|
|
|
||||||
2
test/fixtures/integration/alternateVideo.js
vendored
2
test/fixtures/integration/alternateVideo.js
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
aac: {
|
aac: {
|
||||||
|
|
|
||||||
2
test/fixtures/integration/brightcove.js
vendored
2
test/fixtures/integration/brightcove.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
iFramePlaylists: [],
|
||||||
|
dateRanges: [],
|
||||||
playlists: [
|
playlists: [
|
||||||
{
|
{
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
||||||
2
test/fixtures/integration/byteRange.js
vendored
2
test/fixtures/integration/byteRange.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
4
test/fixtures/integration/dateTime.js
vendored
4
test/fixtures/integration/dateTime.js
vendored
|
|
@ -1,11 +1,14 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: false,
|
allowCache: false,
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
|
dateRanges: [],
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
dateTimeString: '2016-06-22T09:20:16.166-04:00',
|
dateTimeString: '2016-06-22T09:20:16.166-04:00',
|
||||||
dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
|
dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
|
||||||
|
programDateTime: 1466601616166,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'hls_450k_video.ts'
|
uri: 'hls_450k_video.ts'
|
||||||
|
|
@ -13,6 +16,7 @@ module.exports = {
|
||||||
{
|
{
|
||||||
dateTimeString: '2016-06-22T09:20:26.166-04:00',
|
dateTimeString: '2016-06-22T09:20:26.166-04:00',
|
||||||
dateTimeObject: new Date('2016-06-22T09:20:26.166-04:00'),
|
dateTimeObject: new Date('2016-06-22T09:20:26.166-04:00'),
|
||||||
|
programDateTime: 1466601626166,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'hls_450k_video.ts'
|
uri: 'hls_450k_video.ts'
|
||||||
|
|
|
||||||
2
test/fixtures/integration/diff-init-key.js
vendored
2
test/fixtures/integration/diff-init-key.js
vendored
|
|
@ -2,6 +2,8 @@ module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 7794,
|
mediaSequence: 7794,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
2
test/fixtures/integration/disallowCache.js
vendored
2
test/fixtures/integration/disallowCache.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: false,
|
allowCache: false,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
14
test/fixtures/integration/disc-sequence.js
vendored
14
test/fixtures/integration/disc-sequence.js
vendored
|
|
@ -1,28 +1,34 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
discontinuitySequence: 3,
|
discontinuitySequence: 3,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 3,
|
timeline: 3,
|
||||||
uri: '001.ts'
|
uri: '001.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 19,
|
duration: 19,
|
||||||
timeline: 3,
|
timeline: 3,
|
||||||
uri: '002.ts'
|
uri: '002.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
discontinuity: true,
|
discontinuity: true,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 4,
|
timeline: 4,
|
||||||
uri: '003.ts'
|
uri: '003.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 11,
|
duration: 11,
|
||||||
timeline: 4,
|
timeline: 4,
|
||||||
uri: '004.ts'
|
uri: '004.ts',
|
||||||
|
title: '0'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 19,
|
targetDuration: 19,
|
||||||
|
|
|
||||||
29
test/fixtures/integration/discontinuity.js
vendored
29
test/fixtures/integration/discontinuity.js
vendored
|
|
@ -1,55 +1,66 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '001.ts'
|
uri: '001.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 19,
|
duration: 19,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '002.ts'
|
uri: '002.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
discontinuity: true,
|
discontinuity: true,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 1,
|
timeline: 1,
|
||||||
uri: '003.ts'
|
uri: '003.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 11,
|
duration: 11,
|
||||||
timeline: 1,
|
timeline: 1,
|
||||||
uri: '004.ts'
|
uri: '004.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
discontinuity: true,
|
discontinuity: true,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 2,
|
timeline: 2,
|
||||||
uri: '005.ts'
|
uri: '005.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 2,
|
timeline: 2,
|
||||||
uri: '006.ts'
|
uri: '006.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 2,
|
timeline: 2,
|
||||||
uri: '007.ts'
|
uri: '007.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
discontinuity: true,
|
discontinuity: true,
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 3,
|
timeline: 3,
|
||||||
uri: '008.ts'
|
uri: '008.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 16,
|
duration: 16,
|
||||||
timeline: 3,
|
timeline: 3,
|
||||||
uri: '009.ts'
|
uri: '009.ts',
|
||||||
|
title: '0'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 19,
|
targetDuration: 19,
|
||||||
|
|
|
||||||
2
test/fixtures/integration/domainUris.js
vendored
2
test/fixtures/integration/domainUris.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
2
test/fixtures/integration/empty.js
vendored
2
test/fixtures/integration/empty.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
segments: []
|
segments: []
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
test/fixtures/integration/emptyAllowCache.js
vendored
2
test/fixtures/integration/emptyAllowCache.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
14
test/fixtures/integration/emptyMediaSequence.js
vendored
14
test/fixtures/integration/emptyMediaSequence.js
vendored
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
playlists: [
|
playlists: [
|
||||||
{
|
{
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
||||||
2
test/fixtures/integration/encrypted.js
vendored
2
test/fixtures/integration/encrypted.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 7794,
|
mediaSequence: 7794,
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
|
|
||||||
2
test/fixtures/integration/event.js
vendored
2
test/fixtures/integration/event.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'EVENT',
|
playlistType: 'EVENT',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 1,
|
mediaSequence: 1,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
5
test/fixtures/integration/extinf.js
vendored
5
test/fixtures/integration/extinf.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
@ -19,7 +21,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'hls_450k_video.ts'
|
uri: 'hls_450k_video.ts',
|
||||||
|
title: ';asljasdfii11)))00,'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
byterange: {
|
byterange: {
|
||||||
|
|
|
||||||
5
test/fixtures/integration/fmp4.js
vendored
5
test/fixtures/integration/fmp4.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 1,
|
mediaSequence: 1,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
targetDuration: 6,
|
targetDuration: 6,
|
||||||
|
|
@ -40,5 +42,6 @@ module.exports = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
endList: true,
|
endList: true,
|
||||||
version: 7
|
version: 7,
|
||||||
|
independentSegments: true
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
test/fixtures/integration/headerOnly.js
vendored
2
test/fixtures/integration/headerOnly.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
segments: []
|
segments: []
|
||||||
};
|
};
|
||||||
|
|
|
||||||
288
test/fixtures/integration/iFramePlaylist.js
vendored
Normal file
288
test/fixtures/integration/iFramePlaylist.js
vendored
Normal file
|
|
@ -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
|
||||||
|
};
|
||||||
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
Normal file
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:7
|
||||||
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
|
|
||||||
|
# https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices-appendixes#Example-playlist
|
||||||
|
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=248586,BANDWIDTH=593626,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="sdr_720/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=399790,BANDWIDTH=956552,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="sdr_1080/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=826971,BANDWIDTH=1941397,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="sdr_2160/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232253,BANDWIDTH=573073,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="dolby_720/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=365337,BANDWIDTH=905037,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="dolby_1080/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739114,BANDWIDTH=1893236,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="dolby_2160/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232511,BANDWIDTH=572673,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="hdr10_720/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=364552,BANDWIDTH=905053,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8"
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2778321,BANDWIDTH=3971374,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||||
|
sdr_720/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6759875,BANDWIDTH=10022043,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||||
|
sdr_1080/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=20985770,BANDWIDTH=28058971,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||||
|
sdr_2160/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3385450,BANDWIDTH=5327059,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||||
|
dolby_720/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7999361,BANDWIDTH=12876596,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||||
|
dolby_1080/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24975091,BANDWIDTH=30041698,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||||
|
dolby_2160/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3320040,BANDWIDTH=5280654,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
|
||||||
|
hdr10_720/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7964551,BANDWIDTH=12886714,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
|
||||||
|
hdr10_1080/prog_index.m3u8
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24833402,BANDWIDTH=29983769,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
|
||||||
|
hdr10_2160/prog_index.m3u8
|
||||||
45
test/fixtures/integration/iFramesOnly.js
vendored
Normal file
45
test/fixtures/integration/iFramesOnly.js
vendored
Normal file
|
|
@ -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
test/fixtures/integration/iFramesOnly.m3u8
vendored
Normal file
19
test/fixtures/integration/iFramesOnly.m3u8
vendored
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 8,
|
duration: 8,
|
||||||
|
|
|
||||||
29
test/fixtures/integration/liveStart30sBefore.js
vendored
29
test/fixtures/integration/liveStart30sBefore.js
vendored
|
|
@ -1,51 +1,62 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '001.ts'
|
uri: '001.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 19,
|
duration: 19,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '002.ts'
|
uri: '002.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '003.ts'
|
uri: '003.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 11,
|
duration: 11,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '004.ts'
|
uri: '004.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '005.ts'
|
uri: '005.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '006.ts'
|
uri: '006.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '007.ts'
|
uri: '007.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 10,
|
duration: 10,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '008.ts'
|
uri: '008.ts',
|
||||||
|
title: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 16,
|
duration: 16,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '009.ts'
|
uri: '009.ts',
|
||||||
|
title: '0'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 10,
|
targetDuration: 10,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
preloadSegment: {
|
preloadSegment: {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
preloadSegment: {
|
preloadSegment: {
|
||||||
|
|
|
||||||
9
test/fixtures/integration/llhls.js
vendored
9
test/fixtures/integration/llhls.js
vendored
|
|
@ -2,8 +2,10 @@ module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
||||||
dateTimeString: '2019-02-14T02:13:36.106Z',
|
dateTimeString: '2019-02-14T02:13:36.106Z',
|
||||||
|
dateRanges: [],
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 266,
|
mediaSequence: 266,
|
||||||
preloadSegment: {
|
preloadSegment: {
|
||||||
map: {uri: 'init.mp4'},
|
map: {uri: 'init.mp4'},
|
||||||
|
|
@ -40,6 +42,7 @@ module.exports = {
|
||||||
{
|
{
|
||||||
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
||||||
dateTimeString: '2019-02-14T02:13:36.106Z',
|
dateTimeString: '2019-02-14T02:13:36.106Z',
|
||||||
|
programDateTime: 1550110416106,
|
||||||
duration: 4.00008,
|
duration: 4.00008,
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
|
|
@ -52,6 +55,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110420106.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence267.mp4'
|
uri: 'fileSequence267.mp4'
|
||||||
},
|
},
|
||||||
|
|
@ -60,6 +64,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110424106.1602,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence268.mp4'
|
uri: 'fileSequence268.mp4'
|
||||||
},
|
},
|
||||||
|
|
@ -68,6 +73,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110428106.2402,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence269.mp4'
|
uri: 'fileSequence269.mp4'
|
||||||
},
|
},
|
||||||
|
|
@ -76,6 +82,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110432106.3203,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence270.mp4'
|
uri: 'fileSequence270.mp4'
|
||||||
},
|
},
|
||||||
|
|
@ -84,6 +91,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110436106.4004,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence271.mp4',
|
uri: 'fileSequence271.mp4',
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -146,6 +154,7 @@ module.exports = {
|
||||||
map: {
|
map: {
|
||||||
uri: 'init.mp4'
|
uri: 'init.mp4'
|
||||||
},
|
},
|
||||||
|
programDateTime: 1550110440106,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence272.mp4',
|
uri: 'fileSequence272.mp4',
|
||||||
parts: [
|
parts: [
|
||||||
|
|
|
||||||
6
test/fixtures/integration/llhlsDelta.js
vendored
6
test/fixtures/integration/llhlsDelta.js
vendored
|
|
@ -2,8 +2,10 @@ module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
||||||
dateTimeString: '2019-02-14T02:14:00.106Z',
|
dateTimeString: '2019-02-14T02:14:00.106Z',
|
||||||
|
dateRanges: [],
|
||||||
discontinuitySequence: 0,
|
discontinuitySequence: 0,
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 266,
|
mediaSequence: 266,
|
||||||
preloadSegment: {
|
preloadSegment: {
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
|
|
@ -42,16 +44,19 @@ module.exports = {
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 4.00008,
|
duration: 4.00008,
|
||||||
|
programDateTime: 1550110428105.7598,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence269.mp4'
|
uri: 'fileSequence269.mp4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 4.00008,
|
duration: 4.00008,
|
||||||
|
programDateTime: 1550110432105.8398,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence270.mp4'
|
uri: 'fileSequence270.mp4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 4.00008,
|
duration: 4.00008,
|
||||||
|
programDateTime: 1550110436105.92,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence271.mp4',
|
uri: 'fileSequence271.mp4',
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -111,6 +116,7 @@ module.exports = {
|
||||||
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
||||||
dateTimeString: '2019-02-14T02:14:00.106Z',
|
dateTimeString: '2019-02-14T02:14:00.106Z',
|
||||||
duration: 4.00008,
|
duration: 4.00008,
|
||||||
|
programDateTime: 1550110440106,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: 'fileSequence272.mp4',
|
uri: 'fileSequence272.mp4',
|
||||||
parts: [
|
parts: [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
90
test/fixtures/integration/master-fmp4.js
vendored
90
test/fixtures/integration/master-fmp4.js
vendored
|
|
@ -1,6 +1,93 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 163198,
|
||||||
|
'BANDWIDTH': 166942,
|
||||||
|
'CODECS': 'avc1.64002a',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 1080,
|
||||||
|
width: 1920
|
||||||
|
},
|
||||||
|
'URI': 'v6/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v6/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 131314,
|
||||||
|
'BANDWIDTH': 139041,
|
||||||
|
'CODECS': 'avc1.640020',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 720,
|
||||||
|
width: 1280
|
||||||
|
},
|
||||||
|
'URI': 'v5/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v5/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 100233,
|
||||||
|
'BANDWIDTH': 101724,
|
||||||
|
'CODECS': 'avc1.640020',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 540,
|
||||||
|
width: 960
|
||||||
|
},
|
||||||
|
'URI': 'v4/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v4/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 81002,
|
||||||
|
'BANDWIDTH': 84112,
|
||||||
|
'CODECS': 'avc1.64001e',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 432,
|
||||||
|
width: 768
|
||||||
|
},
|
||||||
|
'URI': 'v3/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v3/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 64987,
|
||||||
|
'BANDWIDTH': 65835,
|
||||||
|
'CODECS': 'avc1.64001e',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 360,
|
||||||
|
width: 640
|
||||||
|
},
|
||||||
|
'URI': 'v2/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v2/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
'AVERAGE-BANDWIDTH': 41547,
|
||||||
|
'BANDWIDTH': 42106,
|
||||||
|
'CODECS': 'avc1.640015',
|
||||||
|
'RESOLUTION': {
|
||||||
|
height: 270,
|
||||||
|
width: 480
|
||||||
|
},
|
||||||
|
'URI': 'v1/iframe_index.m3u8'
|
||||||
|
},
|
||||||
|
timeline: 0,
|
||||||
|
uri: 'v1/iframe_index.m3u8'
|
||||||
|
}
|
||||||
|
],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
aud1: {
|
aud1: {
|
||||||
|
|
@ -461,5 +548,6 @@ module.exports = {
|
||||||
uri: 'v1/prog_index.m3u8'
|
uri: 'v1/prog_index.m3u8'
|
||||||
}],
|
}],
|
||||||
segments: [],
|
segments: [],
|
||||||
version: 6
|
version: 6,
|
||||||
|
independentSegments: true
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
test/fixtures/integration/master.js
vendored
2
test/fixtures/integration/master.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
playlists: [
|
playlists: [
|
||||||
{
|
{
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
||||||
2
test/fixtures/integration/media.js
vendored
2
test/fixtures/integration/media.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
14
test/fixtures/integration/mediaSequence.js
vendored
14
test/fixtures/integration/mediaSequence.js
vendored
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
2
test/fixtures/integration/missingEndlist.js
vendored
2
test/fixtures/integration/missingEndlist.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
2
test/fixtures/integration/missingExtinf.js
vendored
2
test/fixtures/integration/missingExtinf.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 8,
|
duration: 8,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
'audio-lo': {
|
'audio-lo': {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
'audio-lo': {
|
'audio-lo': {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
targetDuration: 10,
|
targetDuration: 10,
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
2
test/fixtures/integration/multipleVideo.js
vendored
2
test/fixtures/integration/multipleVideo.js
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
discontinuityStarts: [],
|
discontinuityStarts: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaGroups: {
|
mediaGroups: {
|
||||||
'AUDIO': {
|
'AUDIO': {
|
||||||
aac: {
|
aac: {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: -11,
|
mediaSequence: -11,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
2
test/fixtures/integration/playlist.js
vendored
2
test/fixtures/integration/playlist.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 17,
|
mediaSequence: 17,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
2
test/fixtures/integration/start.js
vendored
2
test/fixtures/integration/start.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
playlists: [
|
playlists: [
|
||||||
{
|
{
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
||||||
14
test/fixtures/integration/twoMediaSequences.js
vendored
14
test/fixtures/integration/twoMediaSequences.js
vendored
|
|
@ -1,27 +1,33 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 11,
|
mediaSequence: 11,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
duration: 6.64,
|
duration: 6.64,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.08,
|
duration: 6.08,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 6.6,
|
duration: 6.6,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 5,
|
duration: 5,
|
||||||
timeline: 0,
|
timeline: 0,
|
||||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||||
|
title: '{}'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
targetDuration: 8,
|
targetDuration: 8,
|
||||||
|
|
|
||||||
2
test/fixtures/integration/versionInvalid.js
vendored
2
test/fixtures/integration/versionInvalid.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
2
test/fixtures/integration/whiteSpace.js
vendored
2
test/fixtures/integration/whiteSpace.js
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
|
|
|
||||||
2
test/fixtures/integration/zeroDuration.js
vendored
2
test/fixtures/integration/zeroDuration.js
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowCache: true,
|
allowCache: true,
|
||||||
mediaSequence: 0,
|
mediaSequence: 0,
|
||||||
|
dateRanges: [],
|
||||||
|
iFramePlaylists: [],
|
||||||
playlistType: 'VOD',
|
playlistType: 'VOD',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ QUnit.test('parses #EXTM3U tags', function(assert) {
|
||||||
|
|
||||||
// #EXTINF
|
// #EXTINF
|
||||||
QUnit.test('parses minimal #EXTINF tags', function(assert) {
|
QUnit.test('parses minimal #EXTINF tags', function(assert) {
|
||||||
const manifest = '#EXTINF\n';
|
const manifest = '#EXTINF:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -319,7 +319,7 @@ QUnit.test('parses #EXTINF tags with carriage returns', function(assert) {
|
||||||
|
|
||||||
// #EXT-X-TARGETDURATION
|
// #EXT-X-TARGETDURATION
|
||||||
QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function(assert) {
|
QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function(assert) {
|
||||||
const manifest = '#EXT-X-TARGETDURATION\n';
|
const manifest = '#EXT-X-TARGETDURATION:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -379,7 +379,7 @@ QUnit.test('parses #EXT-X-VERSION with a version', function(assert) {
|
||||||
|
|
||||||
// #EXT-X-MEDIA-SEQUENCE
|
// #EXT-X-MEDIA-SEQUENCE
|
||||||
QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function(assert) {
|
QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function(assert) {
|
||||||
const manifest = '#EXT-X-MEDIA-SEQUENCE\n';
|
const manifest = '#EXT-X-MEDIA-SEQUENCE:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -453,7 +453,7 @@ QUnit.test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function(assert)
|
||||||
|
|
||||||
// #EXT-X-BYTERANGE
|
// #EXT-X-BYTERANGE
|
||||||
QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function(assert) {
|
QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function(assert) {
|
||||||
const manifest = '#EXT-X-BYTERANGE\n';
|
const manifest = '#EXT-X-BYTERANGE:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -589,7 +589,7 @@ QUnit.test('parses #EXT-X-MAP tags with arbitrary attributes', function(assert)
|
||||||
});
|
});
|
||||||
// #EXT-X-STREAM-INF
|
// #EXT-X-STREAM-INF
|
||||||
QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function(assert) {
|
QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function(assert) {
|
||||||
const manifest = '#EXT-X-STREAM-INF\n';
|
const manifest = '#EXT-X-STREAM-INF:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -604,7 +604,7 @@ QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function(assert) {
|
||||||
});
|
});
|
||||||
// #EXT-X-PROGRAM-DATE-TIME
|
// #EXT-X-PROGRAM-DATE-TIME
|
||||||
QUnit.test('parses minimal EXT-X-PROGRAM-DATE-TIME tags', function(assert) {
|
QUnit.test('parses minimal EXT-X-PROGRAM-DATE-TIME tags', function(assert) {
|
||||||
const manifest = '#EXT-X-PROGRAM-DATE-TIME\n';
|
const manifest = '#EXT-X-PROGRAM-DATE-TIME:\n';
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
this.parseStream.on('data', function(elem) {
|
this.parseStream.on('data', function(elem) {
|
||||||
|
|
@ -698,6 +698,14 @@ QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
|
||||||
'avc1.4d400d, mp4a.40.2',
|
'avc1.4d400d, mp4a.40.2',
|
||||||
'codecs are parsed'
|
'codecs are parsed'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
manifest = '#EXT-X-STREAM-INF:PATHWAY-ID="CDN-A"\n';
|
||||||
|
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, 'stream-inf', 'the tag type is stream-inf');
|
||||||
|
assert.strictEqual(element.attributes['PATHWAY-ID'], 'CDN-A', 'pathway-id is parsed');
|
||||||
});
|
});
|
||||||
QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function(assert) {
|
QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function(assert) {
|
||||||
const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
|
const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
|
||||||
|
|
@ -809,16 +817,6 @@ QUnit.test('parses lightly-broken #EXT-X-KEY tags', function(assert) {
|
||||||
'parsed a single-quoted uri'
|
'parsed a single-quoted uri'
|
||||||
);
|
);
|
||||||
|
|
||||||
element = null;
|
|
||||||
manifest = '#EXT-X-KEYURI="https://example.com/key",METHOD=AES-128\n';
|
|
||||||
this.lineStream.push(manifest);
|
|
||||||
assert.strictEqual(element.tagType, 'key', 'parsed the tag type');
|
|
||||||
assert.strictEqual(
|
|
||||||
element.attributes.URI,
|
|
||||||
'https://example.com/key',
|
|
||||||
'inferred a colon after the tag type'
|
|
||||||
);
|
|
||||||
|
|
||||||
element = null;
|
element = null;
|
||||||
manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
|
manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
|
||||||
this.lineStream.push(manifest);
|
this.lineStream.push(manifest);
|
||||||
|
|
@ -889,6 +887,104 @@ QUnit.test('parses EXT-X-START PRECISE attribute', function(assert) {
|
||||||
assert.strictEqual(element.attributes['TIME-OFFSET'], 1.4, 'parses time offset');
|
assert.strictEqual(element.attributes['TIME-OFFSET'], 1.4, 'parses time offset');
|
||||||
assert.strictEqual(element.attributes.PRECISE, true, 'parses precise attribute');
|
assert.strictEqual(element.attributes.PRECISE, true, 'parses precise attribute');
|
||||||
});
|
});
|
||||||
|
// #EXT-X-DATERANGE:
|
||||||
|
QUnit.test('parses minimal EXT-X-DATERANGE tag', function(assert) {
|
||||||
|
const manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z"\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, 'daterange', 'the tag type is daterange');
|
||||||
|
assert.strictEqual(element.attributes.ID, '12345');
|
||||||
|
assert.deepEqual(element.attributes['START-DATE'], new Date('2023-04-13T15:15:15.840000Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses DURATION and PLANNED-DURATION attributes in EXT-X-DATERANGE tag', function(assert) {
|
||||||
|
const manifest = '#EXT-X-DATERANGE:ID="54545",START-DATE="2023-04-23T18:17:16.54000Z",PLANNED-DURATION="38.4",DURATION="15.5"\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, 'daterange', 'the tag type is daterange');
|
||||||
|
assert.strictEqual(element.attributes.ID, '54545');
|
||||||
|
assert.deepEqual(element.attributes['START-DATE'], new Date('2023-04-23T18:17:16.540000Z'));
|
||||||
|
assert.strictEqual(element.attributes.DURATION, 15.5);
|
||||||
|
assert.strictEqual(element.attributes['PLANNED-DURATION'], 38.4);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses SCTE35-CMD, SCTE35-OUT, SCTE35-IN EXT-X-DATERANGE tags', function(assert) {
|
||||||
|
let manifest = '#EXT-X-DATERANGE:ID="splice-6FFFFFF0",START-DATE="2023-04-13T15:15:15.840000Z",SCTE35-OUT="0xFC002F0000000000FF000014056FFFFFFF0"\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.attributes['SCTE35-OUT'], '0xFC002F0000000000FF000014056FFFFFFF0');
|
||||||
|
|
||||||
|
manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",SCTE35-IN="0xFC0000425100FFF0140500000300000000E77FEFFE0011FB9EFE0029004D1932E0000100101002A22"\n';
|
||||||
|
this.lineStream.push(manifest);
|
||||||
|
|
||||||
|
assert.ok(element, 'an event was triggered');
|
||||||
|
assert.strictEqual(element.attributes['SCTE35-IN'], '0xFC0000425100FFF0140500000300000000E77FEFFE0011FB9EFE0029004D1932E0000100101002A22');
|
||||||
|
|
||||||
|
manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",SCTE35-CMD="0xFC0000425100FFF014"\n';
|
||||||
|
this.lineStream.push(manifest);
|
||||||
|
|
||||||
|
assert.ok(element, 'an event was triggered');
|
||||||
|
assert.strictEqual(element.attributes['SCTE35-CMD'], '0xFC0000425100FFF014');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('custom attributes in EXT-X-DATERANGE are parsed', function(assert) {
|
||||||
|
let manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",X-CUSTOM-KEY="value"\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.attributes['X-CUSTOM-KEY'], 'value');
|
||||||
|
|
||||||
|
manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",X-CUSTOM-KEY="17.8"\n';
|
||||||
|
this.lineStream.push(manifest);
|
||||||
|
|
||||||
|
assert.ok(element, 'an event was triggered');
|
||||||
|
assert.strictEqual(element.attributes['X-CUSTOM-KEY'], 17.8);
|
||||||
|
|
||||||
|
manifest = '#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",X-CUSTOM-KEY="0X12345abcde"\n';
|
||||||
|
this.lineStream.push(manifest);
|
||||||
|
|
||||||
|
assert.ok(element, 'an event was triggered');
|
||||||
|
assert.strictEqual(element.attributes['X-CUSTOM-KEY'], '0X12345abcde');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses EXT-X-INDEPENDENT-SEGMENTS', function(assert) {
|
||||||
|
const manifest = '#EXT-X-INDEPENDENT-SEGMENTS\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, 'independent-segments', 'the tag type is independent-segments');
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('ignores empty lines', function(assert) {
|
QUnit.test('ignores empty lines', function(assert) {
|
||||||
const manifest = '\n';
|
const manifest = '\n';
|
||||||
|
|
@ -901,3 +997,40 @@ QUnit.test('ignores empty lines', function(assert) {
|
||||||
|
|
||||||
assert.ok(!event, 'no event is triggered');
|
assert.ok(!event, 'no event is triggered');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) {
|
||||||
|
const manifest = '#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"\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-frame-playlist', 'the tag type is i-frame-playlist');
|
||||||
|
assert.strictEqual(element.uri, 'hdr10_2160/iframe_index.m3u8', 'the uri text is parsed');
|
||||||
|
assert.strictEqual(element.attributes['AVERAGE-BANDWIDTH'], 739757, 'the average bandwidth is parsed');
|
||||||
|
assert.strictEqual(element.attributes.BANDWIDTH, 1895477, 'the bandwidth is parsed');
|
||||||
|
assert.strictEqual(element.attributes['VIDEO-RANGE'], 'PQ', 'the video range is parsed');
|
||||||
|
assert.strictEqual(element.attributes.CODECS, 'hvc1.2.4.L150.B0', 'the codecs is parsed');
|
||||||
|
assert.strictEqual(element.attributes.RESOLUTION.width, 3840, 'the resolution width is parsed');
|
||||||
|
assert.strictEqual(element.attributes.RESOLUTION.height, 2160, 'the resolution height is parsed');
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,10 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
'#EXT-X-CUE-OUT:10',
|
'#EXT-X-CUE-OUT:10',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex2.ts',
|
'ex2.ts',
|
||||||
'#EXT-X-CUE-OUT15',
|
|
||||||
'#EXT-UKNOWN-TAG',
|
'#EXT-UKNOWN-TAG',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-CUE-OUT',
|
'#EXT-X-CUE-OUT:',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-ENDLIST'
|
'#EXT-X-ENDLIST'
|
||||||
|
|
@ -122,7 +121,6 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
this.parser.end();
|
this.parser.end();
|
||||||
|
|
||||||
assert.equal(this.parser.manifest.segments[1].cueOut, '10', 'parser attached cue out tag');
|
assert.equal(this.parser.manifest.segments[1].cueOut, '10', 'parser attached cue out tag');
|
||||||
assert.equal(this.parser.manifest.segments[2].cueOut, '15', 'cue out without : seperator');
|
|
||||||
assert.equal(this.parser.manifest.segments[3].cueOut, '', 'cue out without data');
|
assert.equal(this.parser.manifest.segments[3].cueOut, '', 'cue out without data');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -135,11 +133,10 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
'#EXT-X-CUE-OUT-CONT:10/60',
|
'#EXT-X-CUE-OUT-CONT:10/60',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex2.ts',
|
'ex2.ts',
|
||||||
'#EXT-X-CUE-OUT-CONT15/30',
|
|
||||||
'#EXT-UKNOWN-TAG',
|
'#EXT-UKNOWN-TAG',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-CUE-OUT-CONT',
|
'#EXT-X-CUE-OUT-CONT:',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-ENDLIST'
|
'#EXT-X-ENDLIST'
|
||||||
|
|
@ -152,10 +149,6 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
this.parser.manifest.segments[1].cueOutCont, '10/60',
|
this.parser.manifest.segments[1].cueOutCont, '10/60',
|
||||||
'parser attached cue out cont tag'
|
'parser attached cue out cont tag'
|
||||||
);
|
);
|
||||||
assert.equal(
|
|
||||||
this.parser.manifest.segments[2].cueOutCont, '15/30',
|
|
||||||
'cue out cont without : seperator'
|
|
||||||
);
|
|
||||||
assert.equal(this.parser.manifest.segments[3].cueOutCont, '', 'cue out cont without data');
|
assert.equal(this.parser.manifest.segments[3].cueOutCont, '', 'cue out cont without data');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -165,14 +158,13 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'#COMMENT',
|
'#COMMENT',
|
||||||
'ex1.ts',
|
'ex1.ts',
|
||||||
'#EXT-X-CUE-IN',
|
'#EXT-X-CUE-IN:',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex2.ts',
|
'ex2.ts',
|
||||||
'#EXT-X-CUE-IN:15',
|
'#EXT-X-CUE-IN:15',
|
||||||
'#EXT-UKNOWN-TAG',
|
'#EXT-UKNOWN-TAG',
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-CUE-IN=abc',
|
|
||||||
'#EXTINF:5,',
|
'#EXTINF:5,',
|
||||||
'ex3.ts',
|
'ex3.ts',
|
||||||
'#EXT-X-ENDLIST'
|
'#EXT-X-ENDLIST'
|
||||||
|
|
@ -183,10 +175,6 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
|
|
||||||
assert.equal(this.parser.manifest.segments[1].cueIn, '', 'parser attached cue in tag');
|
assert.equal(this.parser.manifest.segments[1].cueIn, '', 'parser attached cue in tag');
|
||||||
assert.equal(this.parser.manifest.segments[2].cueIn, '15', 'cue in with data');
|
assert.equal(this.parser.manifest.segments[2].cueIn, '15', 'cue in with data');
|
||||||
assert.equal(
|
|
||||||
this.parser.manifest.segments[3].cueIn, '=abc',
|
|
||||||
'cue in without colon seperator'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('parses characteristics attribute', function(assert) {
|
QUnit.test('parses characteristics attribute', function(assert) {
|
||||||
|
|
@ -857,6 +845,617 @@ QUnit.module('m3u8s', function(hooks) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('PDT value is assigned to segments with explicit #EXT-X-PROGRAM-DATE-TIME tags', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-VERSION:6',
|
||||||
|
'#EXT-X-TARGETDURATION:8',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'https://example.com/playlist1.m3u8',
|
||||||
|
'#EXTINF:8.0,',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T22:14:10.053+00:00',
|
||||||
|
'https://example.com/playlist2.m3u8',
|
||||||
|
'#EXT-X-ENDLIST'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
assert.equal(this.parser.manifest.segments[0].programDateTime, new Date('2017-07-31T20:35:35.053+00:00').getTime());
|
||||||
|
assert.equal(this.parser.manifest.segments[1].programDateTime, new Date('2017-07-31T22:14:10.053+00:00').getTime());
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('backfill PDT values when the first EXT-X-PROGRAM-DATE-TIME tag appears after one or more Media Segment URIs', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-VERSION:6',
|
||||||
|
'#EXT-X-TARGETDURATION:8',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'https://example.com/playlist1.m3u8',
|
||||||
|
'#EXTINF:8.0,',
|
||||||
|
'https://example.com/playlist2.m3u8',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'https://example.com/playlist3.m3u8',
|
||||||
|
'#EXT-X-ENDLIST'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
const segments = this.parser.manifest.segments;
|
||||||
|
|
||||||
|
assert.equal(segments[2].programDateTime, new Date('2017-07-31T20:35:35.053+00:00').getTime());
|
||||||
|
assert.equal(segments[1].programDateTime, segments[2].programDateTime - (segments[1].duration * 1000));
|
||||||
|
assert.equal(segments[0].programDateTime, segments[1].programDateTime - (segments[0].duration * 1000));
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('extrapolates forward when subsequent fragments do not have explicit PDT tags', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-VERSION:6',
|
||||||
|
'#EXT-X-TARGETDURATION:8',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'https://example.com/playlist1.m3u8',
|
||||||
|
'#EXTINF:8.0,',
|
||||||
|
'https://example.com/playlist2.m3u8',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'https://example.com/playlist3.m3u8',
|
||||||
|
'#EXT-X-ENDLIST'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
const segments = this.parser.manifest.segments;
|
||||||
|
|
||||||
|
assert.equal(segments[0].programDateTime, new Date('2017-07-31T20:35:35.053+00:00').getTime());
|
||||||
|
assert.equal(segments[1].programDateTime, segments[0].programDateTime + segments[1].duration * 1000);
|
||||||
|
assert.equal(segments[2].programDateTime, segments[1].programDateTime + segments[2].duration * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-DATERANGE missing attribute', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'#EXT-X-DATERANGE #0 lacks required attribute(s): START-DATE'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-DATERANGE end date attribute is less than start date', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-DATE="2023-04-13T15:15:15.840000Z"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-DATERANGE duration or planned duration attribute is negative', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",PLANNED-DURATION=-38.4,DURATION=-15.5'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'EXT-X-DATERANGE DURATION must not be negative',
|
||||||
|
'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-DATERANGE has a END-ON-NEXT=YES attribute and a DURATION or END-DATE attribute', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:15:15.840000Z",END-ON-NEXT=YES, END-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-DATERANGE has a END-ON-NEXT=YES attribute but not a CLASS attribute', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-ON-NEXT=YES'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when playlist has multiple #EXT-X-DATERANGE tag same ID but different attribute values', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-ON-NEXT=YES,CLASS="CLASSATTRIBUTE"',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE1"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('when #EXT-X-DATERANGE has both DURATION and END-DATE attributes, value of the END-DATE attribute must be START-DATE + DURATION', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T15:16:15.840000Z",DURATION=14.0,END-DATE="2023-04-13T18:15:15.840000Z"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
assert.deepEqual(this.parser.manifest.dateRanges[0].endDate, new Date('2023-04-13T15:16:29.840000Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when playlist contains #EXT-X-DATERANGE tag but no #EXT-X-PROGRAM-DATE-TIME', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",START-DATE="2023-04-13T18:16:15.840000Z",END-ON-NEXT=YES,CLASS="sampleClassAttrib"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('playlist with multiple ext-x-daterange with same ID but no conflicting attributes', function(assert) {
|
||||||
|
const expectedDateRange = {
|
||||||
|
id: '12345',
|
||||||
|
scte35In: '0xFC30200FFF2',
|
||||||
|
scte35Out: '0xFC30200FFF2',
|
||||||
|
startDate: new Date('2023-04-13T18:16:15.840000Z'),
|
||||||
|
class: 'CLASSATTRIBUTE'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parser.push([
|
||||||
|
'#EXT-X-VERSION:3',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-DISCONTINUITY-SEQUENCE:0',
|
||||||
|
'#EXTINF:10,',
|
||||||
|
'media-00001.ts',
|
||||||
|
'#EXT-X-ENDLIST',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",SCTE35-IN=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z",CLASS="CLASSATTRIBUTE"',
|
||||||
|
'#EXT-X-DATERANGE:ID="12345",SCTE35-OUT=0xFC30200FFF2,START-DATE="2023-04-13T18:16:15.840000Z"'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
assert.equal(this.parser.manifest.dateRanges.length, 1, 'two dateranges with same ID are merged');
|
||||||
|
assert.deepEqual(this.parser.manifest.dateRanges[0], expectedDateRange);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('playlist with multiple ext-x-daterange ', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
' #EXTM3U',
|
||||||
|
'#EXT-X-VERSION:6',
|
||||||
|
'#EXT-X-TARGETDURATION:8',
|
||||||
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
||||||
|
'#EXT-X-PROGRAM-DATE-TIME:2017-07-31T20:35:35.053+00:00',
|
||||||
|
'#EXT-X-DATERANGE:ID="event1",START-DATE="2023-04-20T10:00:00Z",DURATION=30.0,END-DATE="2023-04-20T10:00:30Z",X-CUSTOM-KEY="value"',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'https://example.com/playlist1.m3u8',
|
||||||
|
'#EXT-SCTE35-IN:0xFC002F0000000000FF000014056FFFFFFF065870697070657220506F6F7200',
|
||||||
|
'#EXT-X-DATERANGE:ID="event2",START-DATE="2023-04-20T11:00:00Z",DURATION=60.0,END-DATE="2023-04-20T11:01:00Z",X-CUSTOM-KEY="value"',
|
||||||
|
'#EXTINF:8.0,',
|
||||||
|
'https://example.com/playlist2.m3u8',
|
||||||
|
'#EXT-SCTE35-OUT:0xFC002F0000000000FF000014056FFFFFFF065870697070657220506F6F7200',
|
||||||
|
'#EXT-X-DATERANGE:ID="event3",START-DATE="2023-04-20T12:00:00Z",DURATION=120.0,END-DATE="2023-04-20T12:02:00Z",X-CUSTOM-KEY="value"',
|
||||||
|
'#EXTINF:8.0',
|
||||||
|
'https://example.com/playlist3.m3u8',
|
||||||
|
'#EXT-SCTE35-IN:0xFC002F0000000000FF000014056FFFFFFF065870697070657220506F6F7200',
|
||||||
|
'#EXT-SCTE35-OUT:0xFC002F0000000000FF000014056FFFFFFF065870697070657220506F6F7200',
|
||||||
|
'#EXT-X-ENDLIST'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
assert.equal(this.parser.manifest.dateRanges.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses #EXT-X-INDEPENDENT-SEGMENTS', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-VERSION:6',
|
||||||
|
'#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.252,CAN-SKIP-UNTIL=42.0',
|
||||||
|
'#EXT-X-INDEPENDENT-SEGMENTS'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
assert.equal(this.parser.manifest.independentSegments, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses #EXT-X-I-FRAME-STREAM-INF', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=86000,URI="low/iframe.m3u8"',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=550000,URI="hi/iframe.m3u8"',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
||||||
|
'low/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=2560000',
|
||||||
|
'mid/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=7680000',
|
||||||
|
'hi/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"',
|
||||||
|
'audio-only.m3u8'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
assert.equal(this.parser.manifest.iFramePlaylists.length, 3);
|
||||||
|
assert.equal(this.parser.manifest.iFramePlaylists[0].uri, 'low/iframe.m3u8');
|
||||||
|
assert.strictEqual(this.parser.manifest.iFramePlaylists[0].attributes.BANDWIDTH, 86000);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns when #EXT-X-I-FRAME-STREAM-INF missing BANDWIDTH/URI attributes', function(assert) {
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:URI="low/iframe.m3u8"',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF:',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=1280000',
|
||||||
|
'low/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=2560000',
|
||||||
|
'mid/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=7680000',
|
||||||
|
'hi/audio-video.m3u8',
|
||||||
|
'#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"',
|
||||||
|
'audio-only.m3u8'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
const warnings = [
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH',
|
||||||
|
'#EXT-X-I-FRAME-STREAM-INF lacks required attribute(s): BANDWIDTH, URI'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
this.warnings,
|
||||||
|
warnings,
|
||||||
|
'warnings as expected'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
pathwayId: 'CDN-A'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/foo?bar=00012",PATHWAY-ID="CDN-A"');
|
||||||
|
this.parser.end();
|
||||||
|
assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('parses #EXT-X-CONTENT-STEERING without PATHWAY-ID', function(assert) {
|
||||||
|
const expectedContentSteeringObject = {
|
||||||
|
serverUri: '/bar?foo=00012'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parser.push('#EXT-X-CONTENT-STEERING:SERVER-URI="/bar?foo=00012"');
|
||||||
|
this.parser.end();
|
||||||
|
assert.deepEqual(this.parser.manifest.contentSteering, expectedContentSteeringObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('warns on #EXT-X-CONTENT-STEERING missing SERVER-URI', function(assert) {
|
||||||
|
const warning = ['#EXT-X-CONTENT-STEERING lacks required attribute(s): SERVER-URI'];
|
||||||
|
|
||||||
|
this.parser.push('#EXT-X-CONTENT-STEERING:PATHWAY-ID="CDN-A"');
|
||||||
|
this.parser.end();
|
||||||
|
assert.deepEqual(this.warnings, warning, 'warnings as expected');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.module('define', {
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3
|
||||||
|
beforeEach() {
|
||||||
|
this.errors = [];
|
||||||
|
|
||||||
|
this.parser.on('error', (err) => this.errors.push(err.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fails on missing attributes', function(assert) {
|
||||||
|
const err = ['EXT-X-DEFINE: No attribute'];
|
||||||
|
|
||||||
|
this.parser.push('#EXT-X-DEFINE:');
|
||||||
|
this.parser.end();
|
||||||
|
assert.deepEqual(this.errors, err, 'errors as expected');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fails on disallowed combinatons', function(assert) {
|
||||||
|
const permutations = [
|
||||||
|
'#EXT-X-DEFINE:NAME="a",QUERYPARAM="b"',
|
||||||
|
'#EXT-X-DEFINE:NAME="a",IMPORT="b"',
|
||||||
|
'#EXT-X-DEFINE:QUERYPARAM="a",IMPORT="b"',
|
||||||
|
'#EXT-X-DEFINE:NAME="a",QUERYPARAM="b",IMPORT="c"'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.expect(permutations.length);
|
||||||
|
|
||||||
|
permutations.forEach((p) => {
|
||||||
|
this.parser = new Parser();
|
||||||
|
this.parser.on('error', (e) => {
|
||||||
|
assert.equal(e.message, 'EXT-X-DEFINE: Invalid attributes', `${p} errors as expected`);
|
||||||
|
});
|
||||||
|
this.parser.push(p);
|
||||||
|
this.parser.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('query params substituted', function(assert) {
|
||||||
|
this.parser = new Parser({
|
||||||
|
uri: 'https://example.com?aParam=aValue'
|
||||||
|
});
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||||
|
'#EXTINF:10',
|
||||||
|
'segment.ts?replaced_param={$aParam}'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param stored');
|
||||||
|
assert.equal('segment.ts?replaced_param=aValue', this.parser.manifest.segments[0].uri, 'substituted in url');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('query params substituted with relative URL', function(assert) {
|
||||||
|
this.parser = new Parser({
|
||||||
|
uri: 'playlist.m3u8?aParam=aValue'
|
||||||
|
});
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||||
|
'#EXTINF:10',
|
||||||
|
'segment.ts?replaced_param={$aParam}'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param stored');
|
||||||
|
assert.equal('segment.ts?replaced_param=aValue', this.parser.manifest.segments[0].uri, 'substituted in url');
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fails with missing query params', function(assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
this.parser = new Parser({
|
||||||
|
uri: 'https://example.com?bParam=bValue'
|
||||||
|
});
|
||||||
|
this.parser.on('error', (e) => {
|
||||||
|
assert.equal(e.message, 'EXT-X-DEFINE: No query param aParam');
|
||||||
|
});
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-DEFINE:QUERYPARAM="aParam"',
|
||||||
|
'#EXTINF:10',
|
||||||
|
'segment.ts?replacedparam={$aParam}'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fails on redefinition', function(assert) {
|
||||||
|
const permutations = [
|
||||||
|
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:NAME="a",VALUE="c"'],
|
||||||
|
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||||
|
['#EXT-X-DEFINE:NAME="a",VALUE="b"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||||
|
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||||
|
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||||
|
['#EXT-X-DEFINE:IMPORT="a"', '#EXT-X-DEFINE:NAME="a",VALUE="c"'],
|
||||||
|
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:IMPORT="a"'],
|
||||||
|
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:QUERYPARAM="a"'],
|
||||||
|
['#EXT-X-DEFINE:QUERYPARAM="a"', '#EXT-X-DEFINE:NAME="a",VALUE="c"']
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.expect(permutations.length);
|
||||||
|
|
||||||
|
permutations.forEach((p) => {
|
||||||
|
this.parser = new Parser({
|
||||||
|
uri: 'https:example.com?a=1',
|
||||||
|
mainDefinitions: {
|
||||||
|
a: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.parser.on('error', (e) => {
|
||||||
|
assert.equal(e.message, 'EXT-X-DEFINE: Duplicate name a', 'errosr on combination');
|
||||||
|
});
|
||||||
|
this.parser.push(p.join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fails with IMPORT on main playlist', function(assert) {
|
||||||
|
this.parser.on('error', function(e) {
|
||||||
|
assert.equal(e.message, 'EXT-X-DEFINE: No value imported_param to import, or IMPORT used on main playlist', 'fails when missing');
|
||||||
|
});
|
||||||
|
this.parser.push('#EXT-X-DEFINE:IMPORT="imported_param"');
|
||||||
|
this.parser.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('named and imported substiutions work', function(assert) {
|
||||||
|
this.parser = new Parser({
|
||||||
|
mainDefinitions: {
|
||||||
|
aParam: 'aValue',
|
||||||
|
engLabel: 'Anglais'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.parser.push([
|
||||||
|
'#EXTM3U',
|
||||||
|
'#EXT-X-DEFINE:IMPORT="aParam"',
|
||||||
|
'#EXT-X-DEFINE:NAME="bParam",VALUE="bValue"',
|
||||||
|
'#EXT-X-DEFINE:IMPORT="engLabel"',
|
||||||
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="eng",NAME="{$engLabel}",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8?bParam={$bParam}"',
|
||||||
|
'#EXTINF:10',
|
||||||
|
'segment.ts?aParam={$aParam}&bParam={$bParam}'
|
||||||
|
].join('\n'));
|
||||||
|
this.parser.end();
|
||||||
|
|
||||||
|
assert.equal('aValue', this.parser.manifest.definitions.aParam, 'value of param from import stored');
|
||||||
|
assert.equal('bValue', this.parser.manifest.definitions.bParam, 'value of param from name stored');
|
||||||
|
assert.equal('segment.ts?aParam=aValue&bParam=bValue', this.parser.manifest.segments[0].uri, 'substituted in uri');
|
||||||
|
assert.ok(this.parser.manifest.mediaGroups.AUDIO.aac.hasOwnProperty('Anglais'), 'replacement in attribute');
|
||||||
|
assert.equal('eng/prog_index.m3u8?bParam=bValue', this.parser.manifest.mediaGroups.AUDIO.aac.Anglais.uri, 'replacement in uri in attribute');
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.module('integration');
|
QUnit.module('integration');
|
||||||
|
|
||||||
for (const key in testDataExpected) {
|
for (const key in testDataExpected) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue