Compare commits

..

64 commits

Author SHA1 Message Date
mister-ben
26d880375e 7.2.0 2024-08-21 22:06:55 +02:00
André M.
c060bc7338
chore: update vhs-utils dependency (#182)
* chore: update vhs-utils dependency to 4.1.1

---------

Co-authored-by: mister-ben <1676039+mister-ben@users.noreply.github.com>
2024-08-21 22:06:15 +02:00
mister-ben
ba6e7cbafe
feat: add support for #EXT-X-DEFINE (#185)
* feat: add support for EXT-X-DEFINE

* tests

* readme

* cleanup

* work with relative URLs

* missing return

* fix typo

Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com>

* lint

* lint

---------

Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com>
2024-08-21 18:10:14 +02:00
André M.
e5dbdb6288
feat: add support for #EXT-X-I-FRAMES-ONLY (#173)
* feat: add support for #EXT-X-I-FRAMES-ONLY

Handles I-frames-only `segments`, providing a basis for the creation of trick-play functionality.

**parse-stream.js**

- add match statement for parsing the `EXT-X-I-FRAMES-ONLY` tag
- add test case

**parser.js**

- add a property `iFramesOnly` to the `manifest`
- add a function to validate the minimum version required
- trigger a `warn` event if the minimum version required is not supported or undefined, as required by the specification
- add test case

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

- update `README.md` documentation

* Update src/parse-stream.js

* Update test for #171 changes

---------

Co-authored-by: mister-ben <1676039+mister-ben@users.noreply.github.com>
2024-08-16 17:49:27 +02:00
Logan Song
3f49bb4331
EXT-X-CUE-IN ":" is not necessary (#181) 2024-07-06 07:43:15 +01:00
André M
990c6ced71
feat: add support for #EXT-X-I-FRAME-STREAM-INF (#171)
* feat: add support for #EXT-X-I-FRAME-STREAM-INF

Exposes I-frame playlists through the `iFramePlaylists` property, providing a basis for the creation of trick-play functionality.

**parse-stream.js**

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

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

**parser.js**

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

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

- update `master-fmp4.js` to add `iFramePlaylists`
- update `README.md` documentation

* test: update fixtures to take iFramePlaylists property into account

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

---------

Co-authored-by: mister-ben <1676039+mister-ben@users.noreply.github.com>
2024-07-06 07:41:01 +01:00
Adam Waldron
f8c9817a95
chore: add content-steering tag to readme (#177) 2023-08-15 15:39:09 -07:00
hswaminathan
b2d44f2042 7.1.0 2023-08-07 12:26:05 -04:00
Adam Waldron
42472c5979
feat: parse content steering tags and attributes (#176) 2023-08-07 09:19:35 -07:00
Harisha Rajam Swaminathan
73d934ce58
fix: merge dateRange tags with same IDs and no conflicting attributes (#175) 2023-08-07 11:58:23 -04:00
Harisha Rajam Swaminathan
6944bb1b2f
fix: add dateTimeObject and dateTimeString for backward compatibility (#174) 2023-08-07 11:57:27 -04:00
Harisha Rajam Swaminathan
72da994469
chore: update v7.0.0 documentation (#172) 2023-07-24 18:09:50 -04:00
hswaminathan
a673efcae1 7.0.0 2023-07-10 11:03:32 -04:00
Piotr Błażejewicz (Peter Blazejewicz)
4d3e6ce140
docs: correct customType option name (#147)
`Parser.addParser` is a pass through to `ParseStream.addParser`, which uses and
documents `customType`. The same is in public docs (README)

Thanks!
2023-07-07 20:01:09 -04:00
Harisha Rajam Swaminathan
e7c683f5f6
feat: Add PDT to each segment (#168) 2023-07-07 15:10:40 -04:00
Genteure
4adaa2c600
feat: output segment title from EXTINF (#158) 2023-07-06 19:03:35 -07:00
Harisha Rajam Swaminathan
516ab67d17
fix: rename daterange to dateRanges (#166) 2023-06-28 14:14:06 -04:00
Adam Waldron
ad1f11f17d 6.2.0 2023-05-25 11:32:40 -07:00
Adam Waldron
8c47d81a6c
feat: add independent-segments support (#165) 2023-05-22 21:34:13 -07:00
hswaminathan
055d7b760e 6.1.0 2023-05-12 12:09:53 -04:00
Harisha Rajam Swaminathan
cf744dbcf4
Merge pull request #163 from videojs/feat/daterange
Add DATERANGE support
2023-05-01 18:03:49 -04:00
hswaminathan
fc746e7d7f parse end-on-next 2023-05-01 17:23:03 -04:00
hswaminathan
d35b7acaf1 Add tests for DURATION and PLANNED-DURATION attributes 2023-04-24 19:24:18 -04:00
hswaminathan
33b24c4cb5 add support for multiple daterange tags and add tests 2023-04-22 01:07:58 -04:00
hswaminathan
1124e08af5 update docs, add warning and tests 2023-04-20 21:58:49 -04:00
hswaminathan
9e4c3ad0ba add test 2023-04-18 16:01:23 -04:00
hswaminathan
5a76ec0716 remove comment 2023-04-18 15:35:19 -04:00
hswaminathan
8059d61a73 Add support for Daterange 2023-04-18 00:47:17 -04:00
Sarah Rimron-Soutter
f38d60de6c 6.0.0 2022-09-27 17:08:00 +01:00
Essk
8d56f30c4d
fix: non standard tag match (#156)
Prevent non-standard tags from being erroneously matched as standard tags by enforcing the colon tag delimiter

BREAKING CHANGE: Missing colon (:) tag delimiters are no longer supported
Closes #22
2022-09-27 17:05:11 +01:00
Essk
6fe98eeea4
Merge pull request #157 from videojs/remove-version-tests
chore: don't run tests on version
2022-09-27 16:58:38 +01:00
Sarah Rimron-Soutter
b84575feb1 chore: don't run tests on version 2022-09-27 16:39:19 +01:00
Pat O'Neill
701d7f153e 5.0.0 2022-08-19 11:41:27 -04:00
Pat O'Neill
4e0bc63a76
chore: do not run es-check on publish (#153) 2022-08-19 11:40:26 -04:00
kchang-brightcove
fc12241088
chore: remove IE11 support (#152)
BREAKING CHANGE: Internet Explorer is no longer supported.
2022-08-19 10:04:01 -04:00
Grzegorz Blaszczyk
d51e93fa75 feat: parse FRAME-RATE as a number (#150)
BREAKING CHANGE: parser changes an output type for 'FRAME-RATE'
attribute from a string to a number.
2022-08-02 12:18:20 -04:00
Gary Katsevman
c2dfa4dbfe 4.7.1 2022-04-05 12:40:39 -04:00
Gary Katsevman
efce797151
fix: update vhs-utils to 3.0.5 for tizen 2.4 support (#149) 2022-04-04 14:22:44 -04:00
Brandon Casey
4e7c9ebdc1
fix: EXT-X-KEY support playready keyformat (#143) 2021-06-15 13:24:18 -04:00
brandonocasey
ccde054a38 4.7.0 2021-05-19 10:43:53 -04:00
Brandon Casey
ae5fa64118
feat: add key property to init segment/map (#141) 2021-05-19 10:42:32 -04:00
Brandon Casey
9f62c85ef0
fix: ignore fairplay content protection (#140) 2021-05-19 10:41:24 -04:00
brandonocasey
30d3b3d8ea 4.6.0 2021-03-04 13:40:38 -05:00
Brandon Casey
31ed0528b1
refactor: llhls attributes to camel case (#138) 2021-03-04 13:32:52 -05:00
Brandon Casey
2c2dffe68f
feat(llhls): preloadSegment, associate parts/preloadHints with segments, unify byterange handling (#137) 2021-03-04 13:06:25 -05:00
Brandon Casey
98f0421468
feat: add version parsing and remove totalduration (#135) 2021-01-22 13:07:47 -05:00
Brandon Casey
4f4da3d52b
feat: add warn/info triggers and defaults for ll-hls tags (#131) 2021-01-22 13:06:53 -05:00
Brandon Casey
e09c7edd8f
chore: lint fixtures (#134) 2021-01-22 12:45:49 -05:00
Brandon Casey
fb3b629357
chore: remove unused and non-standard tag #ZEN-TOTAL-DURATION (#133) 2021-01-21 15:38:22 -05:00
Brandon Casey
8f69b457cb chore: switch to rollup-plugin-data-files (#130) 2021-01-21 12:00:39 -05:00
Brandon Casey
e86dcae09d
test: move tests around (#129) 2021-01-21 11:57:57 -05:00
Brandon Casey
985ab68718
feat: add support for #EXT-X-PART-INF (#126)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282434
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.7
2021-01-20 10:37:14 -05:00
Brandon Casey
03f4345096
feat: add support for #EXT-X-RENDITION-REPORT (#124)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282435
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.5.4
2021-01-20 10:33:48 -05:00
Brandon Casey
7f82f53d49
feat: add support for #EXT-X-SERVER-CONTROL (#121)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3281374
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.3.8
2021-01-20 10:31:06 -05:00
Brandon Casey
4fd693a11c
feat: add support for #EXT-X-PRELOAD-HINT (#123)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3526694
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.5.3
2021-01-20 10:24:34 -05:00
Brandon Casey
9f5a224cd7
feat: add support for #EXT-X-PART (#127)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282436
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.4.9
2021-01-20 10:12:38 -05:00
Brandon Casey
9cebc864b0
feat: add support for #EXT-X-SKIP (#122)
https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282433
https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.5.2
2021-01-20 10:01:28 -05:00
Brandon Casey
0823ea88d3
test: add llhls manifests for incoming features (#125) 2021-01-20 09:58:02 -05:00
brandonocasey
22dbb60c3f 4.5.2 2021-01-12 15:31:36 -05:00
Brandon Casey
a58149de54
fix: cjs dist should import only cjs (#120) 2021-01-12 15:30:55 -05:00
brandonocasey
1c938cadeb 4.5.1 2021-01-11 16:43:04 -05:00
Brandon Casey
f701c0f8c7
chore: update to vhs-utils@3 (#118) 2021-01-11 16:38:45 -05:00
Gary Katsevman
ec98d7263b 4.5.0 2020-11-03 15:28:28 -05:00
Gary Katsevman
57ac9d2f6a
chore(package): update to vhs-utils@2 (#117) 2020-11-03 15:21:30 -05:00
195 changed files with 11295 additions and 8196 deletions

77
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,77 @@
name: ci
on: [push, pull_request]
jobs:
should-skip:
continue-on-error: true
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should-skip-job: ${{steps.skip-check.outputs.should_skip}}
steps:
- id: skip-check
uses: fkirc/skip-duplicate-actions@v2.1.0
with:
github_token: ${{github.token}}
ci:
needs: should-skip
if: ${{needs.should-skip.outputs.should-skip-job != 'true'}}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
env:
BROWSER_STACK_USERNAME: ${{secrets.BROWSER_STACK_USERNAME}}
BROWSER_STACK_ACCESS_KEY: ${{secrets.BROWSER_STACK_ACCESS_KEY}}
runs-on: ${{matrix.os}}
steps:
- name: checkout code
uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
path: |
~/.npm
**/node_modules
key: ${{runner.os}}-npm-${{hashFiles('**/package-lock.json')}}
restore-keys: |
${{runner.os}}-npm-
${{runner.os}}-
- name: read node version from .nvmrc
run: echo ::set-output name=NVMRC::$(cat .nvmrc)
shell: bash
id: nvm
- name: update apt cache on linux w/o browserstack
run: sudo apt-get update
if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}}
- name: install ffmpeg/pulseaudio for firefox on linux w/o browserstack
run: sudo apt-get install ffmpeg pulseaudio
if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}}
- name: start pulseaudio for firefox on linux w/o browserstack
run: pulseaudio -D
if: ${{startsWith(matrix.os, 'ubuntu') && !env.BROWSER_STACK_USERNAME}}
- name: setup node
uses: actions/setup-node@v1
with:
node-version: '${{steps.nvm.outputs.NVMRC}}'
# turn off the default setup-node problem watchers...
- run: echo "::remove-matcher owner=eslint-compact::"
- run: echo "::remove-matcher owner=eslint-stylish::"
- run: echo "::remove-matcher owner=tsc::"
- name: npm install
run: npm i --prefer-offline --no-audit
- name: run npm test
uses: GabrielBB/xvfb-action@v1
with:
run: npm run test

2
.nvmrc
View file

@ -1 +1 @@
lts/carbon
10

View file

@ -1,16 +0,0 @@
sudo: false
dist: trusty
language: node_js
# node version is specified using the .nvmrc file
before_install:
- npm install -g greenkeeper-lockfile@1
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- greenkeeper-lockfile-update
after_script:
- greenkeeper-lockfile-upload
addons:
firefox: latest
chrome: stable

View file

@ -1,3 +1,165 @@
<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)
### Features
* parse FRAME-RATE as a number ([#150](https://github.com/videojs/m3u8-parser/issues/150)) ([d51e93f](https://github.com/videojs/m3u8-parser/commit/d51e93f))
### Chores
* do not run es-check on publish ([#153](https://github.com/videojs/m3u8-parser/issues/153)) ([4e0bc63](https://github.com/videojs/m3u8-parser/commit/4e0bc63))
* remove IE11 support ([#152](https://github.com/videojs/m3u8-parser/issues/152)) ([fc12241](https://github.com/videojs/m3u8-parser/commit/fc12241))
### BREAKING CHANGES
* Internet Explorer is no longer supported.
* parser changes an output type for 'FRAME-RATE'
attribute from a string to a number.
<a name="4.7.1"></a>
## [4.7.1](https://github.com/videojs/m3u8-parser/compare/v4.7.0...v4.7.1) (2022-04-05)
### Bug Fixes
* EXT-X-KEY support playready keyformat ([#143](https://github.com/videojs/m3u8-parser/issues/143)) ([4e7c9eb](https://github.com/videojs/m3u8-parser/commit/4e7c9eb))
* update vhs-utils to 3.0.5 for tizen 2.4 support ([#149](https://github.com/videojs/m3u8-parser/issues/149)) ([efce797](https://github.com/videojs/m3u8-parser/commit/efce797))
<a name="4.7.0"></a>
# [4.7.0](https://github.com/videojs/m3u8-parser/compare/v4.6.0...v4.7.0) (2021-05-19)
### Features
* add key property to init segment/map ([#141](https://github.com/videojs/m3u8-parser/issues/141)) ([ae5fa64](https://github.com/videojs/m3u8-parser/commit/ae5fa64))
### Bug Fixes
* ignore fairplay content protection ([#140](https://github.com/videojs/m3u8-parser/issues/140)) ([9f62c85](https://github.com/videojs/m3u8-parser/commit/9f62c85))
<a name="4.6.0"></a>
# [4.6.0](https://github.com/videojs/m3u8-parser/compare/v4.5.2...v4.6.0) (2021-03-04)
### Features
* add support for #EXT-X-PART ([#127](https://github.com/videojs/m3u8-parser/issues/127)) ([9f5a224](https://github.com/videojs/m3u8-parser/commit/9f5a224)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282436](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3282436) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add support for #EXT-X-PART-INF ([#126](https://github.com/videojs/m3u8-parser/issues/126)) ([985ab68](https://github.com/videojs/m3u8-parser/commit/985ab68)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282434](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3282434) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add support for #EXT-X-PRELOAD-HINT ([#123](https://github.com/videojs/m3u8-parser/issues/123)) ([4fd693a](https://github.com/videojs/m3u8-parser/commit/4fd693a)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3526694](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3526694) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add support for #EXT-X-RENDITION-REPORT ([#124](https://github.com/videojs/m3u8-parser/issues/124)) ([03f4345](https://github.com/videojs/m3u8-parser/commit/03f4345)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282435](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3282435) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add support for #EXT-X-SERVER-CONTROL ([#121](https://github.com/videojs/m3u8-parser/issues/121)) ([7f82f53](https://github.com/videojs/m3u8-parser/commit/7f82f53)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3281374](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3281374) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add support for #EXT-X-SKIP ([#122](https://github.com/videojs/m3u8-parser/issues/122)) ([9cebc86](https://github.com/videojs/m3u8-parser/commit/9cebc86)), closes [/developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282433](https://github.com//developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls/issues/3282433) [/tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4](https://github.com//tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08/issues/section-4)
* add version parsing and remove totalduration ([#135](https://github.com/videojs/m3u8-parser/issues/135)) ([98f0421](https://github.com/videojs/m3u8-parser/commit/98f0421))
* add warn/info triggers and defaults for ll-hls tags ([#131](https://github.com/videojs/m3u8-parser/issues/131)) ([4f4da3d](https://github.com/videojs/m3u8-parser/commit/4f4da3d))
* **llhls:** preloadSegment, associate parts/preloadHints with segments, unify byterange handling ([#137](https://github.com/videojs/m3u8-parser/issues/137)) ([2c2dffe](https://github.com/videojs/m3u8-parser/commit/2c2dffe))
### Chores
* lint fixtures ([#134](https://github.com/videojs/m3u8-parser/issues/134)) ([e09c7ed](https://github.com/videojs/m3u8-parser/commit/e09c7ed))
* remove unused and non-standard tag #ZEN-TOTAL-DURATION ([#133](https://github.com/videojs/m3u8-parser/issues/133)) ([fb3b629](https://github.com/videojs/m3u8-parser/commit/fb3b629))
* switch to rollup-plugin-data-files ([#130](https://github.com/videojs/m3u8-parser/issues/130)) ([8f69b45](https://github.com/videojs/m3u8-parser/commit/8f69b45))
### Code Refactoring
* llhls attributes to camel case ([#138](https://github.com/videojs/m3u8-parser/issues/138)) ([31ed052](https://github.com/videojs/m3u8-parser/commit/31ed052))
### Tests
* add llhls manifests for incoming features ([#125](https://github.com/videojs/m3u8-parser/issues/125)) ([0823ea8](https://github.com/videojs/m3u8-parser/commit/0823ea8))
* move tests around ([#129](https://github.com/videojs/m3u8-parser/issues/129)) ([e86dcae](https://github.com/videojs/m3u8-parser/commit/e86dcae))
<a name="4.5.2"></a>
## [4.5.2](https://github.com/videojs/m3u8-parser/compare/v4.5.1...v4.5.2) (2021-01-12)
### Bug Fixes
* cjs dist should import only cjs ([#120](https://github.com/videojs/m3u8-parser/issues/120)) ([a58149d](https://github.com/videojs/m3u8-parser/commit/a58149d))
<a name="4.5.1"></a>
## [4.5.1](https://github.com/videojs/m3u8-parser/compare/v4.5.0...v4.5.1) (2021-01-11)
### Chores
* update to vhs-utils[@3](https://github.com/3) ([#118](https://github.com/videojs/m3u8-parser/issues/118)) ([f701c0f](https://github.com/videojs/m3u8-parser/commit/f701c0f))
<a name="4.5.0"></a>
# [4.5.0](https://github.com/videojs/m3u8-parser/compare/v4.4.3...v4.5.0) (2020-11-03)
### Chores
* **package:** update to vhs-utils[@2](https://github.com/2) ([#117](https://github.com/videojs/m3u8-parser/issues/117)) ([57ac9d2](https://github.com/videojs/m3u8-parser/commit/57ac9d2))
<a name="4.4.3"></a>
## [4.4.3](https://github.com/videojs/m3u8-parser/compare/v4.4.2...v4.4.3) (2020-08-12)

View file

@ -13,12 +13,13 @@ 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)
- [Master Playlist Tags](#master-playlist-tags)
- [Main Playlist Tags](#main-playlist-tags)
- [Experimental Tags](#experimental-tags)
- [EXT-X-CUE-OUT](#ext-x-cue-out)
- [EXT-X-CUE-OUT-CONT](#ext-x-cue-out-cont)
@ -57,6 +58,7 @@ 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'
@ -69,6 +71,21 @@ 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&param_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
@ -79,6 +96,7 @@ Manifest {
allowCache: boolean,
endList: boolean,
mediaSequence: number,
dateRanges: [],
discontinuitySequence: number,
playlistType: string,
custom: {},
@ -113,11 +131,13 @@ Manifest {
discontinuityStarts: [number],
segments: [
{
title: string,
byterange: {
length: number,
offset: number
},
duration: number,
programDateTime: number,
attributes: {},
discontinuity: number,
uri: string,
@ -158,6 +178,8 @@ Manifest {
* [EXT-X-KEY](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.2.4)
* [EXT-X-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
@ -167,11 +189,16 @@ Manifest {
* [EXT-X-ENDLIST](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.3.4)
* [EXT-X-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)
### Master Playlist Tags
### Main Playlist Tags
* [EXT-X-MEDIA](http://tools.ietf.org/html/draft-pantos-http-live-streaming#section-4.3.4.1)
* [EXT-X-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
@ -236,12 +263,8 @@ 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

8098
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "m3u8-parser",
"version": "4.4.3",
"version": "7.2.0",
"description": "m3u8 parser",
"main": "dist/m3u8-parser.cjs.js",
"module": "dist/m3u8-parser.es.js",
@ -25,19 +25,17 @@
"scripts": {
"build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'",
"build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'",
"build-test-data": "node scripts/m3u8.js",
"build": "npm-run-all -s clean build-test-data -p build:*",
"build": "npm-run-all -s clean -p build:*",
"build:js": "rollup -c scripts/rollup.config.js",
"clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist",
"lint": "vjsstandard",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check",
"start": "npm-run-all -p server watch",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"test": "npm-run-all lint build-test test:*",
"test:browser": "karma start scripts/karma.conf.js",
"test:node": "qunit test/dist/bundle.js",
"posttest": "shx cat test/dist/coverage/text.txt",
"preversion": "npm test",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"watch": "npm-run-all -p watch:*",
@ -66,40 +64,32 @@
"test/"
],
"dependencies": {
"@babel/runtime": "^7.5.5",
"@videojs/vhs-utils": "^1.1.0",
"global": "^4.3.2"
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^4.1.1",
"global": "^4.4.0"
},
"devDependencies": {
"rollup": "^1.19.4",
"@videojs/generator-helpers": "~1.2.0",
"karma": "^4.0.0",
"sinon": "^7.2.2",
"videojs-generate-karma-config": "~5.3.1",
"videojs-generator-verify": "~2.0.0",
"videojs-generate-rollup-config": "~5.0.1",
"videojs-standard": "^8.0.3"
"@rollup/plugin-replace": "^2.3.4",
"@videojs/generator-helpers": "~2.0.1",
"karma": "^5.2.3",
"rollup": "^2.38.0",
"rollup-plugin-data-files": "^0.1.0",
"sinon": "^9.2.3",
"videojs-generate-karma-config": "^8.0.1",
"videojs-generate-rollup-config": "~7.0.0",
"videojs-generator-verify": "~3.0.1",
"videojs-standard": "^8.0.4"
},
"generator-videojs-plugin": {
"version": "7.7.3"
},
"browserslist": [
"defaults",
"ie 11"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"doctoc --notitle",
"git add"
]
"*.js": "vjsstandard --fix",
"README.md": "doctoc --notitle"
}
}

View file

@ -1,83 +0,0 @@
'use strict';
/* eslint no-console: 0 */
const fs = require('fs');
const path = require('path');
const basePath = path.resolve(__dirname, '..');
const testDataDir = path.join(basePath, 'test');
const manifestDir = path.join(basePath, 'test', 'fixtures', 'm3u8');
const manifestFilepath = path.join(testDataDir, 'dist', 'test-manifests.js');
const expectedFilepath = path.join(testDataDir, 'dist', 'test-expected.js');
const build = function() {
let manifests = 'export default {\n';
let expected = 'export default {\n';
const files = fs.readdirSync(manifestDir);
while (files.length > 0) {
const file = path.resolve(manifestDir, files.shift());
const extname = path.extname(file);
if (extname === '.m3u8') {
// translate this manifest
manifests += ' \'' + path.basename(file, '.m3u8') + '\': ';
manifests += fs.readFileSync(file, 'utf8')
.split(/\r\n|\n/)
// quote and concatenate
.map(function(line) {
return ' \'' + line + '\\n\' +\n';
}).join('')
// strip leading spaces and the trailing '+'
.slice(4, -3);
manifests += ',\n';
} else if (extname === '.json') {
// append the expected parse
expected += ' "' + path.basename(file, '.json') + '": ';
expected += fs.readFileSync(file, 'utf8');
expected += ',\n';
} else {
console.log('Unknown file ' + file + ' found in manifest dir ' + manifestDir);
}
}
// clean up and close the objects
manifests = manifests.slice(0, -2);
manifests += '\n};\n';
expected = expected.slice(0, -2);
expected += '\n};\n';
fs.writeFileSync(manifestFilepath, manifests);
fs.writeFileSync(expectedFilepath, expected);
console.log('Wrote test data file ' + manifestFilepath);
console.log('Wrote test data file ' + expectedFilepath);
};
const watch = function() {
build();
fs.watch(manifestDir, function(event, filename) {
console.log('files in manifest dir were changed rebuilding manifest data');
build();
});
};
const clean = function() {
try {
fs.unlinkSync(manifestFilepath);
} catch (e) {
console.log(e);
}
try {
fs.unlinkSync(expectedFilepath);
} catch (e) {
console.log(e);
}
};
module.exports = {
build,
watch,
clean
};

View file

@ -1,22 +0,0 @@
'use strict';
const m3u8 = require('./export-m3u8s.js');
const args = require('minimist')(process.argv.slice(2), {
boolean: ['watch', 'clean', 'build'],
default: {
build: true
},
alias: {
b: 'build',
c: 'clean',
w: 'watch'
}
});
if (args.w) {
m3u8.watch();
} else if (args.c) {
m3u8.clean();
} else if (args.b) {
m3u8.build();
}

View file

@ -1,4 +1,6 @@
const generate = require('videojs-generate-rollup-config');
const replace = require('@rollup/plugin-replace');
const dataFiles = require('rollup-plugin-data-files');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
@ -7,6 +9,29 @@ const options = {
externals(defaults) {
defaults.module.push('@videojs/vhs-utils');
return defaults;
},
primedPlugins(defaults) {
// when using "require" rather than import
// require cjs module
defaults.replace = replace({
// single quote replace
"require('@videojs/vhs-utils/es": "require('@videojs/vhs-utils/cjs",
// double quote replace
'require("@videojs/vhs-utils/es': 'require("@videojs/vhs-utils/cjs'
});
defaults.dataFiles = dataFiles({
expecteds: {include: 'test/fixtures/integration/*.js', transform: 'js', extensions: false},
manifests: {include: 'test/fixtures/integration/*.m3u8', transform: 'string', extensions: false}
});
return defaults;
},
plugins(defaults) {
defaults.module.unshift('replace');
defaults.test.unshift('dataFiles');
return defaults;
}
};

View file

@ -1,7 +1,7 @@
/**
* @file m3u8/line-stream.js
*/
import Stream from '@videojs/vhs-utils/dist/stream.js';
import Stream from '@videojs/vhs-utils/es/stream.js';
/**
* A stream that buffers string input and generates a `data` event for each

View file

@ -1,7 +1,26 @@
/**
* @file m3u8/parse-stream.js
*/
import Stream from '@videojs/vhs-utils/dist/stream.js';
import Stream from '@videojs/vhs-utils/es/stream.js';
const TAB = String.fromCharCode(0x09);
const parseByterange = function(byterangeString) {
// optionally match and capture 0+ digits before `@`
// optionally match and capture 0+ digits after `@`
const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
const result = {};
if (match[1]) {
result.length = parseInt(match[1], 10);
}
if (match[2]) {
result.offset = parseInt(match[2], 10);
}
return result;
};
/**
* "forgiving" attribute list psuedo-grammar:
@ -24,9 +43,14 @@ 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;
@ -47,6 +71,29 @@ const parseAttributes = function(attributes) {
return result;
};
/**
* Converts a string into a resolution object
*
* @param {string} resolution a string such as 3840x2160
*
* @return {Object} An object representing the resolution
*
*/
const parseResolution = (resolution) => {
const split = resolution.split('x');
const result = {};
if (split[0]) {
result.width = parseInt(split[0], 10);
}
if (split[1]) {
result.height = parseInt(split[1], 10);
}
return result;
};
/**
* A line-level M3U8 parser event stream. It expects to receive input one
* line at a time and performs a context-free parse of its contents. A stream
@ -145,7 +192,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',
@ -160,7 +207,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',
@ -172,19 +219,7 @@ export default class ParseStream extends Stream {
this.trigger('data', event);
return;
}
match = (/^#ZEN-TOTAL-DURATION:?([0-9.]*)?/).exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'totalduration'
};
if (match[1]) {
event.duration = parseInt(match[1], 10);
}
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',
@ -196,7 +231,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',
@ -208,7 +243,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',
@ -220,7 +255,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',
@ -232,22 +267,16 @@ export default class ParseStream extends Stream {
this.trigger('data', event);
return;
}
match = (/^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/).exec(newLine);
match = (/^#EXT-X-BYTERANGE:(.*)?$/).exec(newLine);
if (match) {
event = {
event = Object.assign(parseByterange(match[1]), {
type: 'tag',
tagType: 'byterange'
};
if (match[1]) {
event.length = parseInt(match[1], 10);
}
if (match[2]) {
event.offset = parseInt(match[2], 10);
}
});
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',
@ -259,7 +288,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',
@ -273,22 +302,14 @@ export default class ParseStream extends Stream {
event.uri = attributes.URI;
}
if (attributes.BYTERANGE) {
const [length, offset] = attributes.BYTERANGE.split('@');
event.byterange = {};
if (length) {
event.byterange.length = parseInt(length, 10);
}
if (offset) {
event.byterange.offset = parseInt(offset, 10);
}
event.byterange = parseByterange(attributes.BYTERANGE);
}
}
this.trigger('data', event);
return;
}
match = (/^#EXT-X-STREAM-INF:?(.*)$/).exec(newLine);
match = (/^#EXT-X-STREAM-INF:(.*)$/).exec(newLine);
if (match) {
event = {
type: 'tag',
@ -298,20 +319,14 @@ export default class ParseStream extends Stream {
event.attributes = parseAttributes(match[1]);
if (event.attributes.RESOLUTION) {
const split = event.attributes.RESOLUTION.split('x');
const resolution = {};
if (split[0]) {
resolution.width = parseInt(split[0], 10);
}
if (split[1]) {
resolution.height = parseInt(split[1], 10);
}
event.attributes.RESOLUTION = resolution;
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
}
if (event.attributes.BANDWIDTH) {
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
}
if (event.attributes['FRAME-RATE']) {
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
}
if (event.attributes['PROGRAM-ID']) {
event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
}
@ -319,7 +334,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',
@ -347,7 +362,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',
@ -360,7 +375,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',
@ -385,7 +400,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',
@ -400,7 +415,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',
@ -414,7 +429,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',
@ -442,6 +457,252 @@ export default class ParseStream extends Stream {
this.trigger('data', event);
return;
}
match = (/^#EXT-X-SKIP:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'skip'
};
event.attributes = parseAttributes(match[1]);
if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
}
if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
event.attributes['RECENTLY-REMOVED-DATERANGES'] =
event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
}
this.trigger('data', event);
return;
}
match = (/^#EXT-X-PART:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'part'
};
event.attributes = parseAttributes(match[1]);
['DURATION'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
['INDEPENDENT', 'GAP'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = (/YES/).test(event.attributes[key]);
}
});
if (event.attributes.hasOwnProperty('BYTERANGE')) {
event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
}
this.trigger('data', event);
return;
}
match = (/^#EXT-X-SERVER-CONTROL:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'server-control'
};
event.attributes = parseAttributes(match[1]);
['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = (/YES/).test(event.attributes[key]);
}
});
this.trigger('data', event);
return;
}
match = (/^#EXT-X-PART-INF:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'part-inf'
};
event.attributes = parseAttributes(match[1]);
['PART-TARGET'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
this.trigger('data', event);
return;
}
match = (/^#EXT-X-PRELOAD-HINT:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'preload-hint'
};
event.attributes = parseAttributes(match[1]);
['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseInt(event.attributes[key], 10);
const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
event.attributes.byterange = event.attributes.byterange || {};
event.attributes.byterange[subkey] = event.attributes[key];
// only keep the parsed byterange object.
delete event.attributes[key];
}
});
this.trigger('data', event);
return;
}
match = (/^#EXT-X-RENDITION-REPORT:(.*)$/).exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'rendition-report'
};
event.attributes = parseAttributes(match[1]);
['LAST-MSN', 'LAST-PART'].forEach(function(key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseInt(event.attributes[key], 10);
}
});
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', {
@ -459,9 +720,8 @@ export default class ParseStream extends Stream {
* @param {string} options.customType the custom type to register to the output
* @param {Function} [options.dataParser] function to parse the line into an object
* @param {boolean} [options.segment] should tag data be attached to the segment object
* @param {boolean} [options.multiple] does the tag appear multiple times
*/
addParser({expression, customType, dataParser, segment, multiple}) {
addParser({expression, customType, dataParser, segment}) {
if (typeof dataParser !== 'function') {
dataParser = (line) => line;
}
@ -473,8 +733,7 @@ export default class ParseStream extends Stream {
type: 'custom',
data: dataParser(line),
customType,
segment,
multiple
segment
});
return true;
}

View file

@ -1,11 +1,74 @@
/**
* @file m3u8/parser.js
*/
import Stream from '@videojs/vhs-utils/dist/stream.js';
import decodeB64ToUint8Array from '@videojs/vhs-utils/dist/decode-b64-to-uint8-array.js';
import Stream from '@videojs/vhs-utils/es/stream.js';
import decodeB64ToUint8Array from '@videojs/vhs-utils/es/decode-b64-to-uint8-array.js';
import LineStream from './line-stream';
import ParseStream from './parse-stream';
const camelCase = (str) => str
.toLowerCase()
.replace(/-(\w)/g, (a) => a[1].toUpperCase());
const camelCaseKeys = function(attributes) {
const result = {};
Object.keys(attributes).forEach(function(key) {
result[camelCase(key)] = attributes[key];
});
return result;
};
// set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
// we need this helper because defaults are based upon targetDuration and
// partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
// target durations are set.
const setHoldBack = function(manifest) {
const {serverControl, targetDuration, partTargetDuration} = manifest;
if (!serverControl) {
return;
}
const tag = '#EXT-X-SERVER-CONTROL';
const hb = 'holdBack';
const phb = 'partHoldBack';
const minTargetDuration = targetDuration && targetDuration * 3;
const minPartDuration = partTargetDuration && partTargetDuration * 2;
if (targetDuration && !serverControl.hasOwnProperty(hb)) {
serverControl[hb] = minTargetDuration;
this.trigger('info', {
message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
});
}
if (minTargetDuration && serverControl[hb] < minTargetDuration) {
this.trigger('warn', {
message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
});
serverControl[hb] = minTargetDuration;
}
// default no part hold back to part target duration * 3
if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
serverControl[phb] = partTargetDuration * 3;
this.trigger('info', {
message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
});
}
// if part hold back is too small default it to part target duration * 2
if (partTargetDuration && serverControl[phb] < (minPartDuration)) {
this.trigger('warn', {
message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
});
serverControl[phb] = minPartDuration;
}
};
/**
* A parser for M3U8 files. The current interpretation of the input is
* exposed as a property `manifest` on parser objects. It's just two lines to
@ -25,14 +88,20 @@ import ParseStream from './parse-stream';
* 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() {
constructor(opts = {}) {
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;
@ -43,6 +112,7 @@ export default class Parser extends Stream {
let currentMap;
// if specified, the active decryption key
let key;
let hasParts = false;
const noop = function() {};
const defaultMediaGroups = {
'AUDIO': {},
@ -60,22 +130,69 @@ 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
// to provide the offset, in which case it defaults to the next byte after the
// previous segment
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.
// and we actually have parts/preloadHints
if (currentUri.uri || (!currentUri.parts && !currentUri.preloadHints)) {
return;
}
if (!currentUri.map && currentMap) {
currentUri.map = currentMap;
}
if (!currentUri.key && key) {
currentUri.key = key;
}
if (!currentUri.timeline && typeof currentTimeline === 'number') {
currentUri.timeline = currentTimeline;
}
this.manifest.preloadSegment = currentUri;
});
// update the manifest with the m3u8 entry from the parse stream
this.parseStream.on('data', function(entry) {
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
(({
version() {
if (entry.version) {
this.manifest.version = entry.version;
}
},
'allow-cache'() {
this.manifest.allowCache = entry.allowed;
if (!('allowed' in entry)) {
@ -128,6 +245,11 @@ 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;
}
@ -160,6 +282,28 @@ export default class Parser extends Stream {
return;
}
if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
this.manifest.contentProtection = this.manifest.contentProtection || {};
// TODO: add full support for this.
this.manifest.contentProtection['com.apple.fps.1_0'] = {
attributes: entry.attributes
};
return;
}
if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
this.manifest.contentProtection = this.manifest.contentProtection || {};
// TODO: add full support for this.
this.manifest.contentProtection['com.microsoft.playready'] = {
uri: entry.attributes.URI
};
return;
}
// check if the content is encrypted for Widevine
// Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
if (entry.attributes.KEYFORMAT === widevineUuid) {
@ -194,16 +338,15 @@ export default class Parser extends Stream {
// if Widevine key attributes are valid, store them as `contentProtection`
// on the manifest to emulate Widevine tag structure in a DASH mpd
this.manifest.contentProtection = {
'com.widevine.alpha': {
attributes: {
schemeIdUri: entry.attributes.KEYFORMAT,
// remove '0x' from the key id string
keyId: entry.attributes.KEYID.substring(2)
},
// decode the base64-encoded PSSH box
pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
}
this.manifest.contentProtection = this.manifest.contentProtection || {};
this.manifest.contentProtection['com.widevine.alpha'] = {
attributes: {
schemeIdUri: entry.attributes.KEYFORMAT,
// remove '0x' from the key id string
keyId: entry.attributes.KEYID.substring(2)
},
// decode the base64-encoded PSSH box
pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
};
return;
}
@ -260,6 +403,10 @@ export default class Parser extends Stream {
if (entry.byterange) {
currentMap.byterange = entry.byterange;
}
if (key) {
currentMap.key = key;
}
},
'stream-inf'() {
this.manifest.playlists = uris;
@ -341,9 +488,24 @@ 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) {
@ -353,15 +515,8 @@ export default class Parser extends Stream {
return;
}
this.manifest.targetDuration = entry.duration;
},
totalduration() {
if (!isFinite(entry.duration) || entry.duration < 0) {
this.trigger('warn', {
message: 'ignoring invalid total duration: ' + entry.duration
});
return;
}
this.manifest.totalDuration = entry.duration;
setHoldBack.call(this, this.manifest);
},
start() {
if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
@ -383,7 +538,333 @@ export default class Parser extends Stream {
},
'cue-in'() {
currentUri.cueIn = entry.data;
},
'skip'() {
this.manifest.skip = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_(
'#EXT-X-SKIP',
entry.attributes,
['SKIPPED-SEGMENTS']
);
},
'part'() {
hasParts = true;
// parts are always specifed before a segment
const segmentIndex = this.manifest.segments.length;
const part = camelCaseKeys(entry.attributes);
currentUri.parts = currentUri.parts || [];
currentUri.parts.push(part);
if (part.byterange) {
if (!part.byterange.hasOwnProperty('offset')) {
part.byterange.offset = lastPartByterangeEnd;
}
lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
}
const partIndex = currentUri.parts.length - 1;
this.warnOnMissingAttributes_(
`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`,
entry.attributes,
['URI', 'DURATION']
);
if (this.manifest.renditionReports) {
this.manifest.renditionReports.forEach((r, i) => {
if (!r.hasOwnProperty('lastPart')) {
this.trigger('warn', {
message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
});
}
});
}
},
'server-control'() {
const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
if (!attrs.hasOwnProperty('canBlockReload')) {
attrs.canBlockReload = false;
this.trigger('info', {
message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
});
}
setHoldBack.call(this, this.manifest);
if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
this.trigger('warn', {
message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
});
}
},
'preload-hint'() {
// parts are always specifed before a segment
const segmentIndex = this.manifest.segments.length;
const hint = camelCaseKeys(entry.attributes);
const isPart = hint.type && hint.type === 'PART';
currentUri.preloadHints = currentUri.preloadHints || [];
currentUri.preloadHints.push(hint);
if (hint.byterange) {
if (!hint.byterange.hasOwnProperty('offset')) {
// use last part byterange end or zero if not a part.
hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
if (isPart) {
lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
}
}
}
const index = currentUri.preloadHints.length - 1;
this.warnOnMissingAttributes_(
`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`,
entry.attributes,
['TYPE', 'URI']
);
if (!hint.type) {
return;
}
// search through all preload hints except for the current one for
// a duplicate type.
for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
const otherHint = currentUri.preloadHints[i];
if (!otherHint.type) {
continue;
}
if (otherHint.type === hint.type) {
this.trigger('warn', {
message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
});
}
}
},
'rendition-report'() {
const report = camelCaseKeys(entry.attributes);
this.manifest.renditionReports = this.manifest.renditionReports || [];
this.manifest.renditionReports.push(report);
const index = this.manifest.renditionReports.length - 1;
const required = ['LAST-MSN', 'URI'];
if (hasParts) {
required.push('LAST-PART');
}
this.warnOnMissingAttributes_(
`#EXT-X-RENDITION-REPORT #${index}`,
entry.attributes,
required
);
},
'part-inf'() {
this.manifest.partInf = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_(
'#EXT-X-PART-INF',
entry.attributes,
['PART-TARGET']
);
if (this.manifest.partInf.partTarget) {
this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
}
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() {
@ -407,6 +888,15 @@ export default class Parser extends Stream {
currentUri.map = currentMap;
}
// 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 = {};
},
@ -418,11 +908,7 @@ export default class Parser extends Stream {
if (entry.segment) {
currentUri.custom = currentUri.custom || {};
currentUri.custom[entry.customType] = entry.data;
// if this is manifest-level data attach to the top level manifest object
} else if (entry.multiple) {
this.manifest.custom = this.manifest.custom || {};
this.manifest.custom[entry.customType] = this.manifest.custom[entry.customType] || [];
this.manifest.custom[entry.customType].push(entry.data);
// if this is manifest-level data attach to the top level manifest object
} else {
this.manifest.custom = this.manifest.custom || {};
this.manifest.custom[entry.customType] = entry.data;
@ -432,6 +918,28 @@ 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 = [];
required.forEach(function(key) {
if (!attributes.hasOwnProperty(key)) {
missing.push(key);
}
});
if (missing.length) {
this.trigger('warn', {message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`});
}
}
/**
* Parse the input string and update the manifest object.
*
@ -449,13 +957,21 @@ 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');
}
/**
* Add an additional parser for non-standard tags
*
* @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.type the type to register to the output
* @param {string} options.customType the custom type to register to the output
* @param {Function} [options.dataParser] function to parse the line into an object
* @param {boolean} [options.segment] should tag data be attached to the segment object
*/

View file

@ -0,0 +1,33 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
duration: 10,
timeline: 0,
uri: 'http://example.com/00001.ts'
},
{
duration: 10,
timeline: 0,
uri: 'https://example.com/00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '//example.com/00003.ts'
},
{
duration: 10,
timeline: 0,
uri: 'http://example.com/00004.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -9,5 +9,4 @@ https://example.com/00002.ts
//example.com/00003.ts
#EXTINF:10,
http://example.com/00004.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

167
test/fixtures/integration/allowCache.js vendored Normal file
View file

@ -0,0 +1,167 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
mediaSequence: 0,
dateRanges: [],
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 587500,
offset: 522828
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 713084,
offset: 1110328
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 476580,
offset: 1823412
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 535612,
offset: 2299992
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 207176,
offset: 2835604
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 455900,
offset: 3042780
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 657248,
offset: 3498680
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 571708,
offset: 4155928
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 485040,
offset: 4727636
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 709136,
offset: 5212676
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 730004,
offset: 5921812
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 456276,
offset: 6651816
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 1.4167,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,23 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
mediaSequence: 0,
dateRanges: [],
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,58 @@
module.exports = {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaGroups: {
// TYPE
'AUDIO': {
// GROUP-ID
audio: {
// NAME
English: {
language: 'eng',
autoselect: true,
default: true,
uri: 'eng/prog_index.m3u8'
},
// NAME
Français: {
language: 'fre',
autoselect: true,
default: false,
uri: 'fre/prog_index.m3u8'
},
// NAME
Espanol: {
language: 'sp',
autoselect: true,
default: false,
uri: 'sp/prog_index.m3u8'
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
playlists: [{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 195023,
'CODECS': 'avc1.42e00a,mp4a.40.2',
'AUDIO': 'audio'
},
timeline: 0,
uri: 'lo/prog_index.m3u8'
}, {
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 591680,
'CODECS': 'avc1.42e01e,mp4a.40.2',
'AUDIO': 'audio'
},
timeline: 0,
uri: 'hi/prog_index.m3u8'
}],
segments: []
};

View file

@ -1,19 +1,21 @@
{
module.exports = {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaGroups: {
AUDIO: {
'AUDIO': {
aac: {
English: {
autoselect: true,
default: true,
language: "eng",
uri: "eng/prog_index.m3u8"
language: 'eng',
uri: 'eng/prog_index.m3u8'
}
}
},
VIDEO: {
"500kbs": {
'VIDEO': {
'500kbs': {
Angle1: {
autoselect: true,
default: true
@ -21,28 +23,28 @@
Angle2: {
autoselect: true,
default: false,
uri: "Angle2/500kbs/prog_index.m3u8"
uri: 'Angle2/500kbs/prog_index.m3u8'
},
Angle3: {
autoselect: true,
default: false,
uri: "Angle3/500kbs/prog_index.m3u8"
uri: 'Angle3/500kbs/prog_index.m3u8'
}
}
},
"CLOSED-CAPTIONS": {},
SUBTITLES: {}
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
playlists: [{
attributes: {
"PROGRAM-ID": 1,
BANDWIDTH: 754857,
CODECS: "mp4a.40.2,avc1.4d401e",
AUDIO: "aac",
VIDEO: "500kbs"
'PROGRAM-ID': 1,
'BANDWIDTH': 754857,
'CODECS': 'mp4a.40.2,avc1.4d401e',
'AUDIO': 'aac',
'VIDEO': '500kbs'
},
timeline: 0,
uri: "Angle1/500kbs/prog_index.m3u8"
uri: 'Angle1/500kbs/prog_index.m3u8'
}],
segments: []
}
};

59
test/fixtures/integration/brightcove.js vendored Normal file
View file

@ -0,0 +1,59 @@
module.exports = {
allowCache: true,
iFramePlaylists: [],
dateRanges: [],
playlists: [
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 240000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 40000
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 440000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 1928000,
'RESOLUTION': {
width: 960,
height: 540
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001'
}
],
discontinuityStarts: [],
mediaGroups: {
'VIDEO': {},
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
segments: []
};

163
test/fixtures/integration/byteRange.js vendored Normal file
View file

@ -0,0 +1,163 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 587500,
offset: 522828
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 713084,
offset: 1110328
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video2.ts'
},
{
byterange: {
length: 476580,
offset: 1823412
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 535612,
offset: 2299992
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 207176,
offset: 2835604
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 455900,
offset: 3042780
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 657248,
offset: 3498680
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 571708,
offset: 4155928
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 485040,
offset: 4727636
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 709136,
offset: 5212676
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 730004,
offset: 5921812
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 456276,
offset: 6651816
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 1.4167,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 3
};

31
test/fixtures/integration/dateTime.js vendored Normal file
View file

@ -0,0 +1,31 @@
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'
},
{
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'
}
],
targetDuration: 10,
endList: true,
dateTimeString: '2016-06-22T09:20:16.166-04:00',
dateTimeObject: new Date('2016-06-22T09:20:16.166-04:00'),
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,166 @@
module.exports = {
allowCache: true,
discontinuitySequence: 0,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 7794,
segments: [
{
duration: 2.833,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/init52.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence52-A.m4s'
},
{
duration: 15,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/init52.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence52-B.m4s'
},
{
duration: 13.333,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/init52.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence52-C.m4s'
},
{
duration: 15,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=53'
},
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=53'
},
uri: 'http://media.example.com/init53-A.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence53-A.m4s'
},
{
duration: 14,
key: {
iv: new Uint32Array([0, 0, 331, 3063767524]),
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=53'
},
map: {
key: {
iv: new Uint32Array([0, 0, 331, 3063767524]),
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=53'
},
uri: 'http://media.example.com/init53-B.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence53-B.m4s'
},
{
duration: 12,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54'
},
map: {
uri: 'http://media.example.com/init54-A.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-A.m4s'
},
{
duration: 13,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54'
},
map: {
uri: 'http://media.example.com/init54-A.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-B.m4s'
},
{
duration: 10,
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54'
},
uri: 'http://media.example.com/init54-B.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-A.m4s'
},
{
duration: 11,
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54'
},
uri: 'http://media.example.com/init54-B.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-B.m4s'
},
{
duration: 4,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54-b'
},
map: {
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54-a'
},
uri: 'http://media.example.com/init54-D.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-A.m4s'
},
{
duration: 12,
map: {
uri: 'http://media.example.com/init54-E.mp4'
},
timeline: 0,
uri: 'http://media.example.com/fileSequence54-A.m4s'
}
],
targetDuration: 15,
version: 7
};

View file

@ -0,0 +1,57 @@
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-MEDIA-SEQUENCE:7794
#EXT-X-TARGETDURATION:15
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
#EXT-X-MAP:URI="http://media.example.com/init52.mp4"
#EXTINF:2.833,
http://media.example.com/fileSequence52-A.m4s
#EXTINF:15.0,
http://media.example.com/fileSequence52-B.m4s
#EXTINF:13.333,
http://media.example.com/fileSequence52-C.m4s
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=53"
#EXT-X-MAP:URI="http://media.example.com/init53-A.mp4"
#EXTINF:15.0,
http://media.example.com/fileSequence53-A.m4s
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=53",IV=0x00000000000000000000014BB69D61E4
#EXT-X-MAP:URI="http://media.example.com/init53-B.mp4"
#EXTINF:14.0,
http://media.example.com/fileSequence53-B.m4s
#EXT-X-KEY:METHOD=NONE
#EXT-X-MAP:URI="http://media.example.com/init54-A.mp4"
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=54"
#EXTINF:12.0,
http://media.example.com/fileSequence54-A.m4s
#EXTINF:13.0,
http://media.example.com/fileSequence54-B.m4s
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=54"
#EXT-X-MAP:URI="http://media.example.com/init54-B.mp4"
#EXT-X-KEY:METHOD=NONE
#EXTINF:10.0,
http://media.example.com/fileSequence54-A.m4s
#EXTINF:11.0,
http://media.example.com/fileSequence54-B.m4s
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=54-a"
#EXT-X-MAP:URI="http://media.example.com/init54-D.mp4"
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=54-b"
#EXTINF:4.0,
http://media.example.com/fileSequence54-A.m4s
#EXT-X-KEY:METHOD=NONE
#EXT-X-MAP:URI="http://media.example.com/init54-E.mp4"
#EXTINF:12.0,
http://media.example.com/fileSequence54-A.m4s

View file

@ -0,0 +1,23 @@
module.exports = {
allowCache: false,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,38 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
discontinuitySequence: 3,
segments: [
{
duration: 10,
timeline: 3,
uri: '001.ts',
title: '0'
},
{
duration: 19,
timeline: 3,
uri: '002.ts',
title: '0'
},
{
discontinuity: true,
duration: 10,
timeline: 4,
uri: '003.ts',
title: '0'
},
{
duration: 11,
timeline: 4,
uri: '004.ts',
title: '0'
}
],
targetDuration: 19,
endList: true,
discontinuityStarts: [2],
version: 3
};

View file

@ -0,0 +1,70 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
discontinuitySequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '001.ts',
title: '0'
},
{
duration: 19,
timeline: 0,
uri: '002.ts',
title: '0'
},
{
discontinuity: true,
duration: 10,
timeline: 1,
uri: '003.ts',
title: '0'
},
{
duration: 11,
timeline: 1,
uri: '004.ts',
title: '0'
},
{
discontinuity: true,
duration: 10,
timeline: 2,
uri: '005.ts',
title: '0'
},
{
duration: 10,
timeline: 2,
uri: '006.ts',
title: '0'
},
{
duration: 10,
timeline: 2,
uri: '007.ts',
title: '0'
},
{
discontinuity: true,
duration: 10,
timeline: 3,
uri: '008.ts',
title: '0'
},
{
duration: 16,
timeline: 3,
uri: '009.ts',
title: '0'
}
],
targetDuration: 19,
endList: true,
discontinuityStarts: [2, 4, 7],
version: 3
};

33
test/fixtures/integration/domainUris.js vendored Normal file
View file

@ -0,0 +1,33 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
duration: 10,
timeline: 0,
uri: '/00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '/subdir/00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '/00003.ts'
},
{
duration: 10,
timeline: 0,
uri: '/00004.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -9,5 +9,4 @@
/00003.ts
#EXTINF:10,
/00004.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

7
test/fixtures/integration/empty.js vendored Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [],
segments: []
};

View file

@ -0,0 +1,23 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,37 @@
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: '{}'
},
{
duration: 6.08,
timeline: 0,
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
title: '{}'
},
{
duration: 6.6,
timeline: 0,
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
title: '{}'
},
{
duration: 5,
timeline: 0,
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
title: '{}'
}
],
targetDuration: 8,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,42 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts'
},
{
duration: 8,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -13,5 +13,4 @@
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts
#EXTINF:8,
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

View file

@ -0,0 +1,59 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
playlists: [
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 240000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 40000
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 440000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 1928000,
'RESOLUTION': {
width: 960,
height: 540
}
},
timeline: 0,
uri: 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001'
}
],
discontinuityStarts: [],
mediaGroups: {
'VIDEO': {},
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
segments: []
};

63
test/fixtures/integration/encrypted.js vendored Normal file
View file

@ -0,0 +1,63 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 7794,
discontinuitySequence: 0,
discontinuityStarts: [],
segments: [
{
duration: 2.833,
timeline: 0,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/fileSequence52-A.ts'
},
{
duration: 15,
timeline: 0,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/fileSequence52-B.ts'
},
{
duration: 13.333,
timeline: 0,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=52'
},
uri: 'http://media.example.com/fileSequence52-C.ts'
},
{
duration: 15,
timeline: 0,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=53'
},
uri: 'http://media.example.com/fileSequence53-A.ts'
},
{
duration: 14,
timeline: 0,
key: {
method: 'AES-128',
uri: 'https://priv.example.com/key.php?r=54',
iv: new Uint32Array([0, 0, 331, 3063767524])
},
uri: 'http://media.example.com/fileSequence53-B.ts'
},
{
duration: 15,
timeline: 0,
uri: 'http://media.example.com/fileSequence53-B.ts'
}
],
targetDuration: 15,
version: 3
};

43
test/fixtures/integration/event.js vendored Normal file
View file

@ -0,0 +1,43 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'EVENT',
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts'
},
{
duration: 8,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -13,5 +13,4 @@
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts
#EXTINF:8,
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

View file

@ -0,0 +1,18 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 1,
segments: [
{
duration: 6.64,
timeline: 0,
uri: '/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts',
title: '{}'
}
],
targetDuration: 8,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

168
test/fixtures/integration/extinf.js vendored Normal file
View file

@ -0,0 +1,168 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 587500,
offset: 522828
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts',
title: ';asljasdfii11)))00,'
},
{
byterange: {
length: 713084,
offset: 1110328
},
duration: 5,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 476580,
offset: 1823412
},
duration: 9.7,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 535612,
offset: 2299992
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 207176,
offset: 2835604
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 455900,
offset: 3042780
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 657248,
offset: 3498680
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 571708,
offset: 4155928
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 485040,
offset: 4727636
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 709136,
offset: 5212676
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 730004,
offset: 5921812
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 456276,
offset: 6651816
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 3
};

47
test/fixtures/integration/fmp4.js vendored Normal file
View file

@ -0,0 +1,47 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 1,
playlistType: 'VOD',
targetDuration: 6,
discontinuitySequence: 0,
discontinuityStarts: [],
segments: [
{
byterange: {
length: 5666510,
offset: 720
},
duration: 6.006,
timeline: 0,
uri: 'main.mp4',
map: {
byterange: {
length: 720,
offset: 0
},
uri: 'main.mp4'
}
},
{
byterange: {
length: 5861577,
offset: 5667230
},
duration: 6.006,
timeline: 0,
uri: 'main.mp4',
map: {
byterange: {
length: 720,
offset: 0
},
uri: 'main.mp4'
}
}
],
endList: true,
version: 7,
independentSegments: true
};

View file

@ -0,0 +1,7 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [],
segments: []
};

View file

@ -0,0 +1,288 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuityStarts: [],
iFramePlaylists: [
{
attributes: {
'AVERAGE-BANDWIDTH': 248586,
'BANDWIDTH': 593626,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'sdr_720/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 399790,
'BANDWIDTH': 956552,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'sdr_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 826971,
'BANDWIDTH': 1941397,
'CODECS': 'hvc1.2.4.L150.B0',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'sdr_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'SDR'
},
timeline: 0,
uri: 'sdr_2160/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 232253,
'BANDWIDTH': 573073,
'CODECS': 'dvh1.05.01',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'dolby_720/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 365337,
'BANDWIDTH': 905037,
'CODECS': 'dvh1.05.03',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'dolby_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 739114,
'BANDWIDTH': 1893236,
'CODECS': 'dvh1.05.06',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'dolby_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'dolby_2160/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 232511,
'BANDWIDTH': 572673,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'NONE',
'RESOLUTION': { width: 1280, height: 720 },
'URI': 'hdr10_720/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_720/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 364552,
'BANDWIDTH': 905053,
'CODECS': 'hvc1.2.4.L123.B0',
'HDCP-LEVEL': 'TYPE-0',
'RESOLUTION': { width: 1920, height: 1080 },
'URI': 'hdr10_1080/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_1080/iframe_index.m3u8'
},
{
attributes: {
'AVERAGE-BANDWIDTH': 739757,
'BANDWIDTH': 1895477,
'CODECS': 'hvc1.2.4.L150.B0',
'HDCP-LEVEL': 'TYPE-1',
'RESOLUTION': { width: 3840, height: 2160 },
'URI': 'hdr10_2160/iframe_index.m3u8',
'VIDEO-RANGE': 'PQ'
},
timeline: 0,
uri: 'hdr10_2160/iframe_index.m3u8'
}
],
independentSegments: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 3971374,
'AVERAGE-BANDWIDTH': '2778321'
},
uri: 'sdr_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 10022043,
'AVERAGE-BANDWIDTH': '6759875'
},
uri: 'sdr_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'hvc1.2.4.L150.B0',
'VIDEO-RANGE': 'SDR',
'BANDWIDTH': 28058971,
'AVERAGE-BANDWIDTH': '20985770'
},
uri: 'sdr_2160/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'dvh1.05.01',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 5327059,
'AVERAGE-BANDWIDTH': '3385450'
},
uri: 'dolby_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'dvh1.05.03',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 12876596,
'AVERAGE-BANDWIDTH': '7999361'
},
uri: 'dolby_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'dvh1.05.06',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 30041698,
'AVERAGE-BANDWIDTH': '24975091'
},
uri: 'dolby_2160/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'NONE',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 5280654,
'AVERAGE-BANDWIDTH': '3320040'
},
uri: 'hdr10_720/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-0',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 1920,
height: 1080
},
'CODECS': 'hvc1.2.4.L123.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 12886714,
'AVERAGE-BANDWIDTH': '7964551'
},
uri: 'hdr10_1080/prog_index.m3u8',
timeline: 0
},
{
attributes: {
'HDCP-LEVEL': 'TYPE-1',
'CLOSED-CAPTIONS': 'NONE',
'FRAME-RATE': 23.976,
'RESOLUTION': {
width: 3840,
height: 2160
},
'CODECS': 'hvc1.2.4.L150.B0',
'VIDEO-RANGE': 'PQ',
'BANDWIDTH': 29983769,
'AVERAGE-BANDWIDTH': '24833402'
},
uri: 'hdr10_2160/prog_index.m3u8',
timeline: 0
}
],
segments: [],
version: 7
};

View file

@ -0,0 +1,42 @@
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
# https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices-appendixes#Example-playlist
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=248586,BANDWIDTH=593626,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="sdr_720/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=399790,BANDWIDTH=956552,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="sdr_1080/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=826971,BANDWIDTH=1941397,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="sdr_2160/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232253,BANDWIDTH=573073,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="dolby_720/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=365337,BANDWIDTH=905037,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="dolby_1080/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739114,BANDWIDTH=1893236,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="dolby_2160/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232511,BANDWIDTH=572673,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="hdr10_720/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=364552,BANDWIDTH=905053,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8"
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2778321,BANDWIDTH=3971374,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
sdr_720/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6759875,BANDWIDTH=10022043,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
sdr_1080/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=20985770,BANDWIDTH=28058971,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
sdr_2160/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3385450,BANDWIDTH=5327059,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
dolby_720/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7999361,BANDWIDTH=12876596,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
dolby_1080/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24975091,BANDWIDTH=30041698,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
dolby_2160/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3320040,BANDWIDTH=5280654,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE
hdr10_720/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7964551,BANDWIDTH=12886714,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0
hdr10_1080/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24833402,BANDWIDTH=29983769,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1
hdr10_2160/prog_index.m3u8

View file

@ -0,0 +1,45 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
iFramesOnly: true,
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
duration: 2.002,
timeline: 0,
uri: '001.ts'
},
{
duration: 2.002,
timeline: 0,
uri: '002.ts'
},
{
duration: 2.002,
timeline: 0,
uri: '003.ts'
},
{
duration: 2.002,
timeline: 0,
uri: '004.ts'
},
{
duration: 2.002,
timeline: 0,
uri: '005.ts'
},
{
duration: 2.002,
timeline: 0,
uri: '006.ts'
}
],
targetDuration: 3,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,19 @@
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:3
#EXT-X-I-FRAMES-ONLY
#EXTINF:2.002,
001.ts
#EXTINF:2.002,
002.ts
#EXTINF:2.002,
003.ts
#EXTINF:2.002,
004.ts
#EXTINF:2.002,
005.ts
#EXTINF:2.002,
006.ts
#EXT-X-ENDLIST

View file

@ -0,0 +1,23 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,37 @@
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: '{}'
},
{
duration: 6.08,
timeline: 0,
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
title: '{}'
},
{
duration: 6.6,
timeline: 0,
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
title: '{}'
},
{
duration: 5,
timeline: 0,
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
title: '{}'
}
],
targetDuration: 8,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,42 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts'
},
{
duration: 8,
timeline: 0,
uri: '/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -13,5 +13,4 @@
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts
#EXTINF:8,
/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

View file

@ -0,0 +1,166 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
byterange: {
length: 522828,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 587500,
offset: 522828
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 713084,
offset: 1110328
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 476580,
offset: 1823412
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 535612,
offset: 2299992
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 207176,
offset: 2835604
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 455900,
offset: 3042780
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 657248,
offset: 3498680
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 571708,
offset: 4155928
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 485040,
offset: 4727636
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 709136,
offset: 5212676
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 730004,
offset: 5921812
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 456276,
offset: 6651816
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 1.4167,
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
endList: true,
discontinuitySequence: 0,
discontinuityStarts: [],
version: 4
};

View file

@ -0,0 +1,28 @@
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: '{}'
},
{
duration: 8,
timeline: 0,
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts'
},
{
duration: 8,
timeline: 0,
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts'
}
],
targetDuration: 8,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,65 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '001.ts',
title: '0'
},
{
duration: 19,
timeline: 0,
uri: '002.ts',
title: '0'
},
{
duration: 10,
timeline: 0,
uri: '003.ts',
title: '0'
},
{
duration: 11,
timeline: 0,
uri: '004.ts',
title: '0'
},
{
duration: 10,
timeline: 0,
uri: '005.ts',
title: '0'
},
{
duration: 10,
timeline: 0,
uri: '006.ts',
title: '0'
},
{
duration: 10,
timeline: 0,
uri: '007.ts',
title: '0'
},
{
duration: 10,
timeline: 0,
uri: '008.ts',
title: '0'
},
{
duration: 16,
timeline: 0,
uri: '009.ts',
title: '0'
}
],
targetDuration: 10,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,255 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuitySequence: 0,
discontinuityStarts: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
preloadSegment: {
preloadHints: [
{
type: 'PART',
uri: 'filePart273.1.mp4',
byterange: {
length: 2000,
offset: 0
}
},
{
type: 'MAP',
uri: 'file-init.mp4',
byterange: {
length: 5000,
offset: 8355216
}
},
{
type: 'FOO',
uri: 'foo.mp4',
byterange: {
length: 5000,
offset: 0
}
}
],
timeline: 0
},
segments: [
{
byterange: {
length: 587500,
offset: 0
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 587500,
offset: 522828
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 713084,
offset: 1110328
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video2.ts'
},
{
byterange: {
length: 476580,
offset: 1823412
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 535612,
offset: 2299992
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 207176,
offset: 2835604
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 455900,
offset: 3042780
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 657248,
offset: 3498680
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 571708,
offset: 4155928
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 485040,
offset: 4727636
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 709136,
offset: 5212676
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 730004,
offset: 5921812
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 456276,
offset: 6651816
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
parts: [
{
duration: 0.33334,
uri: 'hls_450k_video.part.ts',
byterange: {
length: 45553,
offset: 0
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.part.ts',
byterange: {
length: 28823,
offset: 7622329
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.part.ts',
byterange: {
length: 22444,
offset: 7651152
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.part.ts',
byterange: {
length: 22444,
offset: 7673596
}
}
],
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 1.4167,
parts: [
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 45553,
offset: 8021772
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 28823,
offset: 8067325
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 22444,
offset: 8096148
}
}
],
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
targetDuration: 10,
version: 3
};

View file

@ -1,18 +1,17 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:{{{version}}}
{{#if allowCache}}#EXT-X-ALLOW-CACHE:{{{allowCache}}}{{/if}}
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10,
#EXT-X-BYTERANGE:522828@0
#EXT-X-BYTERANGE:587500@
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:587500@522828
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:713084@1110328
hls_450k_video.ts
#EXT-X-BYTERANGE:713084
hls_450k_video2.ts
#EXTINF:10,
#EXT-X-BYTERANGE:476580@1823412
hls_450k_video.ts
@ -49,10 +48,19 @@ hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:444996@7576776
hls_450k_video.ts
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.part.ts",BYTERANGE=45553
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.part.ts",BYTERANGE=28823@7622329
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.part.ts",BYTERANGE=22444
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.part.ts",BYTERANGE=22444
#EXTINF:10,
#EXT-X-BYTERANGE:331444@8021772
hls_450k_video.ts
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=45553@8021772
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=28823
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=22444
#EXTINF:1.4167,
#EXT-X-BYTERANGE:44556@8353216
hls_450k_video.ts
#EXT-X-ENDLIST
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.1.mp4",BYTERANGE-LENGTH=2000
#EXT-X-PRELOAD-HINT:TYPE=MAP,URI="file-init.mp4",BYTERANGE-LENGTH=5000,BYTERANGE-START=8355216
#EXT-X-PRELOAD-HINT:TYPE=FOO,URI="foo.mp4",BYTERANGE-LENGTH=5000

View file

@ -0,0 +1,151 @@
module.exports = {
allowCache: true,
dateRanges: [],
discontinuitySequence: 0,
discontinuityStarts: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
preloadSegment: {
parts: [
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 22444,
offset: 0
}
}
],
preloadHints: [
{
type: 'PART',
uri: 'filePart273.1.mp4',
byterange: {
length: 2000,
offset: 22444
}
},
{
type: 'MAP',
uri: 'file-init.mp4',
byterange: {
length: 5000,
offset: 8377660
}
},
{
type: 'FOO',
uri: 'foo.mp4',
byterange: {
length: 5000,
offset: 0
}
}
],
timeline: 0
},
segments: [
{
byterange: {
length: 468684,
offset: 7108092
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 444996,
offset: 7576776
},
duration: 10,
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 331444,
offset: 8021772
},
duration: 10,
parts: [
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 45553,
offset: 0
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 28823,
offset: 7622329
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 22444,
offset: 7651152
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 22444,
offset: 7673596
}
}
],
timeline: 0,
uri: 'hls_450k_video.ts'
},
{
byterange: {
length: 44556,
offset: 8353216
},
duration: 1.4167,
parts: [
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 45553,
offset: 8021772
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 28823,
offset: 8067325
}
},
{
duration: 0.33334,
uri: 'hls_450k_video.ts',
byterange: {
length: 22444,
offset: 8096148
}
}
],
timeline: 0,
uri: 'hls_450k_video.ts'
}
],
skip: {
skippedSegments: 3
},
targetDuration: 10,
version: 3
};

View file

@ -0,0 +1,30 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10,
#EXT-X-SKIP:SKIPPED-SEGMENTS=3
#EXTINF:10,
#EXT-X-BYTERANGE:468684@7108092
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:444996@7576776
hls_450k_video.ts
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=45553
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=28823@7622329
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=22444
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=22444
#EXTINF:10,
#EXT-X-BYTERANGE:331444@8021772
hls_450k_video.ts
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=45553@8021772
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=28823
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=22444
#EXTINF:1.4167,
#EXT-X-BYTERANGE:44556@8353216
hls_450k_video.ts
#EXT-X-PART:DURATION=0.33334,URI="hls_450k_video.ts",BYTERANGE=22444
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.1.mp4",BYTERANGE-LENGTH=2000
#EXT-X-PRELOAD-HINT:TYPE=MAP,URI="file-init.mp4",BYTERANGE-LENGTH=5000,BYTERANGE-START=8377660
#EXT-X-PRELOAD-HINT:TYPE=FOO,URI="foo.mp4",BYTERANGE-LENGTH=5000

223
test/fixtures/integration/llhls.js vendored Normal file
View file

@ -0,0 +1,223 @@
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'},
parts: [
{
duration: 0.33334,
independent: true,
uri: 'filePart273.0.mp4'
},
{
duration: 0.33334,
uri: 'filePart273.1.mp4'
},
{
duration: 0.33334,
uri: 'filePart273.2.mp4'
}
],
preloadHints: [
{type: 'PART', uri: 'filePart273.3.mp4'},
{type: 'MAP', uri: 'file-init.mp4'}
],
timeline: 0
},
renditionReports: [
{lastMsn: 273, lastPart: 2, uri: '../1M/waitForMSN.php'},
{lastMsn: 273, lastPart: 1, uri: '../4M/waitForMSN.php'}
],
partInf: {
partTarget: 0.33334
},
partTargetDuration: 0.33334,
segments: [
{
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'
},
timeline: 0,
uri: 'fileSequence266.mp4'
},
{
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110420106.08,
timeline: 0,
uri: 'fileSequence267.mp4'
},
{
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110424106.1602,
timeline: 0,
uri: 'fileSequence268.mp4'
},
{
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110428106.2402,
timeline: 0,
uri: 'fileSequence269.mp4'
},
{
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110432106.3203,
timeline: 0,
uri: 'fileSequence270.mp4'
},
{
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110436106.4004,
timeline: 0,
uri: 'fileSequence271.mp4',
parts: [
{
duration: 0.33334,
uri: 'filePart271.0.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.1.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.2.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.3.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart271.4.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.5.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.6.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.7.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart271.8.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.9.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.10.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.11.mp4'
}
]
},
{
dateTimeObject: new Date('2019-02-14T02:14:00.106Z'),
dateTimeString: '2019-02-14T02:14:00.106Z',
duration: 4.00008,
map: {
uri: 'init.mp4'
},
programDateTime: 1550110440106,
timeline: 0,
uri: 'fileSequence272.mp4',
parts: [
{
duration: 0.33334,
gap: true,
uri: 'filePart272.a.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.b.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.c.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.d.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.e.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart272.f.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.g.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.h.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.i.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.j.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.k.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.l.mp4'
}
]
}
],
serverControl: {
canSkipDateranges: true,
canBlockReload: true,
canSkipUntil: 12,
partHoldBack: 1,
holdBack: 12
},
targetDuration: 4,
version: 6
};

56
test/fixtures/integration/llhls.m3u8 vendored Normal file
View file

@ -0,0 +1,56 @@
#EXTM3U
# This Playlist is a response to: GET https://example.com/2M/waitForMSN.php?_HLS_msn=273&_HLS_part=2
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,CAN-SKIP-DATERANGES=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=12.0,HOLD-BACK=12.0
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-MEDIA-SEQUENCE:266
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:13:36.106Z
#EXT-X-MAP:URI="init.mp4"
#EXTINF:4.00008,
fileSequence266.mp4
#EXTINF:4.00008,
fileSequence267.mp4
#EXTINF:4.00008,
fileSequence268.mp4
#EXTINF:4.00008,
fileSequence269.mp4
#EXTINF:4.00008,
fileSequence270.mp4
#EXT-X-PART:DURATION=0.33334,URI="filePart271.0.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.1.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.2.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.3.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.4.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.5.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.6.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.7.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.8.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.9.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.10.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.11.mp4"
#EXTINF:4.00008,
fileSequence271.mp4
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z
#EXT-X-PART:GAP=YES,DURATION=0.33334,URI="filePart272.a.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.b.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.c.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.d.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.e.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.f.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart272.g.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.h.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.i.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.j.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.k.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.l.mp4"
#EXTINF:4.00008,
fileSequence272.mp4
#EXT-X-PART:DURATION=0.33334,URI="filePart273.0.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart273.1.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart273.2.mp4"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.3.mp4"
#EXT-X-PRELOAD-HINT:TYPE=MAP,URI="file-init.mp4"
#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=2
#EXT-X-RENDITION-REPORT:URI="../4M/waitForMSN.php",LAST-MSN=273,LAST-PART=1

192
test/fixtures/integration/llhlsDelta.js vendored Normal file
View file

@ -0,0 +1,192 @@
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,
preloadHints: [
{type: 'PART', uri: 'filePart273.4.mp4'},
{type: 'MAP', uri: 'file-init.mp4'}
],
parts: [
{
duration: 0.33334,
independent: true,
uri: 'filePart273.0.mp4'
},
{
duration: 0.33334,
uri: 'filePart273.1.mp4'
},
{
duration: 0.33334,
uri: 'filePart273.2.mp4'
},
{
duration: 0.33334,
uri: 'filePart273.3.mp4'
}
]
},
renditionReports: [
{lastMsn: 273, lastPart: 3, uri: '../1M/waitForMSN.php'},
{lastMsn: 273, lastPart: 3, uri: '../4M/waitForMSN.php'}
],
partInf: {
partTarget: 0.33334
},
partTargetDuration: 0.33334,
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: [
{
duration: 0.33334,
uri: 'filePart271.0.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.1.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.2.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.3.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart271.4.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.5.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.6.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.7.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart271.8.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.9.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.10.mp4'
},
{
duration: 0.33334,
uri: 'filePart271.11.mp4'
}
]
},
{
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: [
{
duration: 0.33334,
gap: true,
uri: 'filePart272.a.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.b.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.c.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.d.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.e.mp4'
},
{
duration: 0.33334,
independent: true,
uri: 'filePart272.f.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.g.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.h.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.i.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.j.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.k.mp4'
},
{
duration: 0.33334,
uri: 'filePart272.l.mp4'
}
]
}
],
skip: {
skippedSegments: 3,
recentlyRemovedDateranges: [
'foo',
'bar'
]
},
serverControl: {
canSkipDateranges: true,
canBlockReload: true,
canSkipUntil: 12,
partHoldBack: 1,
holdBack: 12
},
targetDuration: 4,
version: 9
};

View file

@ -0,0 +1,50 @@
#EXTM3U
# Following the example above, this Playlist is a response to: GET https://example.com/2M/waitForMSN.php?_HLS_msn=273&_HLS_part=3 &_HLS_skip=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,CAN-SKIP-DATERANGES=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=12.0,HOLD-BACK=12.0
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-MEDIA-SEQUENCE:266
#EXT-X-SKIP:SKIPPED-SEGMENTS=3,RECENTLY-REMOVED-DATERANGES=foo bar
#EXTINF:4.00008,
fileSequence269.mp4
#EXTINF:4.00008,
fileSequence270.mp4
#EXT-X-PART:DURATION=0.33334,URI="filePart271.0.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.1.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.2.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.3.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.4.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.5.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.6.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.7.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.8.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.9.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.10.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.11.mp4"
#EXTINF:4.00008,
fileSequence271.mp4
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z
#EXT-X-PART:GAP=YES,DURATION=0.33334,URI="filePart272.a.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.b.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.c.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.d.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.e.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.f.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart272.g.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.h.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.i.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.j.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.k.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.l.mp4"
#EXTINF:4.00008,
fileSequence272.mp4
#EXT-X-PART:DURATION=0.33334,URI="filePart273.0.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart273.1.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart273.2.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart273.3.mp4"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.4.mp4"
#EXT-X-PRELOAD-HINT:TYPE=MAP,URI="file-init.mp4"
#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=3
#EXT-X-RENDITION-REPORT:URI="../4M/waitForMSN.php",LAST-MSN=273,LAST-PART=3

View file

@ -0,0 +1,16 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00001.ts'
}
],
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -1,5 +1,4 @@
#EXTM3U
#ZEN-TOTAL-DURATION:50
#EXT-X-TARGETDURATION:-10
#EXTINF:10,
/test/ts-files/zencoder/gogo/00001.ts

View file

@ -0,0 +1,37 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00002.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00003.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00004.ts'
},
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00005.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -1,5 +1,4 @@
#EXTM3U
#ZEN-TOTAL-DURATION:50
#EXT-X-TARGETDURATION:10
#EXTINF:10,
/test/ts-files/zencoder/gogo/00001.ts

View file

@ -0,0 +1,17 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '/test/ts-files/zencoder/gogo/00001.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -1,4 +1,3 @@
#ZEN-TOTAL-DURATION:10
#EXT-X-TARGETDURATION:10
#EXTINF:10,
/test/ts-files/zencoder/gogo/00001.ts

553
test/fixtures/integration/master-fmp4.js vendored Normal file
View file

@ -0,0 +1,553 @@
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: {
English: {
autoselect: true,
default: true,
language: 'eng',
uri: 'a1/prog_index.m3u8'
}
},
aud2: {
English: {
autoselect: true,
default: true,
language: 'eng',
uri: 'a2/prog_index.m3u8'
}
},
aud3: {
English: {
autoselect: true,
default: true,
language: 'eng',
uri: 'a3/prog_index.m3u8'
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {
cc1: {
English: {
autoselect: true,
default: true,
language: 'eng',
instreamId: 'CC1'
}
}
},
'SUBTITLES': {
sub1: {
English: {
autoselect: true,
default: true,
language: 'eng',
uri: 's1/eng/prog_index.m3u8',
forced: false
}
}
}
},
playlists: [{
attributes: {
'AVERAGE-BANDWIDTH': '2165224',
'BANDWIDTH': 2215219,
'CODECS': 'avc1.640020,mp4a.40.2',
'RESOLUTION': {
width: 960,
height: 540
},
'FRAME-RATE': 59.940,
'CLOSED-CAPTIONS': 'cc1',
'AUDIO': 'aud1',
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v4/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '7962844',
'BANDWIDTH': 7976430,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,mp4a.40.2',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v8/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '6165024',
'BANDWIDTH': 6181885,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,mp4a.40.2',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v7/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '4664459',
'BANDWIDTH': 4682666,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,mp4a.40.2',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v6/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '3164759',
'BANDWIDTH': 3170746,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640020,mp4a.40.2',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 720,
width: 1280
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v5/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '1262552',
'BANDWIDTH': 1276223,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,mp4a.40.2',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 432,
width: 768
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v3/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '893243',
'BANDWIDTH': 904744,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,mp4a.40.2',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 360,
width: 640
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v2/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud1',
'AVERAGE-BANDWIDTH': '527673',
'BANDWIDTH': 538201,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640015,mp4a.40.2',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 270,
width: 480
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v1/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '2390334',
'BANDWIDTH': 2440329,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640020,ac-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 540,
width: 960
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v4/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '8187954',
'BANDWIDTH': 8201540,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ac-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v8/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '6390134',
'BANDWIDTH': 6406995,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ac-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v7/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '4889569',
'BANDWIDTH': 4907776,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ac-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v6/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '3389869',
'BANDWIDTH': 3395856,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640020,ac-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 720,
width: 1280
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v5/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '1487662',
'BANDWIDTH': 1501333,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,ac-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 432,
width: 768
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v3/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '1118353',
'BANDWIDTH': 1129854,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,ac-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 360,
width: 640
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v2/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud2',
'AVERAGE-BANDWIDTH': '752783',
'BANDWIDTH': 763311,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640015,ac-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 270,
width: 480
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v1/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '2198334',
'BANDWIDTH': 2248329,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640020,ec-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 540,
width: 960
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v4/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '7995954',
'BANDWIDTH': 8009540,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ec-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v8/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '6198134',
'BANDWIDTH': 6214995,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ec-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v7/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '4697569',
'BANDWIDTH': 4715776,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64002a,ec-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 1080,
width: 1920
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v6/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '3197869',
'BANDWIDTH': 3203856,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640020,ec-3',
'FRAME-RATE': 59.940,
'RESOLUTION': {
height: 720,
width: 1280
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v5/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '1295662',
'BANDWIDTH': 1309333,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,ec-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 432,
width: 768
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v3/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '926353',
'BANDWIDTH': 937854,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.64001e,ec-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 360,
width: 640
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v2/prog_index.m3u8'
},
{
attributes: {
'AUDIO': 'aud3',
'AVERAGE-BANDWIDTH': '560783',
'BANDWIDTH': 571311,
'CLOSED-CAPTIONS': 'cc1',
'CODECS': 'avc1.640015,ec-3',
'FRAME-RATE': 29.970,
'RESOLUTION': {
height: 270,
width: 480
},
'SUBTITLES': 'sub1'
},
timeline: 0,
uri: 'v1/prog_index.m3u8'
}],
segments: [],
version: 6,
independentSegments: true
};

59
test/fixtures/integration/master.js vendored Normal file
View file

@ -0,0 +1,59 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
playlists: [
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 240000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'media.m3u8'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 40000
},
timeline: 0,
uri: 'media1.m3u8'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 440000,
'RESOLUTION': {
width: 396,
height: 224
}
},
timeline: 0,
uri: 'media2.m3u8'
},
{
attributes: {
'PROGRAM-ID': 1,
'BANDWIDTH': 1928000,
'RESOLUTION': {
width: 960,
height: 540
}
},
timeline: 0,
uri: 'media3.m3u8'
}
],
discontinuityStarts: [],
mediaGroups: {
'VIDEO': {},
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
segments: []
};

33
test/fixtures/integration/media.js vendored Normal file
View file

@ -0,0 +1,33 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
playlistType: 'VOD',
segments: [
{
duration: 10,
timeline: 0,
uri: 'media-00001.ts'
},
{
duration: 10,
timeline: 0,
uri: 'media-00002.ts'
},
{
duration: 10,
timeline: 0,
uri: 'media-00003.ts'
},
{
duration: 10,
timeline: 0,
uri: 'media-00004.ts'
}
],
targetDuration: 10,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -9,5 +9,4 @@ media-00002.ts
media-00003.ts
#EXTINF:10,
media-00004.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST

View file

@ -0,0 +1,37 @@
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: '{}'
},
{
duration: 6.08,
timeline: 0,
uri: '/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts',
title: '{}'
},
{
duration: 6.6,
timeline: 0,
uri: '/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts',
title: '{}'
},
{
duration: 5,
timeline: 0,
uri: '/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts',
title: '{}'
}
],
targetDuration: 8,
endList: true,
discontinuitySequence: 0,
discontinuityStarts: []
};

View file

@ -0,0 +1,21 @@
module.exports = {
allowCache: true,
dateRanges: [],
iFramePlaylists: [],
mediaSequence: 0,
segments: [
{
duration: 10,
timeline: 0,
uri: '00001.ts'
},
{
duration: 10,
timeline: 0,
uri: '00002.ts'
}
],
targetDuration: 10,
discontinuitySequence: 0,
discontinuityStarts: []
};

Some files were not shown because too many files have changed in this diff Show more