mirror of
https://github.com/videojs/m3u8-parser.git
synced 2026-06-06 08:22:33 +00:00
Compare commits
No commits in common. "main" and "remove-version-tests" have entirely different histories.
main
...
remove-ver
70 changed files with 133 additions and 2010 deletions
73
CHANGELOG.md
73
CHANGELOG.md
|
|
@ -1,76 +1,3 @@
|
|||
<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>
|
||||
# [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,13 +13,12 @@ m3u8 parser
|
|||
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Constructor Options](#constructor-options)
|
||||
- [Parsed Output](#parsed-output)
|
||||
- [Supported Tags](#supported-tags)
|
||||
- [Basic Playlist Tags](#basic-playlist-tags)
|
||||
- [Media Segment Tags](#media-segment-tags)
|
||||
- [Media Playlist Tags](#media-playlist-tags)
|
||||
- [Main Playlist Tags](#main-playlist-tags)
|
||||
- [Master Playlist Tags](#master-playlist-tags)
|
||||
- [Experimental Tags](#experimental-tags)
|
||||
- [EXT-X-CUE-OUT](#ext-x-cue-out)
|
||||
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
|
||||
|
|
@ -58,7 +57,6 @@ var manifest = [
|
|||
'0.ts',
|
||||
'#EXTINF:6,',
|
||||
'1.ts',
|
||||
'#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z'
|
||||
'#EXTINF:6,',
|
||||
'2.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
|
|
@ -71,21 +69,6 @@ parser.end();
|
|||
|
||||
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
|
||||
|
||||
|
|
@ -96,7 +79,6 @@ Manifest {
|
|||
allowCache: boolean,
|
||||
endList: boolean,
|
||||
mediaSequence: number,
|
||||
dateRanges: [],
|
||||
discontinuitySequence: number,
|
||||
playlistType: string,
|
||||
custom: {},
|
||||
|
|
@ -131,13 +113,11 @@ Manifest {
|
|||
discontinuityStarts: [number],
|
||||
segments: [
|
||||
{
|
||||
title: string,
|
||||
byterange: {
|
||||
length: number,
|
||||
offset: number
|
||||
},
|
||||
duration: number,
|
||||
programDateTime: number,
|
||||
attributes: {},
|
||||
discontinuity: number,
|
||||
uri: string,
|
||||
|
|
@ -178,8 +158,6 @@ Manifest {
|
|||
* [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-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
|
||||
|
||||
|
|
@ -189,16 +167,11 @@ Manifest {
|
|||
* [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-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)
|
||||
|
||||
### Main Playlist Tags
|
||||
### Master Playlist Tags
|
||||
|
||||
* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
|
||||
* [EXT-X-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.2)
|
||||
* [EXT-X-I-FRAME-STREAM-INF](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.3)
|
||||
* [EXT-X-CONTENT-STEERING](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6)
|
||||
* [EXT-X-DEFINE](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.2.3)
|
||||
|
||||
### Experimental Tags
|
||||
|
||||
|
|
@ -263,8 +236,12 @@ Example media playlist using `EXT-X-CUE-` tags.
|
|||
|
||||
### 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-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
|
||||
|
||||
|
|
|
|||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "m3u8-parser",
|
||||
"version": "7.2.0",
|
||||
"version": "5.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1579,12 +1579,13 @@
|
|||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
|
||||
"integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0"
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"JSONStream": {
|
||||
|
|
@ -8812,6 +8813,11 @@
|
|||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "m3u8-parser",
|
||||
"version": "7.2.0",
|
||||
"version": "5.0.0",
|
||||
"description": "m3u8 parser",
|
||||
"main": "dist/m3u8-parser.cjs.js",
|
||||
"module": "dist/m3u8-parser.es.js",
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -43,14 +43,9 @@ const attributeSeparator = function() {
|
|||
* @param {string} attributes the attribute line to parse
|
||||
*/
|
||||
const parseAttributes = function(attributes) {
|
||||
const result = {};
|
||||
|
||||
if (!attributes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// split the string using attributes as the separator
|
||||
const attrs = attributes.split(attributeSeparator());
|
||||
const result = {};
|
||||
let i = attrs.length;
|
||||
let attr;
|
||||
|
||||
|
|
@ -71,29 +66,6 @@ const parseAttributes = function(attributes) {
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string into a resolution object
|
||||
*
|
||||
* @param {string} resolution a string such as 3840x2160
|
||||
*
|
||||
* @return {Object} An object representing the resolution
|
||||
*
|
||||
*/
|
||||
const parseResolution = (resolution) => {
|
||||
const split = resolution.split('x');
|
||||
const result = {};
|
||||
|
||||
if (split[0]) {
|
||||
result.width = parseInt(split[0], 10);
|
||||
}
|
||||
|
||||
if (split[1]) {
|
||||
result.height = parseInt(split[1], 10);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* A line-level M3U8 parser event stream. It expects to receive input one
|
||||
* line at a time and performs a context-free parse of its contents. A stream
|
||||
|
|
@ -192,7 +164,7 @@ export default class ParseStream extends Stream {
|
|||
});
|
||||
return;
|
||||
}
|
||||
match = (/^#EXTINF:([0-9\.]*)?,?(.*)?$/).exec(newLine);
|
||||
match = (/^#EXTINF:?([0-9\.]*)?,?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -207,7 +179,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-TARGETDURATION:([0-9.]*)?/).exec(newLine);
|
||||
match = (/^#EXT-X-TARGETDURATION:?([0-9.]*)?/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -219,7 +191,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-VERSION:([0-9.]*)?/).exec(newLine);
|
||||
match = (/^#EXT-X-VERSION:?([0-9.]*)?/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -231,7 +203,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/).exec(newLine);
|
||||
match = (/^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -243,7 +215,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/).exec(newLine);
|
||||
match = (/^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -255,7 +227,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-PLAYLIST-TYPE:(.*)?$/).exec(newLine);
|
||||
match = (/^#EXT-X-PLAYLIST-TYPE:?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -267,7 +239,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-BYTERANGE:(.*)?$/).exec(newLine);
|
||||
match = (/^#EXT-X-BYTERANGE:?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = Object.assign(parseByterange(match[1]), {
|
||||
type: 'tag',
|
||||
|
|
@ -276,7 +248,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-ALLOW-CACHE:(YES|NO)?/).exec(newLine);
|
||||
match = (/^#EXT-X-ALLOW-CACHE:?(YES|NO)?/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -288,7 +260,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-MAP:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-MAP:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -309,7 +281,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-STREAM-INF:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-STREAM-INF:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -319,7 +291,16 @@ export default class ParseStream extends Stream {
|
|||
event.attributes = parseAttributes(match[1]);
|
||||
|
||||
if (event.attributes.RESOLUTION) {
|
||||
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
|
||||
const split = event.attributes.RESOLUTION.split('x');
|
||||
const resolution = {};
|
||||
|
||||
if (split[0]) {
|
||||
resolution.width = parseInt(split[0], 10);
|
||||
}
|
||||
if (split[1]) {
|
||||
resolution.height = parseInt(split[1], 10);
|
||||
}
|
||||
event.attributes.RESOLUTION = resolution;
|
||||
}
|
||||
if (event.attributes.BANDWIDTH) {
|
||||
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
||||
|
|
@ -334,7 +315,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-MEDIA:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-MEDIA:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -362,7 +343,7 @@ export default class ParseStream extends Stream {
|
|||
});
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-PROGRAM-DATE-TIME:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -375,7 +356,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-KEY:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-KEY:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -400,7 +381,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-START:(.*)$/).exec(newLine);
|
||||
match = (/^#EXT-X-START:?(.*)$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -415,7 +396,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-CUE-OUT-CONT:(.*)?$/).exec(newLine);
|
||||
match = (/^#EXT-X-CUE-OUT-CONT:?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -429,7 +410,7 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
return;
|
||||
}
|
||||
match = (/^#EXT-X-CUE-OUT:(.*)?$/).exec(newLine);
|
||||
match = (/^#EXT-X-CUE-OUT:?(.*)?$/).exec(newLine);
|
||||
if (match) {
|
||||
event = {
|
||||
type: 'tag',
|
||||
|
|
@ -581,128 +562,6 @@ export default class ParseStream extends Stream {
|
|||
this.trigger('data', event);
|
||||
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
|
||||
this.trigger('data', {
|
||||
|
|
|
|||
259
src/parser.js
259
src/parser.js
|
|
@ -88,20 +88,14 @@ const setHoldBack = function(manifest) {
|
|||
* requires some property of the manifest object to be defaulted.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
export default class Parser extends Stream {
|
||||
constructor(opts = {}) {
|
||||
constructor() {
|
||||
super();
|
||||
this.lineStream = new LineStream();
|
||||
this.parseStream = new 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 */
|
||||
const self = this;
|
||||
|
|
@ -130,8 +124,6 @@ export default class Parser extends Stream {
|
|||
this.manifest = {
|
||||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
// keep track of the last seen segment's byte range end, as segments are not required
|
||||
|
|
@ -140,7 +132,6 @@ export default class Parser extends Stream {
|
|||
let lastByterangeEnd = 0;
|
||||
// keep track of the last seen part's byte range end.
|
||||
let lastPartByterangeEnd = 0;
|
||||
const dateRangeTags = {};
|
||||
|
||||
this.on('end', () => {
|
||||
// only add preloadSegment if we don't yet have a uri for it.
|
||||
|
|
@ -168,22 +159,6 @@ export default class Parser extends Stream {
|
|||
let mediaGroup;
|
||||
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() {
|
||||
// switch based on the tag type
|
||||
|
|
@ -245,11 +220,6 @@ export default class Parser extends Stream {
|
|||
message: 'defaulting discontinuity sequence to zero'
|
||||
});
|
||||
}
|
||||
|
||||
if (entry.title) {
|
||||
currentUri.title = entry.title;
|
||||
}
|
||||
|
||||
if (entry.duration > 0) {
|
||||
currentUri.duration = entry.duration;
|
||||
}
|
||||
|
|
@ -488,24 +458,9 @@ export default class Parser extends Stream {
|
|||
this.manifest.dateTimeString = entry.dateTimeString;
|
||||
this.manifest.dateTimeObject = entry.dateTimeObject;
|
||||
}
|
||||
|
||||
currentUri.dateTimeString = entry.dateTimeString;
|
||||
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() {
|
||||
if (!isFinite(entry.duration) || entry.duration < 0) {
|
||||
|
|
@ -676,195 +631,7 @@ export default class Parser extends Stream {
|
|||
}
|
||||
|
||||
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);
|
||||
},
|
||||
uri() {
|
||||
|
|
@ -891,12 +658,6 @@ export default class Parser extends Stream {
|
|||
// reset the last byterange end as it needs to be 0 between parts
|
||||
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
|
||||
currentUri = {};
|
||||
},
|
||||
|
|
@ -918,14 +679,6 @@ export default class Parser extends Stream {
|
|||
});
|
||||
}
|
||||
|
||||
requiredCompatibilityversion(currentVersion, targetVersion) {
|
||||
if (currentVersion < targetVersion || !currentVersion) {
|
||||
this.trigger('warn', {
|
||||
message: `manifest must be at least version ${targetVersion}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
warnOnMissingAttributes_(identifier, attributes, required) {
|
||||
const missing = [];
|
||||
|
||||
|
|
@ -957,13 +710,7 @@ export default class Parser extends Stream {
|
|||
end() {
|
||||
// flush any buffered input
|
||||
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');
|
||||
}
|
||||
/**
|
||||
|
|
@ -971,7 +718,7 @@ export default class Parser extends Stream {
|
|||
*
|
||||
* @param {Object} options a map of options for the added parser
|
||||
* @param {RegExp} options.expression a regular expression to match the custom header
|
||||
* @param {string} options.customType the custom type to register to the output
|
||||
* @param {string} options.type the type to register to the output
|
||||
* @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
|
||||
*/
|
||||
|
|
|
|||
2
test/fixtures/integration/absoluteUris.js
vendored
2
test/fixtures/integration/absoluteUris.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
2
test/fixtures/integration/allowCache.js
vendored
2
test/fixtures/integration/allowCache.js
vendored
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
2
test/fixtures/integration/alternateAudio.js
vendored
2
test/fixtures/integration/alternateAudio.js
vendored
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
// TYPE
|
||||
'AUDIO': {
|
||||
|
|
|
|||
2
test/fixtures/integration/alternateVideo.js
vendored
2
test/fixtures/integration/alternateVideo.js
vendored
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aac: {
|
||||
|
|
|
|||
2
test/fixtures/integration/brightcove.js
vendored
2
test/fixtures/integration/brightcove.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
iFramePlaylists: [],
|
||||
dateRanges: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
2
test/fixtures/integration/byteRange.js
vendored
2
test/fixtures/integration/byteRange.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
4
test/fixtures/integration/dateTime.js
vendored
4
test/fixtures/integration/dateTime.js
vendored
|
|
@ -1,14 +1,11 @@
|
|||
module.exports = {
|
||||
allowCache: false,
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
dateTimeString: '2016-06-22T09:20:16.166-04:00',
|
||||
dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
|
||||
programDateTime: 1466601616166,
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: 'hls_450k_video.ts'
|
||||
|
|
@ -16,7 +13,6 @@ module.exports = {
|
|||
{
|
||||
dateTimeString: '2016-06-22T09:20:26.166-04:00',
|
||||
dateTimeObject: new Date('2016-06-22T09:20:26.166-04:00'),
|
||||
programDateTime: 1466601626166,
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: 'hls_450k_video.ts'
|
||||
|
|
|
|||
2
test/fixtures/integration/diff-init-key.js
vendored
2
test/fixtures/integration/diff-init-key.js
vendored
|
|
@ -2,8 +2,6 @@ module.exports = {
|
|||
allowCache: true,
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 7794,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
2
test/fixtures/integration/disallowCache.js
vendored
2
test/fixtures/integration/disallowCache.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: false,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
14
test/fixtures/integration/disc-sequence.js
vendored
14
test/fixtures/integration/disc-sequence.js
vendored
|
|
@ -1,34 +1,28 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
discontinuitySequence: 3,
|
||||
segments: [
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 3,
|
||||
uri: '001.ts',
|
||||
title: '0'
|
||||
uri: '001.ts'
|
||||
},
|
||||
{
|
||||
duration: 19,
|
||||
timeline: 3,
|
||||
uri: '002.ts',
|
||||
title: '0'
|
||||
uri: '002.ts'
|
||||
},
|
||||
{
|
||||
discontinuity: true,
|
||||
duration: 10,
|
||||
timeline: 4,
|
||||
uri: '003.ts',
|
||||
title: '0'
|
||||
uri: '003.ts'
|
||||
},
|
||||
{
|
||||
duration: 11,
|
||||
timeline: 4,
|
||||
uri: '004.ts',
|
||||
title: '0'
|
||||
uri: '004.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 19,
|
||||
|
|
|
|||
29
test/fixtures/integration/discontinuity.js
vendored
29
test/fixtures/integration/discontinuity.js
vendored
|
|
@ -1,66 +1,55 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
discontinuitySequence: 0,
|
||||
segments: [
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '001.ts',
|
||||
title: '0'
|
||||
uri: '001.ts'
|
||||
},
|
||||
{
|
||||
duration: 19,
|
||||
timeline: 0,
|
||||
uri: '002.ts',
|
||||
title: '0'
|
||||
uri: '002.ts'
|
||||
},
|
||||
{
|
||||
discontinuity: true,
|
||||
duration: 10,
|
||||
timeline: 1,
|
||||
uri: '003.ts',
|
||||
title: '0'
|
||||
uri: '003.ts'
|
||||
},
|
||||
{
|
||||
duration: 11,
|
||||
timeline: 1,
|
||||
uri: '004.ts',
|
||||
title: '0'
|
||||
uri: '004.ts'
|
||||
},
|
||||
{
|
||||
discontinuity: true,
|
||||
duration: 10,
|
||||
timeline: 2,
|
||||
uri: '005.ts',
|
||||
title: '0'
|
||||
uri: '005.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 2,
|
||||
uri: '006.ts',
|
||||
title: '0'
|
||||
uri: '006.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 2,
|
||||
uri: '007.ts',
|
||||
title: '0'
|
||||
uri: '007.ts'
|
||||
},
|
||||
{
|
||||
discontinuity: true,
|
||||
duration: 10,
|
||||
timeline: 3,
|
||||
uri: '008.ts',
|
||||
title: '0'
|
||||
uri: '008.ts'
|
||||
},
|
||||
{
|
||||
duration: 16,
|
||||
timeline: 3,
|
||||
uri: '009.ts',
|
||||
title: '0'
|
||||
uri: '009.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 19,
|
||||
|
|
|
|||
2
test/fixtures/integration/domainUris.js
vendored
2
test/fixtures/integration/domainUris.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
2
test/fixtures/integration/empty.js
vendored
2
test/fixtures/integration/empty.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
2
test/fixtures/integration/emptyAllowCache.js
vendored
2
test/fixtures/integration/emptyAllowCache.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
14
test/fixtures/integration/emptyMediaSequence.js
vendored
14
test/fixtures/integration/emptyMediaSequence.js
vendored
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
2
test/fixtures/integration/encrypted.js
vendored
2
test/fixtures/integration/encrypted.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 7794,
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
|
|
|
|||
2
test/fixtures/integration/event.js
vendored
2
test/fixtures/integration/event.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'EVENT',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 1,
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
5
test/fixtures/integration/extinf.js
vendored
5
test/fixtures/integration/extinf.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
@ -21,8 +19,7 @@ module.exports = {
|
|||
},
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: 'hls_450k_video.ts',
|
||||
title: ';asljasdfii11)))00,'
|
||||
uri: 'hls_450k_video.ts'
|
||||
},
|
||||
{
|
||||
byterange: {
|
||||
|
|
|
|||
5
test/fixtures/integration/fmp4.js
vendored
5
test/fixtures/integration/fmp4.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 1,
|
||||
playlistType: 'VOD',
|
||||
targetDuration: 6,
|
||||
|
|
@ -42,6 +40,5 @@ module.exports = {
|
|||
}
|
||||
],
|
||||
endList: true,
|
||||
version: 7,
|
||||
independentSegments: true
|
||||
version: 7
|
||||
};
|
||||
|
|
|
|||
2
test/fixtures/integration/headerOnly.js
vendored
2
test/fixtures/integration/headerOnly.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
segments: []
|
||||
};
|
||||
|
|
|
|||
288
test/fixtures/integration/iFramePlaylist.js
vendored
288
test/fixtures/integration/iFramePlaylist.js
vendored
|
|
@ -1,288 +0,0 @@
|
|||
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
42
test/fixtures/integration/iFramePlaylist.m3u8
vendored
|
|
@ -1,42 +0,0 @@
|
|||
#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
45
test/fixtures/integration/iFramesOnly.js
vendored
|
|
@ -1,45 +0,0 @@
|
|||
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
19
test/fixtures/integration/iFramesOnly.m3u8
vendored
|
|
@ -1,19 +0,0 @@
|
|||
#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,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 8,
|
||||
|
|
|
|||
29
test/fixtures/integration/liveStart30sBefore.js
vendored
29
test/fixtures/integration/liveStart30sBefore.js
vendored
|
|
@ -1,62 +1,51 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '001.ts',
|
||||
title: '0'
|
||||
uri: '001.ts'
|
||||
},
|
||||
{
|
||||
duration: 19,
|
||||
timeline: 0,
|
||||
uri: '002.ts',
|
||||
title: '0'
|
||||
uri: '002.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '003.ts',
|
||||
title: '0'
|
||||
uri: '003.ts'
|
||||
},
|
||||
{
|
||||
duration: 11,
|
||||
timeline: 0,
|
||||
uri: '004.ts',
|
||||
title: '0'
|
||||
uri: '004.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '005.ts',
|
||||
title: '0'
|
||||
uri: '005.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '006.ts',
|
||||
title: '0'
|
||||
uri: '006.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '007.ts',
|
||||
title: '0'
|
||||
uri: '007.ts'
|
||||
},
|
||||
{
|
||||
duration: 10,
|
||||
timeline: 0,
|
||||
uri: '008.ts',
|
||||
title: '0'
|
||||
uri: '008.ts'
|
||||
},
|
||||
{
|
||||
duration: 16,
|
||||
timeline: 0,
|
||||
uri: '009.ts',
|
||||
title: '0'
|
||||
uri: '009.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 10,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
preloadSegment: {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
preloadSegment: {
|
||||
|
|
|
|||
9
test/fixtures/integration/llhls.js
vendored
9
test/fixtures/integration/llhls.js
vendored
|
|
@ -2,10 +2,8 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
||||
dateTimeString: '2019-02-14T02:13:36.106Z',
|
||||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 266,
|
||||
preloadSegment: {
|
||||
map: {uri: 'init.mp4'},
|
||||
|
|
@ -42,7 +40,6 @@ module.exports = {
|
|||
{
|
||||
dateTimeObject: new Date('2019-02-14T02:13:36.106Z'),
|
||||
dateTimeString: '2019-02-14T02:13:36.106Z',
|
||||
programDateTime: 1550110416106,
|
||||
duration: 4.00008,
|
||||
map: {
|
||||
uri: 'init.mp4'
|
||||
|
|
@ -55,7 +52,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110420106.08,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence267.mp4'
|
||||
},
|
||||
|
|
@ -64,7 +60,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110424106.1602,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence268.mp4'
|
||||
},
|
||||
|
|
@ -73,7 +68,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110428106.2402,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence269.mp4'
|
||||
},
|
||||
|
|
@ -82,7 +76,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110432106.3203,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence270.mp4'
|
||||
},
|
||||
|
|
@ -91,7 +84,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110436106.4004,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence271.mp4',
|
||||
parts: [
|
||||
|
|
@ -154,7 +146,6 @@ module.exports = {
|
|||
map: {
|
||||
uri: 'init.mp4'
|
||||
},
|
||||
programDateTime: 1550110440106,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence272.mp4',
|
||||
parts: [
|
||||
|
|
|
|||
6
test/fixtures/integration/llhlsDelta.js
vendored
6
test/fixtures/integration/llhlsDelta.js
vendored
|
|
@ -2,10 +2,8 @@ module.exports = {
|
|||
allowCache: true,
|
||||
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
||||
dateTimeString: '2019-02-14T02:14:00.106Z',
|
||||
dateRanges: [],
|
||||
discontinuitySequence: 0,
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 266,
|
||||
preloadSegment: {
|
||||
timeline: 0,
|
||||
|
|
@ -44,19 +42,16 @@ module.exports = {
|
|||
segments: [
|
||||
{
|
||||
duration: 4.00008,
|
||||
programDateTime: 1550110428105.7598,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence269.mp4'
|
||||
},
|
||||
{
|
||||
duration: 4.00008,
|
||||
programDateTime: 1550110432105.8398,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence270.mp4'
|
||||
},
|
||||
{
|
||||
duration: 4.00008,
|
||||
programDateTime: 1550110436105.92,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence271.mp4',
|
||||
parts: [
|
||||
|
|
@ -116,7 +111,6 @@ module.exports = {
|
|||
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
|
||||
dateTimeString: '2019-02-14T02:14:00.106Z',
|
||||
duration: 4.00008,
|
||||
programDateTime: 1550110440106,
|
||||
timeline: 0,
|
||||
uri: 'fileSequence272.mp4',
|
||||
parts: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
90
test/fixtures/integration/master-fmp4.js
vendored
90
test/fixtures/integration/master-fmp4.js
vendored
|
|
@ -1,93 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
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: {
|
||||
'AUDIO': {
|
||||
aud1: {
|
||||
|
|
@ -548,6 +461,5 @@ module.exports = {
|
|||
uri: 'v1/prog_index.m3u8'
|
||||
}],
|
||||
segments: [],
|
||||
version: 6,
|
||||
independentSegments: true
|
||||
version: 6
|
||||
};
|
||||
|
|
|
|||
2
test/fixtures/integration/master.js
vendored
2
test/fixtures/integration/master.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
2
test/fixtures/integration/media.js
vendored
2
test/fixtures/integration/media.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
14
test/fixtures/integration/mediaSequence.js
vendored
14
test/fixtures/integration/mediaSequence.js
vendored
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
2
test/fixtures/integration/missingEndlist.js
vendored
2
test/fixtures/integration/missingEndlist.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
2
test/fixtures/integration/missingExtinf.js
vendored
2
test/fixtures/integration/missingExtinf.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 8,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
'audio-lo': {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
targetDuration: 10,
|
||||
segments: [
|
||||
|
|
|
|||
2
test/fixtures/integration/multipleVideo.js
vendored
2
test/fixtures/integration/multipleVideo.js
vendored
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
discontinuityStarts: [],
|
||||
iFramePlaylists: [],
|
||||
mediaGroups: {
|
||||
'AUDIO': {
|
||||
aac: {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: -11,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
2
test/fixtures/integration/playlist.js
vendored
2
test/fixtures/integration/playlist.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 17,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
2
test/fixtures/integration/start.js
vendored
2
test/fixtures/integration/start.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlists: [
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
|||
14
test/fixtures/integration/twoMediaSequences.js
vendored
14
test/fixtures/integration/twoMediaSequences.js
vendored
|
|
@ -1,33 +1,27 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 11,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
duration: 6.64,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.08,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 6.6,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
timeline: 0,
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
|
||||
title: '{}'
|
||||
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts'
|
||||
}
|
||||
],
|
||||
targetDuration: 8,
|
||||
|
|
|
|||
2
test/fixtures/integration/versionInvalid.js
vendored
2
test/fixtures/integration/versionInvalid.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
2
test/fixtures/integration/whiteSpace.js
vendored
2
test/fixtures/integration/whiteSpace.js
vendored
|
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
mediaSequence: 0,
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
|
|
|
|||
2
test/fixtures/integration/zeroDuration.js
vendored
2
test/fixtures/integration/zeroDuration.js
vendored
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
allowCache: true,
|
||||
mediaSequence: 0,
|
||||
dateRanges: [],
|
||||
iFramePlaylists: [],
|
||||
playlistType: 'VOD',
|
||||
segments: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ QUnit.test('parses #EXTM3U tags', function(assert) {
|
|||
|
||||
// #EXTINF
|
||||
QUnit.test('parses minimal #EXTINF tags', function(assert) {
|
||||
const manifest = '#EXTINF:\n';
|
||||
const manifest = '#EXTINF\n';
|
||||
let element;
|
||||
|
||||
this.parseStream.on('data', function(elem) {
|
||||
|
|
@ -319,7 +319,7 @@ QUnit.test('parses #EXTINF tags with carriage returns', function(assert) {
|
|||
|
||||
// #EXT-X-TARGETDURATION
|
||||
QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function(assert) {
|
||||
const manifest = '#EXT-X-TARGETDURATION:\n';
|
||||
const manifest = '#EXT-X-TARGETDURATION\n';
|
||||
let element;
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
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
|
||||
QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function(assert) {
|
||||
const manifest = '#EXT-X-BYTERANGE:\n';
|
||||
const manifest = '#EXT-X-BYTERANGE\n';
|
||||
let element;
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
this.parseStream.on('data', function(elem) {
|
||||
|
|
@ -698,14 +698,6 @@ QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
|
|||
'avc1.4d400d, mp4a.40.2',
|
||||
'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) {
|
||||
const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
|
||||
|
|
@ -817,6 +809,16 @@ QUnit.test('parses lightly-broken #EXT-X-KEY tags', function(assert) {
|
|||
'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;
|
||||
manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
|
||||
this.lineStream.push(manifest);
|
||||
|
|
@ -887,104 +889,6 @@ 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.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) {
|
||||
const manifest = '\n';
|
||||
|
|
@ -997,40 +901,3 @@ QUnit.test('ignores empty lines', function(assert) {
|
|||
|
||||
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,10 +108,11 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
'#EXT-X-CUE-OUT:10',
|
||||
'#EXTINF:5,',
|
||||
'ex2.ts',
|
||||
'#EXT-X-CUE-OUT15',
|
||||
'#EXT-UKNOWN-TAG',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-CUE-OUT:',
|
||||
'#EXT-X-CUE-OUT',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
|
|
@ -121,6 +122,7 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
this.parser.end();
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
|
|
@ -133,10 +135,11 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
'#EXT-X-CUE-OUT-CONT:10/60',
|
||||
'#EXTINF:5,',
|
||||
'ex2.ts',
|
||||
'#EXT-X-CUE-OUT-CONT15/30',
|
||||
'#EXT-UKNOWN-TAG',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-CUE-OUT-CONT:',
|
||||
'#EXT-X-CUE-OUT-CONT',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
|
|
@ -149,6 +152,10 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
this.parser.manifest.segments[1].cueOutCont, '10/60',
|
||||
'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');
|
||||
});
|
||||
|
||||
|
|
@ -158,13 +165,14 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
'#EXTINF:5,',
|
||||
'#COMMENT',
|
||||
'ex1.ts',
|
||||
'#EXT-X-CUE-IN:',
|
||||
'#EXT-X-CUE-IN',
|
||||
'#EXTINF:5,',
|
||||
'ex2.ts',
|
||||
'#EXT-X-CUE-IN:15',
|
||||
'#EXT-UKNOWN-TAG',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-CUE-IN=abc',
|
||||
'#EXTINF:5,',
|
||||
'ex3.ts',
|
||||
'#EXT-X-ENDLIST'
|
||||
|
|
@ -175,6 +183,10 @@ QUnit.module('m3u8s', function(hooks) {
|
|||
|
||||
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[3].cueIn, '=abc',
|
||||
'cue in without colon seperator'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('parses characteristics attribute', function(assert) {
|
||||
|
|
@ -845,617 +857,6 @@ 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');
|
||||
|
||||
for (const key in testDataExpected) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue