First release

This commit is contained in:
Owen Quinlan 2021-07-02 19:29:34 +10:00
commit fa6c85266e
2339 changed files with 761050 additions and 0 deletions

261
node_modules/mpd-parser/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,261 @@
<a name="0.17.0"></a>
# [0.17.0](https://github.com/videojs/mpd-parser/compare/v0.16.0...v0.17.0) (2021-05-28)
### Features
* caption services metadata support ([#131](https://github.com/videojs/mpd-parser/issues/131)) ([75ecbc2](https://github.com/videojs/mpd-parser/commit/75ecbc2))
### Chores
* **package:** update to vhs-utils[@3](https://github.com/3).0.2 ([#134](https://github.com/videojs/mpd-parser/issues/134)) ([744142d](https://github.com/videojs/mpd-parser/commit/744142d))
<a name="0.16.0"></a>
# [0.16.0](https://github.com/videojs/mpd-parser/compare/v0.15.4...v0.16.0) (2021-03-26)
### Features
* add a binary to convert mpd to m3u8 json in node ([#129](https://github.com/videojs/mpd-parser/issues/129)) ([608aa9d](https://github.com/videojs/mpd-parser/commit/608aa9d))
* expose presentationTimeOffset on segments that have it ([#81](https://github.com/videojs/mpd-parser/issues/81)) ([8b58b39](https://github.com/videojs/mpd-parser/commit/8b58b39))
* parse Label element and use as m3u8 group label ([#113](https://github.com/videojs/mpd-parser/issues/113)) ([5dde0e9](https://github.com/videojs/mpd-parser/commit/5dde0e9))
### Bug Fixes
* bump xmldom to prevent npm security issue ([#122](https://github.com/videojs/mpd-parser/issues/122)) ([e132e40](https://github.com/videojs/mpd-parser/commit/e132e40))
* support multiple audio/subtitle playlists and export generateSidxKey ([#123](https://github.com/videojs/mpd-parser/issues/123)) ([f7105d8](https://github.com/videojs/mpd-parser/commit/f7105d8))
### Chores
* fix publish, add bin to files, update vjsverify ([cb4d772](https://github.com/videojs/mpd-parser/commit/cb4d772))
### Documentation
* better explain how to update the JS manifest files ([#130](https://github.com/videojs/mpd-parser/issues/130)) ([21aa91c](https://github.com/videojs/mpd-parser/commit/21aa91c))
* update usage example in README ([#127](https://github.com/videojs/mpd-parser/issues/127)) ([f0da2cc](https://github.com/videojs/mpd-parser/commit/f0da2cc))
<a name="0.15.4"></a>
## [0.15.4](https://github.com/videojs/mpd-parser/compare/v0.15.3...v0.15.4) (2021-02-24)
An accidental republish of 0.15.3 with no changes.
<a name="0.15.3"></a>
## [0.15.3](https://github.com/videojs/mpd-parser/compare/v0.15.2...v0.15.3) (2021-02-24)
### Features
* add support for endNumber in template duration parsing ([#120](https://github.com/videojs/mpd-parser/issues/120)) ([0a75d62](https://github.com/videojs/mpd-parser/commit/0a75d62))
<a name="0.15.2"></a>
## [0.15.2](https://github.com/videojs/mpd-parser/compare/v0.15.1...v0.15.2) (2021-01-12)
### Bug Fixes
* cjs dist should only import cjs ([#118](https://github.com/videojs/mpd-parser/issues/118)) ([0529e62](https://github.com/videojs/mpd-parser/commit/0529e62))
<a name="0.15.1"></a>
## [0.15.1](https://github.com/videojs/mpd-parser/compare/v0.15.0...v0.15.1) (2021-01-11)
### Chores
* update to vhs-utils[@3](https://github.com/3) ([#115](https://github.com/videojs/mpd-parser/issues/115)) ([3240031](https://github.com/videojs/mpd-parser/commit/3240031))
<a name="0.15.0"></a>
# [0.15.0](https://github.com/videojs/mpd-parser/compare/v0.14.0...v0.15.0) (2020-11-17)
### Features
* expose add addSidxSegmentsToPlaylist ([#109](https://github.com/videojs/mpd-parser/issues/109)) ([880c139](https://github.com/videojs/mpd-parser/commit/880c139))
* recognize webm segments as video ([#79](https://github.com/videojs/mpd-parser/issues/79)) ([aa1bbf1](https://github.com/videojs/mpd-parser/commit/aa1bbf1))
### Bug Fixes
* edge case error handling on ie 11 ([#112](https://github.com/videojs/mpd-parser/issues/112)) ([23040c1](https://github.com/videojs/mpd-parser/commit/23040c1))
### Chores
* switch from travis ci to github ci ([#111](https://github.com/videojs/mpd-parser/issues/111)) ([1dc9090](https://github.com/videojs/mpd-parser/commit/1dc9090))
### Tests
* fix tests introduced in [#79](https://github.com/videojs/mpd-parser/issues/79) that was not rebased on [#103](https://github.com/videojs/mpd-parser/issues/103) ([#110](https://github.com/videojs/mpd-parser/issues/110)) ([1a6abbf](https://github.com/videojs/mpd-parser/commit/1a6abbf))
<a name="0.14.0"></a>
# [0.14.0](https://github.com/videojs/mpd-parser/compare/v0.13.0...v0.14.0) (2020-11-03)
### Chores
* **package:** update to vhs-utils[@2](https://github.com/2) ([#108](https://github.com/videojs/mpd-parser/issues/108)) ([8337333](https://github.com/videojs/mpd-parser/commit/8337333))
<a name="0.13.0"></a>
# [0.13.0](https://github.com/videojs/mpd-parser/compare/v0.12.0...v0.13.0) (2020-09-29)
### Features
* add CODECS attribute to subtitle playlists if it exists ([#106](https://github.com/videojs/mpd-parser/issues/106)) ([bc0872a](https://github.com/videojs/mpd-parser/commit/bc0872a))
<a name="0.12.0"></a>
# [0.12.0](https://github.com/videojs/mpd-parser/compare/v0.11.0...v0.12.0) (2020-09-03)
### Features
* remove default value of 0 for minimumUpdatePeriod ([#103](https://github.com/videojs/mpd-parser/issues/103)) ([38ca9ad](https://github.com/videojs/mpd-parser/commit/38ca9ad))
### BREAKING CHANGES
* The minimumUpdatePeriod property is now omitted from parsed output if it is not present in the manifest, rather than using a default value of 0. This is to allow differentiation between cases when a value of 0 is present in the manifest and when no value is provided.
<a name="0.11.0"></a>
# [0.11.0](https://github.com/videojs/mpd-parser/compare/v0.10.1...v0.11.0) (2020-08-12)
### Features
* parse out Location elements ([#102](https://github.com/videojs/mpd-parser/issues/102)) ([967e5e6](https://github.com/videojs/mpd-parser/commit/967e5e6))
<a name="0.10.1"></a>
## [0.10.1](https://github.com/videojs/mpd-parser/compare/v0.10.0...v0.10.1) (2020-03-31)
### Bug Fixes
* don't adjust mediaPresentationDuration by timescale for segment duration when using SegmentBase ([#94](https://github.com/videojs/mpd-parser/issues/94)) ([40cdd00](https://github.com/videojs/mpd-parser/commit/40cdd00))
<a name="0.10.0"></a>
# [0.10.0](https://github.com/videojs/mpd-parser/compare/v0.9.0...v0.10.0) (2020-02-04)
### Features
* expose suggestPresentationDelay if the type is dynamic ([#82](https://github.com/videojs/mpd-parser/issues/82)) ([cd27003](https://github.com/videojs/mpd-parser/commit/cd27003))
<a name="0.9.0"></a>
# [0.9.0](https://github.com/videojs/mpd-parser/compare/v0.8.2...v0.9.0) (2019-08-30)
### Features
* node support ([#75](https://github.com/videojs/mpd-parser/issues/75)) ([58b43b0](https://github.com/videojs/mpd-parser/commit/58b43b0))
<a name="0.8.2"></a>
## [0.8.2](https://github.com/videojs/mpd-parser/compare/v0.8.1...v0.8.2) (2019-08-22)
### Chores
* update generator and use [@videojs](https://github.com/videojs)/vhs-utils ([#76](https://github.com/videojs/mpd-parser/issues/76)) ([1238749](https://github.com/videojs/mpd-parser/commit/1238749))
<a name="0.8.1"></a>
## [0.8.1](https://github.com/videojs/mpd-parser/compare/v0.8.0...v0.8.1) (2019-05-01)
### Bug Fixes
* skip playlists without sidx ([#73](https://github.com/videojs/mpd-parser/issues/73)) ([67d2bad](https://github.com/videojs/mpd-parser/commit/67d2bad)), closes [videojs/video.js#5289](https://github.com/videojs/video.js/issues/5289)
<a name="0.8.0"></a>
# [0.8.0](https://github.com/videojs/mpd-parser/compare/v0.7.0...v0.8.0) (2019-04-11)
### Features
* add sidx information to segment base playlists ([#41](https://github.com/videojs/mpd-parser/issues/41)) ([1176109](https://github.com/videojs/mpd-parser/commit/1176109))
### Bug Fixes
* make byteRange.length inclusive ([#43](https://github.com/videojs/mpd-parser/issues/43)) ([28d217a](https://github.com/videojs/mpd-parser/commit/28d217a))
### Chores
* add netlify for testing ([#45](https://github.com/videojs/mpd-parser/issues/45)) ([a78a7be](https://github.com/videojs/mpd-parser/commit/a78a7be))
* Update videojs-generate-karma-config to the latest version 🚀 ([#37](https://github.com/videojs/mpd-parser/issues/37)) ([a18c660](https://github.com/videojs/mpd-parser/commit/a18c660))
* Update videojs-generate-karma-config to the latest version 🚀 ([#38](https://github.com/videojs/mpd-parser/issues/38)) ([3aaabac](https://github.com/videojs/mpd-parser/commit/3aaabac))
* Update videojs-generate-rollup-config to the latest version 🚀 ([#36](https://github.com/videojs/mpd-parser/issues/36)) ([3f6ccbd](https://github.com/videojs/mpd-parser/commit/3f6ccbd))
* **package:** update videojs-generate-karma-config to 5.0.2 ([#54](https://github.com/videojs/mpd-parser/issues/54)) ([fcbabc3](https://github.com/videojs/mpd-parser/commit/fcbabc3))
* **package:** videojs-generate-karma-config[@4](https://github.com/4).0.0 does not exist ([#44](https://github.com/videojs/mpd-parser/issues/44)) ([bc361b5](https://github.com/videojs/mpd-parser/commit/bc361b5))
<a name="0.7.0"></a>
# [0.7.0](https://github.com/videojs/mpd-parser/compare/v0.6.1...v0.7.0) (2018-10-24)
### Features
* limited multiperiod support ([#35](https://github.com/videojs/mpd-parser/issues/35)) ([aee87a0](https://github.com/videojs/mpd-parser/commit/aee87a0))
### Bug Fixes
* fixed segment timeline parsing when duration is present ([#34](https://github.com/videojs/mpd-parser/issues/34)) ([90feb2d](https://github.com/videojs/mpd-parser/commit/90feb2d))
* Remove the postinstall script to prevent install issues ([#29](https://github.com/videojs/mpd-parser/issues/29)) ([ae458f4](https://github.com/videojs/mpd-parser/commit/ae458f4))
### Chores
* Update to generator-videojs-plugin[@7](https://github.com/7).2.0 ([#28](https://github.com/videojs/mpd-parser/issues/28)) ([909cf08](https://github.com/videojs/mpd-parser/commit/909cf08))
* **package:** Update dependencies to enable Greenkeeper 🌴 ([#30](https://github.com/videojs/mpd-parser/issues/30)) ([0593c2c](https://github.com/videojs/mpd-parser/commit/0593c2c))
<a name="0.6.1"></a>
## [0.6.1](https://github.com/videojs/mpd-parser/compare/v0.6.0...v0.6.1) (2018-05-17)
### Bug Fixes
* babel es module ([#25](https://github.com/videojs/mpd-parser/issues/25)) ([9a84461](https://github.com/videojs/mpd-parser/commit/9a84461))
<a name="0.6.0"></a>
# [0.6.0](https://github.com/videojs/mpd-parser/compare/v0.5.0...v0.6.0) (2018-03-30)
### Features
* support in-manifest DRM data ([#23](https://github.com/videojs/mpd-parser/issues/23)) ([7ce9aca](https://github.com/videojs/mpd-parser/commit/7ce9aca))
<a name="0.5.0"></a>
# [0.5.0](https://github.com/videojs/mpd-parser/compare/v0.4.0...v0.5.0) (2018-03-15)
### Features
* live support with SegmentTemplate[@duration](https://github.com/duration) and more ([#22](https://github.com/videojs/mpd-parser/issues/22)) ([f1cee87](https://github.com/videojs/mpd-parser/commit/f1cee87))
<a name="0.4.0"></a>
# [0.4.0](https://github.com/videojs/mpd-parser/compare/v0.3.0...v0.4.0) (2018-02-26)
### Features
* Adding support for segments in Period and Representation. ([#19](https://github.com/videojs/mpd-parser/issues/19)) ([8e59b38](https://github.com/videojs/mpd-parser/commit/8e59b38))
<a name="0.3.0"></a>
# [0.3.0](https://github.com/videojs/mpd-parser/compare/v0.2.1...v0.3.0) (2018-02-06)
### Features
* Parse <SegmentList> and <SegmentBase> ([#18](https://github.com/videojs/mpd-parser/issues/18)) ([71b8976](https://github.com/videojs/mpd-parser/commit/71b8976))
* Support for inheriting BaseURL and alternate BaseURLs ([#17](https://github.com/videojs/mpd-parser/issues/17)) ([7dad5d5](https://github.com/videojs/mpd-parser/commit/7dad5d5))
* add support for SegmentTemplate padding format string and SegmentTimeline ([#16](https://github.com/videojs/mpd-parser/issues/16)) ([87933f6](https://github.com/videojs/mpd-parser/commit/87933f6))
<a name="0.2.1"></a>
## [0.2.1](https://github.com/videojs/mpd-parser/compare/v0.2.0...v0.2.1) (2017-12-15)
### Bug Fixes
* access HTMLCollections with IE11 compatibility ([#15](https://github.com/videojs/mpd-parser/issues/15)) ([5612984](https://github.com/videojs/mpd-parser/commit/5612984))
<a name="0.2.0"></a>
# [0.2.0](https://github.com/videojs/mpd-parser/compare/v0.1.1...v0.2.0) (2017-12-12)
### Features
* Support for vtt ([#13](https://github.com/videojs/mpd-parser/issues/13)) ([96fc406](https://github.com/videojs/mpd-parser/commit/96fc406))
### Tests
* add more tests for vtt ([#14](https://github.com/videojs/mpd-parser/issues/14)) ([4068790](https://github.com/videojs/mpd-parser/commit/4068790))
<a name="0.1.1"></a>
## [0.1.1](https://github.com/videojs/mpd-parser/compare/v0.1.0...v0.1.1) (2017-12-07)
### Bug Fixes
* avoid using Array.prototype.fill for IE support ([#11](https://github.com/videojs/mpd-parser/issues/11)) ([5c444de](https://github.com/videojs/mpd-parser/commit/5c444de))
<a name="0.1.0"></a>
# 0.1.0 (2017-11-29)
### Bug Fixes
* switch off in-manifest caption support ([#8](https://github.com/videojs/mpd-parser/issues/8)) ([15712c6](https://github.com/videojs/mpd-parser/commit/15712c6))
CHANGELOG
=========
## HEAD (Unreleased)
_(none)_
--------------------

34
node_modules/mpd-parser/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,34 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have NodeJS 4.0 or higher and npm installed.
1. Fork this repository and clone your fork
1. Install dependencies: `npm install`
1. Run a development server: `npm start`
### Making Changes
Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
#### Updating test expecations
You can regenerate the JS manifest files by running `mpd-to-m3u8-json` binary (or via the web page) but you'll need to update the `pssh` properties to be converted into a `new Uint8Array`.
### Running Tests
Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
- In all available and supported browsers: `npm test`
- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
[karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md

13
node_modules/mpd-parser/LICENSE generated vendored Normal file
View file

@ -0,0 +1,13 @@
Copyright Brightcove, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

161
node_modules/mpd-parser/README.md generated vendored Normal file
View file

@ -0,0 +1,161 @@
# mpd-parser
[![Build Status](https://travis-ci.org/videojs/mpd-parser.svg?branch=master)](https://travis-ci.org/videojs/mpd-parser)
[![Greenkeeper badge](https://badges.greenkeeper.io/videojs/mpd-parser.svg)](https://greenkeeper.io/)
[![Slack Status](http://slack.videojs.com/badge.svg)](http://slack.videojs.com)
[![NPM](https://nodei.co/npm/mpd-parser.png?downloads=true&downloadRank=true)](https://nodei.co/npm/mpd-parser/)
mpd parser
## Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Usage](#usage)
- [Parsed Output](#parsed-output)
- [Including the Parser](#including-the-parser)
- [`<script>` Tag](#script-tag)
- [Browserify](#browserify)
- [RequireJS/AMD](#requirejsamd)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
```sh
npm install --save mpd-parser
```
## Usage
```js
// get your manifest in whatever way works best
// for example, by reading the file from the filesystem in node
// or using fetch in a browser like so:
const manifestUri = 'https://example.com/dash.xml';
const res = await fetch(manifestUri);
const manifest = await res.text();
var parsedManifest = mpdParser.parse(manifest, { manifestUri });
```
### Parsed Output
The parser ouputs a plain javascript object with the following structure:
```js
Manifest {
allowCache: boolean,
endList: boolean,
mediaSequence: number,
discontinuitySequence: number,
playlistType: string,
playlists: [
{
attributes: {},
Manifest
}
],
mediaGroups: {
AUDIO: {
'GROUP-ID': {
default: boolean,
autoselect: boolean,
language: string,
uri: string,
instreamId: string,
characteristics: string,
forced: boolean
}
},
VIDEO: {},
'CLOSED-CAPTIONS': {},
SUBTITLES: {}
},
dateTimeString: string,
dateTimeObject: Date,
targetDuration: number,
totalDuration: number,
discontinuityStarts: [number],
segments: [
{
byterange: {
length: number,
offset: number
},
duration: number,
attributes: {},
discontinuity: number,
uri: string,
timeline: number,
key: {
method: string,
uri: string,
iv: string
},
map: {
uri: string,
byterange: {
length: number,
offset: number
}
},
'cue-out': string,
'cue-out-cont': string,
'cue-in': string
}
]
}
```
## Including the Parser
To include mpd-parser on your website or web application, use any of the following methods.
### `<script>` Tag
This is the simplest case. Get the script in whatever way you prefer and include it on your page.
```html
<script src="//path/to/mpd-parser.min.js"></script>
<script>
var mpdParser = window['mpd-parser'];
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
</script>
```
### Browserify
When using with Browserify, install mpd-parser via npm and `require` the parser as you would any other module.
```js
var mpdParser = require('mpd-parser');
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
```
With ES6:
```js
import { parse } from 'mpd-parser';
const parsedManifest = parse(manifest, manifestUrl);
```
### RequireJS/AMD
When using with RequireJS (or another AMD library), get the script in whatever way you prefer and `require` the parser as you normally would:
```js
require(['mpd-parser'], function(mpdParser) {
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
});
```
## License
Apache-2.0. Copyright (c) Brightcove, Inc

12
node_modules/mpd-parser/bin/parse.js generated vendored Normal file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const {parse} = require('../dist/mpd-parser.cjs.js');
const file = path.resolve(process.cwd(), process.argv[2]);
const result = parse(fs.readFileSync(file, 'utf8'), {
manifestUri: ''
});
console.log(JSON.stringify(result, null, 2));

2092
node_modules/mpd-parser/dist/mpd-parser.cjs.js generated vendored Normal file

File diff suppressed because it is too large Load diff

2074
node_modules/mpd-parser/dist/mpd-parser.es.js generated vendored Normal file

File diff suppressed because it is too large Load diff

2334
node_modules/mpd-parser/dist/mpd-parser.js generated vendored Normal file

File diff suppressed because it is too large Load diff

2
node_modules/mpd-parser/dist/mpd-parser.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

48
node_modules/mpd-parser/index.html generated vendored Normal file
View file

@ -0,0 +1,48 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>mpd-parser Demo</title>
</head>
<body>
<p>Open dev tools to try it out</p>
<ul>
<li><a href="test/debug.html">Run unit tests in browser.</a></li>
<li><a href="docs/api/">Read generated docs.</a></li>
</ul>
<form id=parse>
<label>
Video URL:
<input id=url type=url value="http://dash.edgesuite.net/akamai/bbb_30fps/bbb_30fps.mpd">
</label>
<button type=submit>Parse</button>
</form>
<script src="dist/mpd-parser.js"></script>
<script>
(function(window, mpdParser) {
var parseForm = document.getElementById('parse');
var url = document.getElementById('url');
parseForm.addEventListener('submit', function(event) {
event.preventDefault();
fetch(url.value)
.then(function(response) {
return response.text();
}).then(function(body) {
console.log('Original ->');
console.log(body);
var parsedMpd = mpdParser.parse(body, {});
console.log('Parsed ->');
console.log(parsedMpd);
}).catch(error => console.error(error));
return false;
});
}(window, window.mpdParser));
</script>
</body>
</html>

125
node_modules/mpd-parser/package.json generated vendored Normal file
View file

@ -0,0 +1,125 @@
{
"_from": "mpd-parser@0.17.0",
"_id": "mpd-parser@0.17.0",
"_inBundle": false,
"_integrity": "sha512-oKS5G0jCcHHJ3sHYlcLeM9Xcbuixl08eAx7QW0Th7ChlZiI0YvLtGaHE/L0aKUBJFNvtkeksIr8XgJgSBBsS4g==",
"_location": "/mpd-parser",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "mpd-parser@0.17.0",
"name": "mpd-parser",
"escapedName": "mpd-parser",
"rawSpec": "0.17.0",
"saveSpec": null,
"fetchSpec": "0.17.0"
},
"_requiredBy": [
"/@videojs/http-streaming",
"/video.js"
],
"_resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.17.0.tgz",
"_shasum": "d7f3002edcb706f98993ef75846a713d056d3332",
"_spec": "mpd-parser@0.17.0",
"_where": "F:\\Documents\\websites\\BMM\\node_modules\\video.js",
"author": {
"name": "Brightcove, Inc"
},
"bin": {
"mpd-to-m3u8-json": "bin/parse.js"
},
"browserslist": [
"defaults",
"ie 11"
],
"bugs": {
"url": "https://github.com/videojs/mpd-parser/issues"
},
"bundleDependencies": false,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.2",
"global": "^4.4.0",
"xmldom": "^0.5.0"
},
"deprecated": false,
"description": "mpd parser",
"devDependencies": {
"@rollup/plugin-replace": "^2.3.4",
"@videojs/generator-helpers": "~2.0.1",
"jsdom": "^16.4.0",
"karma": "^5.2.3",
"rollup": "^2.36.1",
"rollup-plugin-string": "^3.0.0",
"sinon": "^9.2.3",
"videojs-generate-karma-config": "~7.0.0",
"videojs-generate-rollup-config": "~6.1.0",
"videojs-generator-verify": "~3.0.2",
"videojs-standard": "^8.0.4"
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"generator-videojs-plugin": {
"version": "7.7.3"
},
"homepage": "https://github.com/videojs/mpd-parser#readme",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"keywords": [
"videojs",
"videojs-plugin"
],
"license": "Apache-2.0",
"lint-staged": {
"*.js": "vjsstandard --fix",
"README.md": "doctoc --notitle"
},
"main": "dist/mpd-parser.cjs.js",
"module": "dist/mpd-parser.es.js",
"name": "mpd-parser",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/videojs/mpd-parser.git"
},
"scripts": {
"build": "npm-run-all -s clean -p build:*",
"build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'",
"build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'",
"build:js": "rollup -c scripts/rollup.config.js",
"clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist",
"lint": "vjsstandard",
"netlify": "node scripts/netlify.js",
"posttest": "shx cat test/dist/coverage/text.txt",
"prenetlify": "npm run build",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose",
"preversion": "npm test",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"start": "npm-run-all -p server watch",
"test": "npm-run-all lint build-test && npm-run-all test:*",
"test:browser": "karma start scripts/karma.conf.js",
"test:node": "qunit test/dist/bundle-node.js",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:js": "npm run build:js -- -w"
},
"version": "0.17.0",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
}
}

13
node_modules/mpd-parser/scripts/karma.conf.js generated vendored Normal file
View file

@ -0,0 +1,13 @@
const generate = require('videojs-generate-karma-config');
module.exports = function(config) {
// see https://github.com/videojs/videojs-generate-karma-config
// for options
const options = {};
config = generate(config, options);
// any other custom stuff not supported by options here!
};

14
node_modules/mpd-parser/scripts/netlify.js generated vendored Normal file
View file

@ -0,0 +1,14 @@
const path = require('path');
const sh = require('shelljs');
const files = ['dist', 'index.html'];
const deployDir = 'deploy';
// cleanup previous deploy
sh.rm('-rf', deployDir);
// make sure the directory exists
sh.mkdir('-p', deployDir);
// copy over dist, and html files
files
.forEach((file) => sh.cp('-r', file, path.join(deployDir, file)));

66
node_modules/mpd-parser/scripts/rollup.config.js generated vendored Normal file
View file

@ -0,0 +1,66 @@
const generate = require('videojs-generate-rollup-config');
const string = require('rollup-plugin-string').string;
const replace = require('@rollup/plugin-replace');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
const options = {
input: 'src/index.js',
plugins(defaults) {
defaults.test.unshift('string');
defaults.module.unshift('replace');
return defaults;
},
primedPlugins(defaults) {
defaults.string = string({include: ['test/manifests/*.mpd']});
// 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'
});
return defaults;
},
externals(defaults) {
defaults.module.push('@videojs/vhs-utils');
defaults.module.push('xmldom');
defaults.module.push('atob');
defaults.module.push('url-toolkit');
return defaults;
},
globals(defaults) {
defaults.browser.xmldom = 'window';
defaults.browser.atob = 'window.atob';
defaults.test.xmldom = 'window';
defaults.test.atob = 'window.atob';
defaults.test.jsdom = '{JSDOM: function() { return {window: window}; }}';
return defaults;
}
};
const config = generate(options);
if (config.builds.test) {
config.builds.testNode = config.makeBuild('test', {
input: 'test/**/*.test.js',
output: [{
name: `${config.settings.exportName}Tests`,
file: 'test/dist/bundle-node.js',
format: 'cjs'
}]
});
config.builds.testNode.output[0].globals = {};
config.builds.testNode.external = [].concat(config.settings.externals.module).concat([
'jsdom',
'qunit'
]);
}
// Add additonal builds/customization here!
// export the builds to rollup
export default Object.values(config.builds);

9
node_modules/mpd-parser/src/errors.js generated vendored Normal file
View file

@ -0,0 +1,9 @@
export default {
INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
DASH_INVALID_XML: 'DASH_INVALID_XML',
NO_BASE_URL: 'NO_BASE_URL',
MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
};

39
node_modules/mpd-parser/src/index.js generated vendored Normal file
View file

@ -0,0 +1,39 @@
import { version } from '../package.json';
import { toM3u8, generateSidxKey } from './toM3u8';
import { toPlaylists } from './toPlaylists';
import { inheritAttributes } from './inheritAttributes';
import { stringToMpdXml } from './stringToMpdXml';
import { parseUTCTimingScheme } from './parseUTCTimingScheme';
import {addSidxSegmentsToPlaylist} from './segment/segmentBase.js';
const VERSION = version;
const parse = (manifestString, options = {}) => {
const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
const playlists = toPlaylists(parsedManifestInfo.representationInfo);
return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping);
};
/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
*
* @param {string} manifestString
* XML string of the MPD manifest
* @return {Object|null}
* Attributes of UTCTiming node specified in the manifest. Null if none found
*/
const parseUTCTiming = (manifestString) =>
parseUTCTimingScheme(stringToMpdXml(manifestString));
export {
VERSION,
parse,
parseUTCTiming,
stringToMpdXml,
inheritAttributes,
toPlaylists,
toM3u8,
addSidxSegmentsToPlaylist,
generateSidxKey
};

436
node_modules/mpd-parser/src/inheritAttributes.js generated vendored Normal file
View file

@ -0,0 +1,436 @@
import window from 'global/window';
import { flatten } from './utils/list';
import { merge } from './utils/object';
import { findChildren, getContent } from './utils/xml';
import { parseAttributes } from './parseAttributes';
import errors from './errors';
import resolveUrl from '@videojs/vhs-utils/es/resolve-url';
import decodeB64ToUint8Array from '@videojs/vhs-utils/es/decode-b64-to-uint8-array';
const keySystemsMap = {
'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
};
/**
* Builds a list of urls that is the product of the reference urls and BaseURL values
*
* @param {string[]} referenceUrls
* List of reference urls to resolve to
* @param {Node[]} baseUrlElements
* List of BaseURL nodes from the mpd
* @return {string[]}
* List of resolved urls
*/
export const buildBaseUrls = (referenceUrls, baseUrlElements) => {
if (!baseUrlElements.length) {
return referenceUrls;
}
return flatten(referenceUrls.map(function(reference) {
return baseUrlElements.map(function(baseUrlElement) {
return resolveUrl(reference, getContent(baseUrlElement));
});
}));
};
/**
* Contains all Segment information for its containing AdaptationSet
*
* @typedef {Object} SegmentInformation
* @property {Object|undefined} template
* Contains the attributes for the SegmentTemplate node
* @property {Object[]|undefined} timeline
* Contains a list of atrributes for each S node within the SegmentTimeline node
* @property {Object|undefined} list
* Contains the attributes for the SegmentList node
* @property {Object|undefined} base
* Contains the attributes for the SegmentBase node
*/
/**
* Returns all available Segment information contained within the AdaptationSet node
*
* @param {Node} adaptationSet
* The AdaptationSet node to get Segment information from
* @return {SegmentInformation}
* The Segment information contained within the provided AdaptationSet
*/
export const getSegmentInformation = (adaptationSet) => {
const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL')
.map(s => merge({ tag: 'SegmentURL' }, parseAttributes(s)));
const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
const segmentTimelineParentNode = segmentList || segmentTemplate;
const segmentTimeline = segmentTimelineParentNode &&
findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
const segmentInitialization = segmentInitializationParentNode &&
findChildren(segmentInitializationParentNode, 'Initialization')[0];
// SegmentTemplate is handled slightly differently, since it can have both
// @initialization and an <Initialization> node. @initialization can be templated,
// while the node can have a url and range specified. If the <SegmentTemplate> has
// both @initialization and an <Initialization> subelement we opt to override with
// the node, as this interaction is not defined in the spec.
const template = segmentTemplate && parseAttributes(segmentTemplate);
if (template && segmentInitialization) {
template.initialization =
(segmentInitialization && parseAttributes(segmentInitialization));
} else if (template && template.initialization) {
// If it is @initialization we convert it to an object since this is the format that
// later functions will rely on for the initialization segment. This is only valid
// for <SegmentTemplate>
template.initialization = { sourceURL: template.initialization };
}
const segmentInfo = {
template,
timeline: segmentTimeline &&
findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
list: segmentList && merge(
parseAttributes(segmentList),
{
segmentUrls,
initialization: parseAttributes(segmentInitialization)
}
),
base: segmentBase && merge(parseAttributes(segmentBase), {
initialization: parseAttributes(segmentInitialization)
})
};
Object.keys(segmentInfo).forEach(key => {
if (!segmentInfo[key]) {
delete segmentInfo[key];
}
});
return segmentInfo;
};
/**
* Contains Segment information and attributes needed to construct a Playlist object
* from a Representation
*
* @typedef {Object} RepresentationInformation
* @property {SegmentInformation} segmentInfo
* Segment information for this Representation
* @property {Object} attributes
* Inherited attributes for this Representation
*/
/**
* Maps a Representation node to an object containing Segment information and attributes
*
* @name inheritBaseUrlsCallback
* @function
* @param {Node} representation
* Representation node from the mpd
* @return {RepresentationInformation}
* Representation information needed to construct a Playlist object
*/
/**
* Returns a callback for Array.prototype.map for mapping Representation nodes to
* Segment information and attributes using inherited BaseURL nodes.
*
* @param {Object} adaptationSetAttributes
* Contains attributes inherited by the AdaptationSet
* @param {string[]} adaptationSetBaseUrls
* Contains list of resolved base urls inherited by the AdaptationSet
* @param {SegmentInformation} adaptationSetSegmentInfo
* Contains Segment information for the AdaptationSet
* @return {inheritBaseUrlsCallback}
* Callback map function
*/
export const inheritBaseUrls =
(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) =>
(representation) => {
const repBaseUrlElements = findChildren(representation, 'BaseURL');
const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
const attributes = merge(adaptationSetAttributes, parseAttributes(representation));
const representationSegmentInfo = getSegmentInformation(representation);
return repBaseUrls.map(baseUrl => {
return {
segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
attributes: merge(attributes, { baseUrl })
};
});
};
/**
* Tranforms a series of content protection nodes to
* an object containing pssh data by key system
*
* @param {Node[]} contentProtectionNodes
* Content protection nodes
* @return {Object}
* Object containing pssh data by key system
*/
const generateKeySystemInformation = (contentProtectionNodes) => {
return contentProtectionNodes.reduce((acc, node) => {
const attributes = parseAttributes(node);
const keySystem = keySystemsMap[attributes.schemeIdUri];
if (keySystem) {
acc[keySystem] = { attributes };
const psshNode = findChildren(node, 'cenc:pssh')[0];
if (psshNode) {
const pssh = getContent(psshNode);
const psshBuffer = pssh && decodeB64ToUint8Array(pssh);
acc[keySystem].pssh = psshBuffer;
}
}
return acc;
}, {});
};
// defined in ANSI_SCTE 214-1 2016
export const parseCaptionServiceMetadata = (service) => {
// 608 captions
if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
const values = service.value.split(';');
return values.map((value) => {
let channel;
let language;
// default language to value
language = value;
if (/^CC\d=/.test(value)) {
[channel, language] = value.split('=');
} else if (/^CC\d$/.test(value)) {
channel = value;
}
return {channel, language};
});
} else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
const values = service.value.split(';');
return values.map((value) => {
const flags = {
// service or channel number 1-63
'channel': undefined,
// language is a 3ALPHA per ISO 639.2/B
// field is required
'language': undefined,
// BIT 1/0 or ?
// default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
'aspectRatio': 1,
// BIT 1/0
// easy reader flag indicated the text is tailed to the needs of beginning readers
// default 0, or off
'easyReader': 0,
// BIT 1/0
// If 3d metadata is present (CEA-708.1) then 1
// default 0
'3D': 0
};
if (/=/.test(value)) {
const [channel, opts = ''] = value.split('=');
flags.channel = channel;
flags.language = value;
opts.split(',').forEach((opt) => {
const [name, val] = opt.split(':');
if (name === 'lang') {
flags.language = val;
// er for easyReadery
} else if (name === 'er') {
flags.easyReader = Number(val);
// war for wide aspect ratio
} else if (name === 'war') {
flags.aspectRatio = Number(val);
} else if (name === '3D') {
flags['3D'] = Number(val);
}
});
} else {
flags.language = value;
}
if (flags.channel) {
flags.channel = 'SERVICE' + flags.channel;
}
return flags;
});
}
};
/**
* Maps an AdaptationSet node to a list of Representation information objects
*
* @name toRepresentationsCallback
* @function
* @param {Node} adaptationSet
* AdaptationSet node from the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/
/**
* Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
* Representation information objects
*
* @param {Object} periodAttributes
* Contains attributes inherited by the Period
* @param {string[]} periodBaseUrls
* Contains list of resolved base urls inherited by the Period
* @param {string[]} periodSegmentInfo
* Contains Segment Information at the period level
* @return {toRepresentationsCallback}
* Callback map function
*/
export const toRepresentations =
(periodAttributes, periodBaseUrls, periodSegmentInfo) => (adaptationSet) => {
const adaptationSetAttributes = parseAttributes(adaptationSet);
const adaptationSetBaseUrls = buildBaseUrls(
periodBaseUrls,
findChildren(adaptationSet, 'BaseURL')
);
const role = findChildren(adaptationSet, 'Role')[0];
const roleAttributes = { role: parseAttributes(role) };
let attrs = merge(
periodAttributes,
adaptationSetAttributes,
roleAttributes,
);
const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
if (captionServices) {
attrs = merge(attrs, { captionServices });
}
const label = findChildren(adaptationSet, 'Label')[0];
if (label && label.childNodes.length) {
const labelVal = label.childNodes[0].nodeValue.trim();
attrs = merge(attrs, { label: labelVal });
}
const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
if (Object.keys(contentProtection).length) {
attrs = merge(attrs, { contentProtection });
}
const segmentInfo = getSegmentInformation(adaptationSet);
const representations = findChildren(adaptationSet, 'Representation');
const adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
};
/**
* Maps an Period node to a list of Representation inforamtion objects for all
* AdaptationSet nodes contained within the Period
*
* @name toAdaptationSetsCallback
* @function
* @param {Node} period
* Period node from the mpd
* @param {number} periodIndex
* Index of the Period within the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/
/**
* Returns a callback for Array.prototype.map for mapping Period nodes to a list of
* Representation information objects
*
* @param {Object} mpdAttributes
* Contains attributes inherited by the mpd
* @param {string[]} mpdBaseUrls
* Contains list of resolved base urls inherited by the mpd
* @return {toAdaptationSetsCallback}
* Callback map function
*/
export const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
const periodAtt = parseAttributes(period);
const parsedPeriodId = parseInt(periodAtt.id, 10);
// fallback to mapping index if Period@id is not a number
const periodIndex = window.isNaN(parsedPeriodId) ? index : parsedPeriodId;
const periodAttributes = merge(mpdAttributes, { periodIndex });
const adaptationSets = findChildren(period, 'AdaptationSet');
const periodSegmentInfo = getSegmentInformation(period);
return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
};
/**
* Traverses the mpd xml tree to generate a list of Representation information objects
* that have inherited attributes from parent nodes
*
* @param {Node} mpd
* The root node of the mpd
* @param {Object} options
* Available options for inheritAttributes
* @param {string} options.manifestUri
* The uri source of the mpd
* @param {number} options.NOW
* Current time per DASH IOP. Default is current time in ms since epoch
* @param {number} options.clientOffset
* Client time difference from NOW (in milliseconds)
* @return {RepresentationInformation[]}
* List of objects containing Representation information
*/
export const inheritAttributes = (mpd, options = {}) => {
const {
manifestUri = '',
NOW = Date.now(),
clientOffset = 0
} = options;
const periods = findChildren(mpd, 'Period');
if (!periods.length) {
throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
}
const locations = findChildren(mpd, 'Location');
const mpdAttributes = parseAttributes(mpd);
const mpdBaseUrls = buildBaseUrls([ manifestUri ], findChildren(mpd, 'BaseURL'));
mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
mpdAttributes.NOW = NOW;
mpdAttributes.clientOffset = clientOffset;
if (locations.length) {
mpdAttributes.locations = locations.map(getContent);
}
return {
locations: mpdAttributes.locations,
representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)))
};
};

267
node_modules/mpd-parser/src/parseAttributes.js generated vendored Normal file
View file

@ -0,0 +1,267 @@
import { from } from './utils/list';
import { parseDuration, parseDate } from './utils/time';
// TODO: maybe order these in some way that makes it easy to find specific attributes
export const parsers = {
/**
* Specifies the duration of the entire Media Presentation. Format is a duration string
* as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
mediaPresentationDuration(value) {
return parseDuration(value);
},
/**
* Specifies the Segment availability start time for all Segments referred to in this
* MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
* time. Format is a date string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The date as seconds from unix epoch
*/
availabilityStartTime(value) {
return parseDate(value) / 1000;
},
/**
* Specifies the smallest period between potential changes to the MPD. Format is a
* duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
minimumUpdatePeriod(value) {
return parseDuration(value);
},
/**
* Specifies the suggested presentation delay. Format is a
* duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
suggestedPresentationDelay(value) {
return parseDuration(value);
},
/**
* specifices the type of mpd. Can be either "static" or "dynamic"
*
* @param {string} value
* value of attribute as a string
*
* @return {string}
* The type as a string
*/
type(value) {
return value;
},
/**
* Specifies the duration of the smallest time shifting buffer for any Representation
* in the MPD. Format is a duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
timeShiftBufferDepth(value) {
return parseDuration(value);
},
/**
* Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
* Format is a duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
start(value) {
return parseDuration(value);
},
/**
* Specifies the width of the visual presentation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed width
*/
width(value) {
return parseInt(value, 10);
},
/**
* Specifies the height of the visual presentation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed height
*/
height(value) {
return parseInt(value, 10);
},
/**
* Specifies the bitrate of the representation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed bandwidth
*/
bandwidth(value) {
return parseInt(value, 10);
},
/**
* Specifies the number of the first Media Segment in this Representation in the Period
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed number
*/
startNumber(value) {
return parseInt(value, 10);
},
/**
* Specifies the timescale in units per seconds
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed timescale
*/
timescale(value) {
return parseInt(value, 10);
},
/**
* Specifies the presentationTimeOffset.
*
* @param {string} value
* value of the attribute as a string
*
* @return {number}
* The parsed presentationTimeOffset
*/
presentationTimeOffset(value) {
return parseInt(value, 10);
},
/**
* Specifies the constant approximate Segment duration
* NOTE: The <Period> element also contains an @duration attribute. This duration
* specifies the duration of the Period. This attribute is currently not
* supported by the rest of the parser, however we still check for it to prevent
* errors.
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed duration
*/
duration(value) {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
return parseDuration(value);
}
return parsedValue;
},
/**
* Specifies the Segment duration, in units of the value of the @timescale.
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed duration
*/
d(value) {
return parseInt(value, 10);
},
/**
* Specifies the MPD start time, in @timescale units, the first Segment in the series
* starts relative to the beginning of the Period
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed time
*/
t(value) {
return parseInt(value, 10);
},
/**
* Specifies the repeat count of the number of following contiguous Segments with the
* same duration expressed by the value of @d
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed number
*/
r(value) {
return parseInt(value, 10);
},
/**
* Default parser for all other attributes. Acts as a no-op and just returns the value
* as a string
*
* @param {string} value
* value of attribute as a string
* @return {string}
* Unparsed value
*/
DEFAULT(value) {
return value;
}
};
/**
* Gets all the attributes and values of the provided node, parses attributes with known
* types, and returns an object with attribute names mapped to values.
*
* @param {Node} el
* The node to parse attributes from
* @return {Object}
* Object with all attributes of el parsed
*/
export const parseAttributes = (el) => {
if (!(el && el.attributes)) {
return {};
}
return from(el.attributes)
.reduce((a, e) => {
const parseFn = parsers[e.name] || parsers.DEFAULT;
a[e.name] = parseFn(e.value);
return a;
}, {});
};

46
node_modules/mpd-parser/src/parseUTCTimingScheme.js generated vendored Normal file
View file

@ -0,0 +1,46 @@
import { findChildren } from './utils/xml';
import { parseAttributes } from './parseAttributes';
import errors from './errors';
/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
*
* @param {string} mpd
* XML string of the MPD manifest
* @return {Object|null}
* Attributes of UTCTiming node specified in the manifest. Null if none found
*/
export const parseUTCTimingScheme = (mpd) => {
const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
if (!UTCTimingNode) {
return null;
}
const attributes = parseAttributes(UTCTimingNode);
switch (attributes.schemeIdUri) {
case 'urn:mpeg:dash:utc:http-head:2014':
case 'urn:mpeg:dash:utc:http-head:2012':
attributes.method = 'HEAD';
break;
case 'urn:mpeg:dash:utc:http-xsdate:2014':
case 'urn:mpeg:dash:utc:http-iso:2014':
case 'urn:mpeg:dash:utc:http-xsdate:2012':
case 'urn:mpeg:dash:utc:http-iso:2012':
attributes.method = 'GET';
break;
case 'urn:mpeg:dash:utc:direct:2014':
case 'urn:mpeg:dash:utc:direct:2012':
attributes.method = 'DIRECT';
attributes.value = Date.parse(attributes.value);
break;
case 'urn:mpeg:dash:utc:http-ntp:2014':
case 'urn:mpeg:dash:utc:ntp:2014':
case 'urn:mpeg:dash:utc:sntp:2014':
default:
throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
}
return attributes;
};

View file

@ -0,0 +1,156 @@
import { range } from '../utils/list';
/**
* parse the end number attribue that can be a string
* number, or undefined.
*
* @param {string|number|undefined} endNumber
* The end number attribute.
*
* @return {number|null}
* The result of parsing the end number.
*/
const parseEndNumber = (endNumber) => {
if (endNumber && typeof endNumber !== 'number') {
endNumber = parseInt(endNumber, 10);
}
if (isNaN(endNumber)) {
return null;
}
return endNumber;
};
/**
* Functions for calculating the range of available segments in static and dynamic
* manifests.
*/
export const segmentRange = {
/**
* Returns the entire range of available segments for a static MPD
*
* @param {Object} attributes
* Inheritied MPD attributes
* @return {{ start: number, end: number }}
* The start and end numbers for available segments
*/
static(attributes) {
const {
duration,
timescale = 1,
sourceDuration
} = attributes;
const endNumber = parseEndNumber(attributes.endNumber);
return {
start: 0,
end: typeof endNumber === 'number' ? endNumber : Math.ceil(sourceDuration / (duration / timescale))
};
},
/**
* Returns the current live window range of available segments for a dynamic MPD
*
* @param {Object} attributes
* Inheritied MPD attributes
* @return {{ start: number, end: number }}
* The start and end numbers for available segments
*/
dynamic(attributes) {
const {
NOW,
clientOffset,
availabilityStartTime,
timescale = 1,
duration,
start = 0,
minimumUpdatePeriod = 0,
timeShiftBufferDepth = Infinity
} = attributes;
const endNumber = parseEndNumber(attributes.endNumber);
const now = (NOW + clientOffset) / 1000;
const periodStartWC = availabilityStartTime + start;
const periodEndWC = now + minimumUpdatePeriod;
const periodDuration = periodEndWC - periodStartWC;
const segmentCount = Math.ceil(periodDuration * timescale / duration);
const availableStart =
Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
return {
start: Math.max(0, availableStart),
end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
};
}
};
/**
* Maps a range of numbers to objects with information needed to build the corresponding
* segment list
*
* @name toSegmentsCallback
* @function
* @param {number} number
* Number of the segment
* @param {number} index
* Index of the number in the range list
* @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
* Object with segment timing and duration info
*/
/**
* Returns a callback for Array.prototype.map for mapping a range of numbers to
* information needed to build the segment list.
*
* @param {Object} attributes
* Inherited MPD attributes
* @return {toSegmentsCallback}
* Callback map function
*/
export const toSegments = (attributes) => (number, index) => {
const {
duration,
timescale = 1,
periodIndex,
startNumber = 1
} = attributes;
return {
number: startNumber + number,
duration: duration / timescale,
timeline: periodIndex,
time: index * duration
};
};
/**
* Returns a list of objects containing segment timing and duration info used for
* building the list of segments. This uses the @duration attribute specified
* in the MPD manifest to derive the range of segments.
*
* @param {Object} attributes
* Inherited MPD attributes
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
export const parseByDuration = (attributes) => {
const {
type = 'static',
duration,
timescale = 1,
sourceDuration
} = attributes;
const { start, end } = segmentRange[type](attributes);
const segments = range(start, end).map(toSegments(attributes));
if (type === 'static') {
const index = segments.length - 1;
// final segment may be less than full segment duration
segments[index].duration = sourceDuration - (duration / timescale * index);
}
return segments;
};

123
node_modules/mpd-parser/src/segment/segmentBase.js generated vendored Normal file
View file

@ -0,0 +1,123 @@
import errors from '../errors';
import urlTypeConverter from './urlType';
import { parseByDuration } from './durationTimeParser';
/**
* Translates SegmentBase into a set of segments.
* (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @return {Object.<Array>} list of segments
*/
export const segmentsFromBase = (attributes) => {
const {
baseUrl,
initialization = {},
sourceDuration,
indexRange = '',
duration
} = attributes;
// base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
if (!baseUrl) {
throw new Error(errors.NO_BASE_URL);
}
const initSegment = urlTypeConverter({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeConverter({ baseUrl, source: baseUrl, indexRange });
segment.map = initSegment;
// If there is a duration, use it, otherwise use the given duration of the source
// (since SegmentBase is only for one total segment)
if (duration) {
const segmentTimeInfo = parseByDuration(attributes);
if (segmentTimeInfo.length) {
segment.duration = segmentTimeInfo[0].duration;
segment.timeline = segmentTimeInfo[0].timeline;
}
} else if (sourceDuration) {
segment.duration = sourceDuration;
segment.timeline = 0;
}
// This is used for mediaSequence
segment.number = 0;
return [segment];
};
/**
* Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
* according to the sidx information given.
*
* playlist.sidx has metadadata about the sidx where-as the sidx param
* is the parsed sidx box itself.
*
* @param {Object} playlist the playlist to update the sidx information for
* @param {Object} sidx the parsed sidx box
* @return {Object} the playlist object with the updated sidx information
*/
export const addSidxSegmentsToPlaylist = (playlist, sidx, baseUrl) => {
// Retain init segment information
const initSegment = playlist.sidx.map ? playlist.sidx.map : null;
// Retain source duration from initial master manifest parsing
const sourceDuration = playlist.sidx.duration;
// Retain source timeline
const timeline = playlist.timeline || 0;
const sidxByteRange = playlist.sidx.byterange;
const sidxEnd = sidxByteRange.offset + sidxByteRange.length;
// Retain timescale of the parsed sidx
const timescale = sidx.timescale;
// referenceType 1 refers to other sidx boxes
const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
const segments = [];
// firstOffset is the offset from the end of the sidx box
let startIndex = sidxEnd + sidx.firstOffset;
for (let i = 0; i < mediaReferences.length; i++) {
const reference = sidx.references[i];
// size of the referenced (sub)segment
const size = reference.referencedSize;
// duration of the referenced (sub)segment, in the timescale
// this will be converted to seconds when generating segments
const duration = reference.subsegmentDuration;
// should be an inclusive range
const endIndex = startIndex + size - 1;
const indexRange = `${startIndex}-${endIndex}`;
const attributes = {
baseUrl,
timescale,
timeline,
// this is used in parseByDuration
periodIndex: timeline,
duration,
sourceDuration,
indexRange
};
const segment = segmentsFromBase(attributes)[0];
if (initSegment) {
segment.map = initSegment;
}
segments.push(segment);
startIndex += size;
}
playlist.segments = segments;
return playlist;
};

90
node_modules/mpd-parser/src/segment/segmentList.js generated vendored Normal file
View file

@ -0,0 +1,90 @@
import { parseByTimeline } from './timelineTimeParser';
import { parseByDuration } from './durationTimeParser';
import urlTypeConverter from './urlType';
import errors from '../errors';
/**
* Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
* to an object that matches the output of a segment in videojs/mpd-parser
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object} segmentUrl
* <SegmentURL> node to translate into a segment object
* @return {Object} translated segment object
*/
const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
const { baseUrl, initialization = {} } = attributes;
const initSegment = urlTypeConverter({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeConverter({
baseUrl,
source: segmentUrl.media,
range: segmentUrl.mediaRange
});
segment.map = initSegment;
return segment;
};
/**
* Generates a list of segments using information provided by the SegmentList element
* SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object.<Array>} list of segments
*/
export const segmentsFromList = (attributes, segmentTimeline) => {
const {
duration,
segmentUrls = []
} = attributes;
// Per spec (5.3.9.2.1) no way to determine segment duration OR
// if both SegmentTimeline and @duration are defined, it is outside of spec.
if ((!duration && !segmentTimeline) ||
(duration && segmentTimeline)) {
throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
}
const segmentUrlMap = segmentUrls.map(segmentUrlObject =>
SegmentURLToSegmentObject(attributes, segmentUrlObject));
let segmentTimeInfo;
if (duration) {
segmentTimeInfo = parseByDuration(attributes);
}
if (segmentTimeline) {
segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
}
const segments = segmentTimeInfo.map((segmentTime, index) => {
if (segmentUrlMap[index]) {
const segment = segmentUrlMap[index];
segment.timeline = segmentTime.timeline;
segment.duration = segmentTime.duration;
segment.number = segmentTime.number;
return segment;
}
// Since we're mapping we should get rid of any blank segments (in case
// the given SegmentTimeline is handling for more elements than we have
// SegmentURLs for).
}).filter(segment => segment);
return segments;
};

176
node_modules/mpd-parser/src/segment/segmentTemplate.js generated vendored Normal file
View file

@ -0,0 +1,176 @@
import resolveUrl from '@videojs/vhs-utils/es/resolve-url';
import urlTypeToSegment from './urlType';
import { parseByTimeline } from './timelineTimeParser';
import { parseByDuration } from './durationTimeParser';
const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
/**
* Replaces template identifiers with corresponding values. To be used as the callback
* for String.prototype.replace
*
* @name replaceCallback
* @function
* @param {string} match
* Entire match of identifier
* @param {string} identifier
* Name of matched identifier
* @param {string} format
* Format tag string. Its presence indicates that padding is expected
* @param {string} width
* Desired length of the replaced value. Values less than this width shall be left
* zero padded
* @return {string}
* Replacement for the matched identifier
*/
/**
* Returns a function to be used as a callback for String.prototype.replace to replace
* template identifiers
*
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {replaceCallback}
* Callback to be used with String.prototype.replace to replace identifiers
*/
export const identifierReplacement = (values) => (match, identifier, format, width) => {
if (match === '$$') {
// escape sequence
return '$';
}
if (typeof values[identifier] === 'undefined') {
return match;
}
const value = '' + values[identifier];
if (identifier === 'RepresentationID') {
// Format tag shall not be present with RepresentationID
return value;
}
if (!format) {
width = 1;
} else {
width = parseInt(width, 10);
}
if (value.length >= width) {
return value;
}
return `${(new Array(width - value.length + 1)).join('0')}${value}`;
};
/**
* Constructs a segment url from a template string
*
* @param {string} url
* Template string to construct url from
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {string}
* Segment url with identifiers replaced
*/
export const constructTemplateUrl = (url, values) =>
url.replace(identifierPattern, identifierReplacement(values));
/**
* Generates a list of objects containing timing and duration information about each
* segment needed to generate segment uris and the complete segment object
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
export const parseTemplateInfo = (attributes, segmentTimeline) => {
if (!attributes.duration && !segmentTimeline) {
// if neither @duration or SegmentTimeline are present, then there shall be exactly
// one media segment
return [{
number: attributes.startNumber || 1,
duration: attributes.sourceDuration,
time: 0,
timeline: attributes.periodIndex
}];
}
if (attributes.duration) {
return parseByDuration(attributes);
}
return parseByTimeline(attributes, segmentTimeline);
};
/**
* Generates a list of segments using information provided by the SegmentTemplate element
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object[]}
* List of segment objects
*/
export const segmentsFromTemplate = (attributes, segmentTimeline) => {
const templateValues = {
RepresentationID: attributes.id,
Bandwidth: attributes.bandwidth || 0
};
const { initialization = { sourceURL: '', range: '' } } = attributes;
const mapSegment = urlTypeToSegment({
baseUrl: attributes.baseUrl,
source: constructTemplateUrl(initialization.sourceURL, templateValues),
range: initialization.range
});
const segments = parseTemplateInfo(attributes, segmentTimeline);
return segments.map(segment => {
templateValues.Number = segment.number;
templateValues.Time = segment.time;
const uri = constructTemplateUrl(attributes.media || '', templateValues);
const map = {
uri,
timeline: segment.timeline,
duration: segment.duration,
resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
map: mapSegment,
number: segment.number
};
if (attributes.presentationTimeOffset) {
map.presentationTimeOffset = attributes.presentationTimeOffset;
}
return map;
});
};

View file

@ -0,0 +1,129 @@
/**
* Calculates the R (repetition) value for a live stream (for the final segment
* in a manifest where the r value is negative 1)
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {number} time
* current time (typically the total time up until the final segment)
* @param {number} duration
* duration property for the given <S />
*
* @return {number}
* R value to reach the end of the given period
*/
const getLiveRValue = (attributes, time, duration) => {
const {
NOW,
clientOffset,
availabilityStartTime,
timescale = 1,
start = 0,
minimumUpdatePeriod = 0
} = attributes;
const now = (NOW + clientOffset) / 1000;
const periodStartWC = availabilityStartTime + start;
const periodEndWC = now + minimumUpdatePeriod;
const periodDuration = periodEndWC - periodStartWC;
return Math.ceil(((periodDuration * timescale) - time) / duration);
};
/**
* Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
* timing and duration
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]} segmentTimeline
* List of objects representing the attributes of each S element contained within
*
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
export const parseByTimeline = (attributes, segmentTimeline) => {
const {
type = 'static',
minimumUpdatePeriod = 0,
media = '',
sourceDuration,
timescale = 1,
startNumber = 1,
periodIndex: timeline
} = attributes;
const segments = [];
let time = -1;
for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
const S = segmentTimeline[sIndex];
const duration = S.d;
const repeat = S.r || 0;
const segmentTime = S.t || 0;
if (time < 0) {
// first segment
time = segmentTime;
}
if (segmentTime && segmentTime > time) {
// discontinuity
// TODO: How to handle this type of discontinuity
// timeline++ here would treat it like HLS discontuity and content would
// get appended without gap
// E.G.
// <S t="0" d="1" />
// <S d="1" />
// <S d="1" />
// <S t="5" d="1" />
// would have $Time$ values of [0, 1, 2, 5]
// should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
// or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
// does the value of sourceDuration consider this when calculating arbitrary
// negative @r repeat value?
// E.G. Same elements as above with this added at the end
// <S d="1" r="-1" />
// with a sourceDuration of 10
// Would the 2 gaps be included in the time duration calculations resulting in
// 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
// with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
time = segmentTime;
}
let count;
if (repeat < 0) {
const nextS = sIndex + 1;
if (nextS === segmentTimeline.length) {
// last segment
if (type === 'dynamic' &&
minimumUpdatePeriod > 0 &&
media.indexOf('$Number$') > 0) {
count = getLiveRValue(attributes, time, duration);
} else {
// TODO: This may be incorrect depending on conclusion of TODO above
count = ((sourceDuration * timescale) - time) / duration;
}
} else {
count = (segmentTimeline[nextS].t - time) / duration;
}
} else {
count = repeat + 1;
}
const end = startNumber + segments.length + count;
let number = startNumber + segments.length;
while (number < end) {
segments.push({ number, duration: duration / timescale, time, timeline });
time += duration;
number++;
}
}
return segments;
};

59
node_modules/mpd-parser/src/segment/urlType.js generated vendored Normal file
View file

@ -0,0 +1,59 @@
import resolveUrl from '@videojs/vhs-utils/es/resolve-url';
/**
* @typedef {Object} SingleUri
* @property {string} uri - relative location of segment
* @property {string} resolvedUri - resolved location of segment
* @property {Object} byterange - Object containing information on how to make byte range
* requests following byte-range-spec per RFC2616.
* @property {String} byterange.length - length of range request
* @property {String} byterange.offset - byte offset of range request
*
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
*/
/**
* Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
* that conforms to how m3u8-parser is structured
*
* @see https://github.com/videojs/m3u8-parser
*
* @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
* @param {string} source - source url for segment
* @param {string} range - optional range used for range calls,
* follows RFC 2616, Clause 14.35.1
* @return {SingleUri} full segment information transformed into a format similar
* to m3u8-parser
*/
export const urlTypeToSegment = ({ baseUrl = '', source = '', range = '', indexRange = '' }) => {
const segment = {
uri: source,
resolvedUri: resolveUrl(baseUrl || '', source)
};
if (range || indexRange) {
const rangeStr = range ? range : indexRange;
const ranges = rangeStr.split('-');
const startRange = parseInt(ranges[0], 10);
const endRange = parseInt(ranges[1], 10);
// byterange should be inclusive according to
// RFC 2616, Clause 14.35.1
segment.byterange = {
length: endRange - startRange + 1,
offset: startRange
};
}
return segment;
};
export const byteRangeToString = (byterange) => {
// `endRange` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
const endRange = byterange.offset + byterange.length - 1;
return `${byterange.offset}-${endRange}`;
};
export default urlTypeToSegment;

27
node_modules/mpd-parser/src/stringToMpdXml.js generated vendored Normal file
View file

@ -0,0 +1,27 @@
import {DOMParser} from 'xmldom';
import errors from './errors';
export const stringToMpdXml = (manifestString) => {
if (manifestString === '') {
throw new Error(errors.DASH_EMPTY_MANIFEST);
}
const parser = new DOMParser();
let xml;
let mpd;
try {
xml = parser.parseFromString(manifestString, 'application/xml');
mpd = xml && xml.documentElement.tagName === 'MPD' ?
xml.documentElement : null;
} catch (e) {
// ie 11 throwsw on invalid xml
}
if (!mpd || mpd &&
mpd.getElementsByTagName('parsererror').length > 0) {
throw new Error(errors.DASH_INVALID_XML);
}
return mpd;
};

339
node_modules/mpd-parser/src/toM3u8.js generated vendored Normal file
View file

@ -0,0 +1,339 @@
import { values } from './utils/object';
import { findIndexes } from './utils/list';
import { addSidxSegmentsToPlaylist as addSidxSegmentsToPlaylist_ } from './segment/segmentBase';
import { byteRangeToString } from './segment/urlType';
export const generateSidxKey = (sidx) => sidx &&
sidx.uri + '-' + byteRangeToString(sidx.byterange);
const mergeDiscontiguousPlaylists = playlists => {
const mergedPlaylists = values(playlists.reduce((acc, playlist) => {
// assuming playlist IDs are the same across periods
// TODO: handle multiperiod where representation sets are not the same
// across periods
const name = playlist.attributes.id + (playlist.attributes.lang || '');
// Periods after first
if (acc[name]) {
// first segment of subsequent periods signal a discontinuity
if (playlist.segments[0]) {
playlist.segments[0].discontinuity = true;
}
acc[name].segments.push(...playlist.segments);
// bubble up contentProtection, this assumes all DRM content
// has the same contentProtection
if (playlist.attributes.contentProtection) {
acc[name].attributes.contentProtection =
playlist.attributes.contentProtection;
}
} else {
// first Period
acc[name] = playlist;
}
return acc;
}, {}));
return mergedPlaylists.map(playlist => {
playlist.discontinuityStarts =
findIndexes(playlist.segments, 'discontinuity');
return playlist;
});
};
export const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
const sidxKey = generateSidxKey(playlist.sidx);
const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
if (sidxMatch) {
addSidxSegmentsToPlaylist_(playlist, sidxMatch, playlist.sidx.resolvedUri);
}
return playlist;
};
export const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
if (!Object.keys(sidxMapping).length) {
return playlists;
}
for (const i in playlists) {
playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
}
return playlists;
};
export const formatAudioPlaylist = ({ attributes, segments, sidx }, isAudioOnly) => {
const playlist = {
attributes: {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
CODECS: attributes.codecs,
['PROGRAM-ID']: 1
},
uri: '',
endList: (attributes.type || 'static') === 'static',
timeline: attributes.periodIndex,
resolvedUri: '',
targetDuration: attributes.duration,
segments,
mediaSequence: segments.length ? segments[0].number : 1
};
if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}
if (sidx) {
playlist.sidx = sidx;
}
if (isAudioOnly) {
playlist.attributes.AUDIO = 'audio';
playlist.attributes.SUBTITLES = 'subs';
}
return playlist;
};
export const formatVttPlaylist = ({ attributes, segments }) => {
if (typeof segments === 'undefined') {
// vtt tracks may use single file in BaseURL
segments = [{
uri: attributes.baseUrl,
timeline: attributes.periodIndex,
resolvedUri: attributes.baseUrl || '',
duration: attributes.sourceDuration,
number: 0
}];
// targetDuration should be the same duration as the only segment
attributes.duration = attributes.sourceDuration;
}
const m3u8Attributes = {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
['PROGRAM-ID']: 1
};
if (attributes.codecs) {
m3u8Attributes.CODECS = attributes.codecs;
}
return {
attributes: m3u8Attributes,
uri: '',
endList: (attributes.type || 'static') === 'static',
timeline: attributes.periodIndex,
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
segments,
mediaSequence: segments.length ? segments[0].number : 1
};
};
export const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
let mainPlaylist;
const formattedPlaylists = playlists.reduce((a, playlist) => {
const role = playlist.attributes.role &&
playlist.attributes.role.value || '';
const language = playlist.attributes.lang || '';
let label = playlist.attributes.label || 'main';
if (language && !playlist.attributes.label) {
const roleLabel = role ? ` (${role})` : '';
label = `${playlist.attributes.lang}${roleLabel}`;
}
if (!a[label]) {
a[label] = {
language,
autoselect: true,
default: role === 'main',
playlists: [],
uri: ''
};
}
const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
a[label].playlists.push(formatted);
if (typeof mainPlaylist === 'undefined' && role === 'main') {
mainPlaylist = playlist;
mainPlaylist.default = true;
}
return a;
}, {});
// if no playlists have role "main", mark the first as main
if (!mainPlaylist) {
const firstLabel = Object.keys(formattedPlaylists)[0];
formattedPlaylists[firstLabel].default = true;
}
return formattedPlaylists;
};
export const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
return playlists.reduce((a, playlist) => {
const label = playlist.attributes.lang || 'text';
if (!a[label]) {
a[label] = {
language: label,
default: false,
autoselect: false,
playlists: [],
uri: ''
};
}
a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
return a;
}, {});
};
const organizeCaptionServices = (captionServices) => captionServices.reduce((svcObj, svc) => {
if (!svc) {
return svcObj;
}
svc.forEach((service) => {
const {
channel,
language
} = service;
svcObj[language] = {
autoselect: false,
default: false,
instreamId: channel,
language
};
if (service.hasOwnProperty('aspectRatio')) {
svcObj[language].aspectRatio = service.aspectRatio;
}
if (service.hasOwnProperty('easyReader')) {
svcObj[language].easyReader = service.easyReader;
}
if (service.hasOwnProperty('3D')) {
svcObj[language]['3D'] = service['3D'];
}
});
return svcObj;
}, {});
export const formatVideoPlaylist = ({ attributes, segments, sidx }) => {
const playlist = {
attributes: {
NAME: attributes.id,
AUDIO: 'audio',
SUBTITLES: 'subs',
RESOLUTION: {
width: attributes.width,
height: attributes.height
},
CODECS: attributes.codecs,
BANDWIDTH: attributes.bandwidth,
['PROGRAM-ID']: 1
},
uri: '',
endList: (attributes.type || 'static') === 'static',
timeline: attributes.periodIndex,
resolvedUri: '',
targetDuration: attributes.duration,
segments,
mediaSequence: segments.length ? segments[0].number : 1
};
if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}
if (sidx) {
playlist.sidx = sidx;
}
return playlist;
};
const videoOnly = ({ attributes }) =>
attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
const audioOnly = ({ attributes }) =>
attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
const vttOnly = ({ attributes }) =>
attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
export const toM3u8 = (dashPlaylists, locations, sidxMapping = {}) => {
if (!dashPlaylists.length) {
return {};
}
// grab all master attributes
const {
sourceDuration: duration,
type = 'static',
suggestedPresentationDelay,
minimumUpdatePeriod
} = dashPlaylists[0].attributes;
const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
const vttPlaylists = dashPlaylists.filter(vttOnly);
const captions = dashPlaylists.map((playlist) => playlist.attributes.captionServices).filter(Boolean);
const master = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
AUDIO: {},
VIDEO: {},
['CLOSED-CAPTIONS']: {},
SUBTITLES: {}
},
uri: '',
duration,
playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
};
if (minimumUpdatePeriod >= 0) {
master.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
}
if (locations) {
master.locations = locations;
}
if (type === 'dynamic') {
master.suggestedPresentationDelay = suggestedPresentationDelay;
}
const isAudioOnly = master.playlists.length === 0;
if (audioPlaylists.length) {
master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly);
}
if (vttPlaylists.length) {
master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
}
if (captions.length) {
master.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
}
return master;
};

66
node_modules/mpd-parser/src/toPlaylists.js generated vendored Normal file
View file

@ -0,0 +1,66 @@
import { merge } from './utils/object';
import { segmentsFromTemplate } from './segment/segmentTemplate';
import { segmentsFromList } from './segment/segmentList';
import { segmentsFromBase } from './segment/segmentBase';
export const generateSegments = ({ attributes, segmentInfo }) => {
let segmentAttributes;
let segmentsFn;
if (segmentInfo.template) {
segmentsFn = segmentsFromTemplate;
segmentAttributes = merge(attributes, segmentInfo.template);
if (segmentInfo.template.presentationTimeOffset) {
segmentAttributes.presentationTimeOffset =
segmentInfo.template.presentationTimeOffset / segmentInfo.template.timescale;
}
} else if (segmentInfo.base) {
segmentsFn = segmentsFromBase;
segmentAttributes = merge(attributes, segmentInfo.base);
} else if (segmentInfo.list) {
segmentsFn = segmentsFromList;
segmentAttributes = merge(attributes, segmentInfo.list);
}
const segmentsInfo = {
attributes
};
if (!segmentsFn) {
return segmentsInfo;
}
const segments = segmentsFn(segmentAttributes, segmentInfo.timeline);
// The @duration attribute will be used to determin the playlist's targetDuration which
// must be in seconds. Since we've generated the segment list, we no longer need
// @duration to be in @timescale units, so we can convert it here.
if (segmentAttributes.duration) {
const { duration, timescale = 1 } = segmentAttributes;
segmentAttributes.duration = duration / timescale;
} else if (segments.length) {
// if there is no @duration attribute, use the largest segment duration as
// as target duration
segmentAttributes.duration = segments.reduce((max, segment) => {
return Math.max(max, Math.ceil(segment.duration));
}, 0);
} else {
segmentAttributes.duration = 0;
}
segmentsInfo.attributes = segmentAttributes;
segmentsInfo.segments = segments;
// This is a sidx box without actual segment information
if (segmentInfo.base && segmentAttributes.indexRange) {
segmentsInfo.sidx = segments[0];
segmentsInfo.segments = [];
}
return segmentsInfo;
};
export const toPlaylists = (representations) => representations.map(generateSegments);

33
node_modules/mpd-parser/src/utils/list.js generated vendored Normal file
View file

@ -0,0 +1,33 @@
export const range = (start, end) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
export const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
export const from = list => {
if (!list.length) {
return [];
}
const result = [];
for (let i = 0; i < list.length; i++) {
result.push(list[i]);
}
return result;
};
export const findIndexes = (l, key) => l.reduce((a, e, i) => {
if (e[key]) {
a.push(i);
}
return a;
}, []);

27
node_modules/mpd-parser/src/utils/object.js generated vendored Normal file
View file

@ -0,0 +1,27 @@
const isObject = (obj) => {
return !!obj && typeof obj === 'object';
};
export const merge = (...objects) => {
return objects.reduce((result, source) => {
if (typeof source !== 'object') {
return result;
}
Object.keys(source).forEach(key => {
if (Array.isArray(result[key]) && Array.isArray(source[key])) {
result[key] = result[key].concat(source[key]);
} else if (isObject(result[key]) && isObject(source[key])) {
result[key] = merge(result[key], source[key]);
} else {
result[key] = source[key];
}
});
return result;
}, {});
};
export const values = o => Object.keys(o).map(k => o[k]);

39
node_modules/mpd-parser/src/utils/time.js generated vendored Normal file
View file

@ -0,0 +1,39 @@
export const parseDuration = (str) => {
const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
const SECONDS_IN_DAY = 24 * 60 * 60;
const SECONDS_IN_HOUR = 60 * 60;
const SECONDS_IN_MIN = 60;
// P10Y10M10DT10H10M10.1S
const durationRegex =
/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
const match = durationRegex.exec(str);
if (!match) {
return 0;
}
const [year, month, day, hour, minute, second] = match.slice(1);
return (parseFloat(year || 0) * SECONDS_IN_YEAR +
parseFloat(month || 0) * SECONDS_IN_MONTH +
parseFloat(day || 0) * SECONDS_IN_DAY +
parseFloat(hour || 0) * SECONDS_IN_HOUR +
parseFloat(minute || 0) * SECONDS_IN_MIN +
parseFloat(second || 0));
};
export const parseDate = (str) => {
// Date format without timezone according to ISO 8601
// YYY-MM-DDThh:mm:ss.ssssss
const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/;
// If the date string does not specifiy a timezone, we must specifiy UTC. This is
// expressed by ending with 'Z'
if (dateRegex.test(str)) {
str += 'Z';
}
return Date.parse(str);
};

6
node_modules/mpd-parser/src/utils/xml.js generated vendored Normal file
View file

@ -0,0 +1,6 @@
import { from } from './list';
export const findChildren = (element, name) =>
from(element.childNodes).filter(({tagName}) => tagName === name);
export const getContent = element => element.textContent.trim();

122
node_modules/mpd-parser/test/index.test.js generated vendored Normal file
View file

@ -0,0 +1,122 @@
import { parse, VERSION } from '../src';
import QUnit from 'qunit';
QUnit.dump.maxDepth = Infinity;
// manifests
import vttCodecsTemplate from './manifests/vtt_codecs.mpd';
import maatVttSegmentTemplate from './manifests/maat_vtt_segmentTemplate.mpd';
import segmentBaseTemplate from './manifests/segmentBase.mpd';
import segmentListTemplate from './manifests/segmentList.mpd';
import cc608CaptionsTemplate from './manifests/608-captions.mpd';
import cc708CaptionsTemplate from './manifests/708-captions.mpd';
import locationTemplate from './manifests/location.mpd';
import locationsTemplate from './manifests/locations.mpd';
import multiperiod from './manifests/multiperiod.mpd';
import webmsegments from './manifests/webmsegments.mpd';
import multiperiodDynamic from './manifests/multiperiod-dynamic.mpd';
import audioOnly from './manifests/audio-only.mpd';
import {
parsedManifest as maatVttSegmentTemplateManifest
} from './manifests/maat_vtt_segmentTemplate.js';
import {
parsedManifest as segmentBaseManifest
} from './manifests/segmentBase.js';
import {
parsedManifest as segmentListManifest
} from './manifests/segmentList.js';
import {
parsedManifest as cc608CaptionsManifest
} from './manifests/608-captions.js';
import {
parsedManifest as cc708CaptionsManifest
} from './manifests/708-captions.js';
import {
parsedManifest as multiperiodManifest
} from './manifests/multiperiod.js';
import {
parsedManifest as webmsegmentsManifest
} from './manifests/webmsegments.js';
import {
parsedManifest as multiperiodDynamicManifest
} from './manifests/multiperiod-dynamic.js';
import {
parsedManifest as locationManifest
} from './manifests/location.js';
import {
parsedManifest as locationsManifest
} from './manifests/locations.js';
import {
parsedManifest as vttCodecsManifest
} from './manifests/vtt_codecs.js';
import {
parsedManifest as audioOnlyManifest
} from './manifests/audio-only.js';
QUnit.module('mpd-parser');
QUnit.test('has VERSION', function(assert) {
assert.ok(VERSION);
});
QUnit.test('has parse', function(assert) {
assert.ok(parse);
});
[{
name: 'maat_vtt_segmentTemplate',
input: maatVttSegmentTemplate,
expected: maatVttSegmentTemplateManifest
}, {
name: 'segmentBase',
input: segmentBaseTemplate,
expected: segmentBaseManifest
}, {
name: 'segmentList',
input: segmentListTemplate,
expected: segmentListManifest
}, {
name: '608-captions',
input: cc608CaptionsTemplate,
expected: cc608CaptionsManifest
}, {
name: '708-captions',
input: cc708CaptionsTemplate,
expected: cc708CaptionsManifest
}, {
name: 'multiperiod',
input: multiperiod,
expected: multiperiodManifest
}, {
name: 'webmsegments',
input: webmsegments,
expected: webmsegmentsManifest
}, {
name: 'multiperiod_dynamic',
input: multiperiodDynamic,
expected: multiperiodDynamicManifest
}, {
name: 'location',
input: locationTemplate,
expected: locationManifest
}, {
name: 'locations',
input: locationsTemplate,
expected: locationsManifest
}, {
name: 'vtt_codecs',
input: vttCodecsTemplate,
expected: vttCodecsManifest
}, {
name: 'audio-only',
input: audioOnly,
expected: audioOnlyManifest
}].forEach(({ name, input, expected }) => {
QUnit.test(`${name} test manifest`, function(assert) {
const actual = parse(input);
assert.deepEqual(actual, expected);
});
});

1758
node_modules/mpd-parser/test/inheritAttributes.test.js generated vendored Normal file

File diff suppressed because it is too large Load diff

70
node_modules/mpd-parser/test/manifests/608-captions.js generated vendored Normal file
View file

@ -0,0 +1,70 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {
cc: {
eng: {
autoselect: false,
default: false,
instreamId: 'CC1',
language: 'eng'
},
swe: {
autoselect: false,
default: false,
instreamId: 'CC3',
language: 'swe'
},
Hello: {
autoselect: false,
default: false,
instreamId: 'CC4',
language: 'Hello'
}
}
},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
targetDuration: 6,
mediaSequence: 0,
segments: [
{
duration: 6,
timeline: 0,
number: 0,
map: {
uri: '',
resolvedUri: 'https://www.example.com/1080p.ts'
},
resolvedUri: 'https://www.example.com/1080p.ts',
uri: 'https://www.example.com/1080p.ts'
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Accessibility schemeIdUri="urn:scte:dash:cc:cea-608:2015" value="CC1=eng;CC3=swe;CC4=Hello"></Accessibility>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
<SegmentBase></SegmentBase>
</Representation>
<BaseURL>1080p.ts</BaseURL>
<SegmentBase>
<RepresentationIndex sourceURL="1080p.sidx"/>
</SegmentBase>
</AdaptationSet>
</Period>
</MPD>

71
node_modules/mpd-parser/test/manifests/708-captions.js generated vendored Normal file
View file

@ -0,0 +1,71 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {
cc: {
// eng: {
// autoselect: false,
// default: false,
// instreamId: '1',
// language: 'eng',
// aspectRatio: 1,
// easyReader: 0,
// '3D': 0
// },
// TODO only this one ends up being represented and not both
eng: {
'autoselect': false,
'default': false,
'instreamId': 'SERVICE2',
'language': 'eng',
'aspectRatio': 1,
'easyReader': 1,
'3D': 0
}
}
},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
targetDuration: 6,
mediaSequence: 0,
segments: [
{
duration: 6,
timeline: 0,
number: 0,
map: {
uri: '',
resolvedUri: 'https://www.example.com/1080p.ts'
},
resolvedUri: 'https://www.example.com/1080p.ts',
uri: 'https://www.example.com/1080p.ts'
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Accessibility schemeIdUri="urn:scte:dash:cc:cea-708:2015" value="1=lang:eng;2=lang:eng,war:1,er:1"></Accessibility>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
<SegmentBase></SegmentBase>
</Representation>
<BaseURL>1080p.ts</BaseURL>
<SegmentBase>
<RepresentationIndex sourceURL="1080p.sidx"/>
</SegmentBase>
</AdaptationSet>
</Period>
</MPD>

106
node_modules/mpd-parser/test/manifests/audio-only.js generated vendored Normal file
View file

@ -0,0 +1,106 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
en: {
language: 'en',
autoselect: true,
default: true,
playlists: [
{
attributes: {
'NAME': '0',
'BANDWIDTH': 130803,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1,
'AUDIO': 'audio',
'SUBTITLES': 'subs'
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 60,
segments: [],
mediaSequence: 1,
sidx: {
uri: 'http://example.com/audio_en_2c_128k_aac.mp4',
resolvedUri: 'http://example.com/audio_en_2c_128k_aac.mp4',
byterange: {
length: 224,
offset: 786
},
map: {
uri: '',
resolvedUri: 'http://example.com/audio_en_2c_128k_aac.mp4',
byterange: {
length: 786,
offset: 0
}
},
duration: 60,
timeline: 0,
number: 0
}
}
],
uri: ''
},
es: {
language: 'es',
autoselect: true,
default: false,
playlists: [
{
attributes: {
'NAME': '1',
'BANDWIDTH': 130405,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1,
'AUDIO': 'audio',
'SUBTITLES': 'subs'
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 60,
segments: [],
mediaSequence: 1,
sidx: {
uri: 'http://example.com/audio_es_2c_128k_aac.mp4',
resolvedUri: 'http://example.com/audio_es_2c_128k_aac.mp4',
byterange: {
length: 224,
offset: 786
},
map: {
uri: '',
resolvedUri: 'http://example.com/audio_es_2c_128k_aac.mp4',
byterange: {
length: 786,
offset: 0
}
},
duration: 60,
timeline: 0,
number: 0
}
}
],
uri: ''
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
uri: '',
duration: 60,
playlists: []
};

25
node_modules/mpd-parser/test/manifests/audio-only.mpd generated vendored Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version v2.4.1-c731217-release-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT60S">
<Period id="0">
<AdaptationSet id="0" contentType="audio" lang="en" subsegmentAlignment="true">
<Representation id="0" bandwidth="130803" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="48000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>http://example.com/audio_en_2c_128k_aac.mp4</BaseURL>
<SegmentBase indexRange="786-1009" timescale="48000">
<Initialization range="0-785"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" lang="es" subsegmentAlignment="true">
<Representation id="1" bandwidth="130405" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="48000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>http://example.com/audio_es_2c_128k_aac.mp4</BaseURL>
<SegmentBase indexRange="786-1009" timescale="48000">
<Initialization range="0-785"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

52
node_modules/mpd-parser/test/manifests/location.js generated vendored Normal file
View file

@ -0,0 +1,52 @@
export const parsedManifest = {
locations: [
'https://www.example.com/updates'
],
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
targetDuration: 6,
mediaSequence: 0,
segments: [
{
duration: 6,
timeline: 0,
number: 0,
map: {
uri: '',
resolvedUri: 'https://www.example.com/1080p.ts'
},
resolvedUri: 'https://www.example.com/1080p.ts',
uri: 'https://www.example.com/1080p.ts'
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

17
node_modules/mpd-parser/test/manifests/location.mpd generated vendored Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Location>https://www.example.com/updates</Location>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
<SegmentBase></SegmentBase>
</Representation>
<BaseURL>1080p.ts</BaseURL>
<SegmentBase>
<RepresentationIndex sourceURL="1080p.sidx"/>
</SegmentBase>
</AdaptationSet>
</Period>
</MPD>

53
node_modules/mpd-parser/test/manifests/locations.js generated vendored Normal file
View file

@ -0,0 +1,53 @@
export const parsedManifest = {
locations: [
'https://www.example.com/updates',
'https://www.example.com/updates2'
],
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
targetDuration: 6,
mediaSequence: 0,
segments: [
{
duration: 6,
timeline: 0,
number: 0,
map: {
uri: '',
resolvedUri: 'https://www.example.com/1080p.ts'
},
resolvedUri: 'https://www.example.com/1080p.ts',
uri: 'https://www.example.com/1080p.ts'
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

18
node_modules/mpd-parser/test/manifests/locations.mpd generated vendored Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Location>https://www.example.com/updates</Location>
<Location>https://www.example.com/updates2</Location>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
<SegmentBase></SegmentBase>
</Representation>
<BaseURL>1080p.ts</BaseURL>
<SegmentBase>
<RepresentationIndex sourceURL="1080p.sidx"/>
</SegmentBase>
</AdaptationSet>
</Period>
</MPD>

View file

@ -0,0 +1,518 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
'en (main)': {
language: 'en',
autoselect: true,
default: true,
playlists: [
{
attributes: {
'NAME': '63000',
'BANDWIDTH': 63000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '63000/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/0.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 0
},
{
uri: '63000/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/1.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 1
},
{
uri: '63000/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/2.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 2
},
{
uri: '63000/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/63000/3.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '125000',
'BANDWIDTH': 125000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '125000/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/0.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 0
},
{
uri: '125000/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/1.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 1
},
{
uri: '125000/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/2.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 2
},
{
uri: '125000/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/125000/3.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
],
uri: ''
},
'es': {
language: 'es',
autoselect: true,
default: false,
playlists: [
{
attributes: {
'NAME': '63000',
'BANDWIDTH': 63000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '63000/es/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/0.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 0
},
{
uri: '63000/es/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/1.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 1
},
{
uri: '63000/es/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/2.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 2
},
{
uri: '63000/es/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/63000/es/3.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '125000',
'BANDWIDTH': 125000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '125000/es/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/0.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 0
},
{
uri: '125000/es/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/1.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 1
},
{
uri: '125000/es/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/2.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 2
},
{
uri: '125000/es/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/125000/es/3.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
],
uri: ''
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {
subs: {
en: {
language: 'en',
default: false,
autoselect: false,
playlists: [
{
attributes: {
'NAME': 'en',
'BANDWIDTH': 256,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: 'https://example.com/en.vtt',
targetDuration: 6,
segments: [
{
uri: 'https://example.com/en.vtt',
timeline: 0,
resolvedUri: 'https://example.com/en.vtt',
duration: 6,
number: 0
}
],
mediaSequence: 0
}
],
uri: ''
},
es: {
language: 'es',
default: false,
autoselect: false,
playlists: [
{
attributes: {
'NAME': 'es',
'BANDWIDTH': 256,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: 'https://example.com/es.vtt',
targetDuration: 6,
segments: [
{
uri: 'https://example.com/es.vtt',
timeline: 0,
resolvedUri: 'https://example.com/es.vtt',
duration: 6,
number: 0
}
],
mediaSequence: 0
}
],
uri: ''
}
}
}
},
uri: '',
duration: 6,
playlists: [
{
attributes: {
'NAME': '482',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 482,
height: 270
},
'CODECS': 'avc1.420015',
'BANDWIDTH': 449000,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.9185833333333333,
segments: [
{
uri: '482/0.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/0.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 0
},
{
uri: '482/1.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/1.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 1
},
{
uri: '482/2.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/2.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 2
},
{
uri: '482/3.m4f',
timeline: 0,
duration: 0.24425000000000008,
resolvedUri: 'https://www.example.com/482/3.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '720',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 720,
height: 404
},
'CODECS': 'avc1.64001e',
'BANDWIDTH': 3971000,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.9185833333333333,
segments: [
{
uri: '720/0.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/0.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 0
},
{
uri: '720/1.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/1.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 1
},
{
uri: '720/2.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/2.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 2
},
{
uri: '720/3.m4f',
timeline: 0,
duration: 0.24425000000000008,
resolvedUri: 'https://www.example.com/720/3.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
]
};

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
</Representation>
<Representation audioSamplingRate="48000" bandwidth="125000" codecs="mp4a.40.2" id="125000">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="es">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/es/init.m4f" media="$RepresentationID$/es/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
</Representation>
<Representation audioSamplingRate="48000" bandwidth="125000" codecs="mp4a.40.2" id="125000">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="46046" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="24000"></SegmentTemplate>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482">
</Representation>
<Representation bandwidth="3971000" codecs="avc1.64001e" frameRate="2997/125" height="404" id="720" width="720">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="en">
<Representation bandwidth="256" id="en">
<BaseURL>https://example.com/en.vtt</BaseURL>
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="es">
<Representation bandwidth="256" id="es">
<BaseURL>https://example.com/es.vtt</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="dynamic" mediaPresentationDuration="PT36.269S" minBufferTime="PT2.000S" suggestedPresentationDelay="PT18S">
<Period id="0" duration="PT5.972633333S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init0.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="0" timescale="44100">
<SegmentTimeline>
<S d="88064" r="2"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init0.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="0" timescale="30000">
<SegmentTimeline>
<S d="60060" r="1"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="1" duration="PT4.838166665S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init1.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="3" timescale="44100">
<SegmentTimeline>
<S d="88064" r="1"></S>
<S d="37888"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init1.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="3" timescale="30000">
<SegmentTimeline>
<S d="58058" r="1"></S>
<S d="29029"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="2" duration="PT13.980633333S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init2.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="6" timescale="44100">
<SegmentTimeline>
<S d="88064" r="6"></S>
<S d="1024"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init2.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="6" timescale="30000">
<SegmentTimeline>
<S d="60060" r="5"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="3" duration="PT3.157913831S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate presentationTimeOffset="214016" initialization="https://example.com/$RepresentationID$/init3.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="14" timescale="44100">
<SegmentTimeline>
<S d="51200"></S>
<S d="88064"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate presentationTimeOffset="145145" initialization="https://example.com/$RepresentationID$/init3.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="13" timescale="30000">
<SegmentTimeline>
<S d="29029"></S>
<S d="58058"></S>
<S d="8008"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="4" duration="PT7.987664396S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/segment$Number$.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="16" timescale="44100">
<SegmentTimeline>
<S d="88064" r="3"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/segment$Number$.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="16" timescale="30000">
<SegmentTimeline>
<S d="60060" r="2"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
</MPD>

1298
node_modules/mpd-parser/test/manifests/multiperiod.js generated vendored Normal file

File diff suppressed because it is too large Load diff

143
node_modules/mpd-parser/test/manifests/multiperiod.mpd generated vendored Normal file
View file

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT36.269S" minBufferTime="PT2.000S">
<Period id="0" duration="PT5.972633333S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init0.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="0" timescale="44100">
<SegmentTimeline>
<S d="88064" r="2"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init0.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="0" timescale="30000">
<SegmentTimeline>
<S d="60060" r="1"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="1" duration="PT4.838166665S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init1.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="3" timescale="44100">
<SegmentTimeline>
<S d="88064" r="1"></S>
<S d="37888"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init1.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="3" timescale="30000">
<SegmentTimeline>
<S d="58058" r="1"></S>
<S d="29029"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="2" duration="PT13.980633333S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init2.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="6" timescale="44100">
<SegmentTimeline>
<S d="88064" r="6"></S>
<S d="1024"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/init2.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="6" timescale="30000">
<SegmentTimeline>
<S d="60060" r="5"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="3" duration="PT3.157913831S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate presentationTimeOffset="214016" initialization="https://example.com/$RepresentationID$/init3.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="14" timescale="44100">
<SegmentTimeline>
<S d="51200"></S>
<S d="88064"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate presentationTimeOffset="145145" initialization="https://example.com/$RepresentationID$/init3.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="13" timescale="30000">
<SegmentTimeline>
<S d="29029"></S>
<S d="58058"></S>
<S d="8008"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
<Period id="4" duration="PT7.987664396S">
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/segment$Number$.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="16" timescale="44100">
<SegmentTimeline>
<S d="88064" r="3"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="123000" codecs="mp4a.40.2" id="default_audio128_2"></Representation>
<Representation audioSamplingRate="44100" bandwidth="93000" codecs="mp4a.40.2" id="default_audio96_2"></Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate initialization="https://example.com/$RepresentationID$/segment$Number$.m4f" media="https://example.com/$RepresentationID$/segment$Number$.m4f" startNumber="16" timescale="30000">
<SegmentTimeline>
<S d="60060" r="2"></S>
<S d="59059"></S>
</SegmentTimeline>
</SegmentTemplate>
<Representation bandwidth="2008000" codecs="avc1.4d001f" frameRate="2997/100" height="720" id="default_video2000_0_1280x720" width="1280"></Representation>
<Representation bandwidth="1195000" codecs="avc1.4d001f" frameRate="2997/100" height="540" id="default_video1200_1_960x540" width="960"></Representation>
<Representation bandwidth="884000" codecs="avc1.4d001e" frameRate="2997/100" height="360" id="default_video900_1_640x360" width="640"></Representation>
</AdaptationSet>
</Period>
</MPD>

49
node_modules/mpd-parser/test/manifests/segmentBase.js generated vendored Normal file
View file

@ -0,0 +1,49 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
targetDuration: 6,
mediaSequence: 0,
segments: [
{
duration: 6,
timeline: 0,
number: 0,
map: {
uri: '',
resolvedUri: 'https://www.example.com/1080p.ts'
},
resolvedUri: 'https://www.example.com/1080p.ts',
uri: 'https://www.example.com/1080p.ts'
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

16
node_modules/mpd-parser/test/manifests/segmentBase.mpd generated vendored Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
<SegmentBase></SegmentBase>
</Representation>
<BaseURL>1080p.ts</BaseURL>
<SegmentBase>
<RepresentationIndex sourceURL="1080p.sidx"/>
</SegmentBase>
</AdaptationSet>
</Period>
</MPD>

236
node_modules/mpd-parser/test/manifests/segmentList.js generated vendored Normal file
View file

@ -0,0 +1,236 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
duration: 6,
endList: true,
mediaGroups: {
'AUDIO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {},
'VIDEO': {}
},
playlists: [
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 449000,
'CODECS': 'avc1.420015',
'NAME': '482',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 270,
width: 482
},
'SUBTITLES': 'subs'
},
endList: true,
mediaSequence: 1,
targetDuration: 1,
resolvedUri: '',
segments: [
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-1.ts',
timeline: 0,
uri: 'low/segment-1.ts',
number: 1
},
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-2.ts',
timeline: 0,
uri: 'low/segment-2.ts',
number: 2
},
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-3.ts',
timeline: 0,
uri: 'low/segment-3.ts',
number: 3
},
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-4.ts',
timeline: 0,
uri: 'low/segment-4.ts',
number: 4
},
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-5.ts',
timeline: 0,
uri: 'low/segment-5.ts',
number: 5
},
{
duration: 1,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/low/segment-6.ts',
timeline: 0,
uri: 'low/segment-6.ts',
number: 6
}
],
timeline: 0,
uri: ''
},
{
attributes: {
'AUDIO': 'audio',
'BANDWIDTH': 3971000,
'CODECS': 'avc1.420015',
'NAME': '720',
'PROGRAM-ID': 1,
'RESOLUTION': {
height: 404,
width: 720
},
'SUBTITLES': 'subs'
},
endList: true,
resolvedUri: '',
mediaSequence: 1,
targetDuration: 60,
segments: [
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-1.ts',
timeline: 0,
uri: 'high/segment-1.ts',
number: 1
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-2.ts',
timeline: 0,
uri: 'high/segment-2.ts',
number: 2
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-3.ts',
timeline: 0,
uri: 'high/segment-3.ts',
number: 3
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-4.ts',
timeline: 0,
uri: 'high/segment-4.ts',
number: 4
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-5.ts',
timeline: 0,
uri: 'high/segment-5.ts',
number: 5
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-6.ts',
timeline: 0,
uri: 'high/segment-6.ts',
number: 6
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-7.ts',
timeline: 0,
uri: 'high/segment-7.ts',
number: 7
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-8.ts',
timeline: 0,
uri: 'high/segment-8.ts',
number: 8
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-9.ts',
timeline: 0,
uri: 'high/segment-9.ts',
number: 9
},
{
duration: 60,
map: {
uri: '',
resolvedUri: 'https://www.example.com/base'
},
resolvedUri: 'https://www.example.com/high/segment-10.ts',
timeline: 0,
uri: 'high/segment-10.ts',
number: 10
}
],
timeline: 0,
uri: ''
}
],
segments: [],
uri: ''
};

47
node_modules/mpd-parser/test/manifests/segmentList.mpd generated vendored Normal file
View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482" >
</Representation>
<SegmentList timescale="1000" duration="1000">
<RepresentationIndex sourceURL="representation-index-low"/>
<SegmentURL media="low/segment-1.ts"/>
<SegmentURL media="low/segment-2.ts"/>
<SegmentURL media="low/segment-3.ts"/>
<SegmentURL media="low/segment-4.ts"/>
<SegmentURL media="low/segment-5.ts"/>
<SegmentURL media="low/segment-6.ts"/>
<SegmentURL media="low/segment-7.ts"/>
<SegmentURL media="low/segment-8.ts"/>
<SegmentURL media="low/segment-9.ts"/>
<SegmentURL media="low/segment-10.ts"/>
</SegmentList>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<Representation bandwidth="3971000" codecs="avc1.420015" frameRate="2997/125" height="404" id="720" width="720" >
<SegmentList timescale="90000">
<RepresentationIndex sourceURL="representation-index-high"/>
<SegmentTimeline>
<S t="0" r="9" d="5400000"/>
</SegmentTimeline>
<SegmentURL media="high/segment-1.ts"/>
<SegmentURL media="high/segment-2.ts"/>
<SegmentURL media="high/segment-3.ts"/>
<SegmentURL media="high/segment-4.ts"/>
<SegmentURL media="high/segment-5.ts"/>
<SegmentURL media="high/segment-6.ts"/>
<SegmentURL media="high/segment-7.ts"/>
<SegmentURL media="high/segment-8.ts"/>
<SegmentURL media="high/segment-9.ts"/>
<SegmentURL media="high/segment-10.ts"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>

519
node_modules/mpd-parser/test/manifests/vtt_codecs.js generated vendored Normal file
View file

@ -0,0 +1,519 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
'en (main)': {
language: 'en',
autoselect: true,
default: true,
playlists: [
{
attributes: {
'NAME': '63000',
'BANDWIDTH': 63000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '63000/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/0.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 0
},
{
uri: '63000/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/1.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 1
},
{
uri: '63000/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/2.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 2
},
{
uri: '63000/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/63000/3.m4f',
map: {
uri: '63000/init.m4f',
resolvedUri: 'https://www.example.com/63000/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '125000',
'BANDWIDTH': 125000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '125000/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/0.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 0
},
{
uri: '125000/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/1.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 1
},
{
uri: '125000/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/2.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 2
},
{
uri: '125000/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/125000/3.m4f',
map: {
uri: '125000/init.m4f',
resolvedUri: 'https://www.example.com/125000/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
],
uri: ''
},
'es': {
language: 'es',
autoselect: true,
default: false,
playlists: [
{
attributes: {
'NAME': '63000',
'BANDWIDTH': 63000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '63000/es/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/0.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 0
},
{
uri: '63000/es/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/1.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 1
},
{
uri: '63000/es/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/63000/es/2.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 2
},
{
uri: '63000/es/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/63000/es/3.m4f',
map: {
uri: '63000/es/init.m4f',
resolvedUri: 'https://www.example.com/63000/es/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '125000',
'BANDWIDTH': 125000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.984,
segments: [
{
uri: '125000/es/0.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/0.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 0
},
{
uri: '125000/es/1.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/1.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 1
},
{
uri: '125000/es/2.m4f',
timeline: 0,
duration: 1.984,
resolvedUri: 'https://www.example.com/125000/es/2.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 2
},
{
uri: '125000/es/3.m4f',
timeline: 0,
duration: 0.04800000000000004,
resolvedUri: 'https://www.example.com/125000/es/3.m4f',
map: {
uri: '125000/es/init.m4f',
resolvedUri: 'https://www.example.com/125000/es/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
],
uri: ''
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {
subs: {
en: {
language: 'en',
default: false,
autoselect: false,
playlists: [
{
attributes: {
'NAME': 'en',
'BANDWIDTH': 256,
'PROGRAM-ID': 1,
'CODECS': 'stpp.ttml.im1t'
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: 'https://example.com/en.dash',
targetDuration: 6,
segments: [
{
uri: 'https://example.com/en.dash',
timeline: 0,
resolvedUri: 'https://example.com/en.dash',
duration: 6,
number: 0
}
],
mediaSequence: 0
}
],
uri: ''
},
es: {
language: 'es',
default: false,
autoselect: false,
playlists: [
{
attributes: {
'NAME': 'es',
'BANDWIDTH': 256,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: 'https://example.com/es.vtt',
targetDuration: 6,
segments: [
{
uri: 'https://example.com/es.vtt',
timeline: 0,
resolvedUri: 'https://example.com/es.vtt',
duration: 6,
number: 0
}
],
mediaSequence: 0
}
],
uri: ''
}
}
}
},
uri: '',
duration: 6,
playlists: [
{
attributes: {
'NAME': '482',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 482,
height: 270
},
'CODECS': 'avc1.420015',
'BANDWIDTH': 449000,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.9185833333333333,
segments: [
{
uri: '482/0.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/0.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 0
},
{
uri: '482/1.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/1.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 1
},
{
uri: '482/2.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/482/2.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 2
},
{
uri: '482/3.m4f',
timeline: 0,
duration: 0.24425000000000008,
resolvedUri: 'https://www.example.com/482/3.m4f',
map: {
uri: '482/init.m4f',
resolvedUri: 'https://www.example.com/482/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
},
{
attributes: {
'NAME': '720',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 720,
height: 404
},
'CODECS': 'avc1.64001e',
'BANDWIDTH': 3971000,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 1.9185833333333333,
segments: [
{
uri: '720/0.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/0.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 0
},
{
uri: '720/1.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/1.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 1
},
{
uri: '720/2.m4f',
timeline: 0,
duration: 1.9185833333333333,
resolvedUri: 'https://www.example.com/720/2.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 2
},
{
uri: '720/3.m4f',
timeline: 0,
duration: 0.24425000000000008,
resolvedUri: 'https://www.example.com/720/3.m4f',
map: {
uri: '720/init.m4f',
resolvedUri: 'https://www.example.com/720/init.m4f'
},
number: 3
}
],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}
]
};

60
node_modules/mpd-parser/test/manifests/vtt_codecs.mpd generated vendored Normal file
View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
</Representation>
<Representation audioSamplingRate="48000" bandwidth="125000" codecs="mp4a.40.2" id="125000">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="es">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/es/init.m4f" media="$RepresentationID$/es/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
</Representation>
<Representation audioSamplingRate="48000" bandwidth="125000" codecs="mp4a.40.2" id="125000">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="46046" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="24000"></SegmentTemplate>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482">
</Representation>
<Representation bandwidth="3971000" codecs="avc1.64001e" frameRate="2997/125" height="404" id="720" width="720">
</Representation>
</AdaptationSet>
<AdaptationSet contentType="text" mimeType="application/mp4" lang="en" codecs="stpp.ttml.im1t">
<Representation bandwidth="256" id="en">
<BaseURL>https://example.com/en.dash</BaseURL>
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="text/vtt" lang="es">
<Representation bandwidth="256" id="es">
<BaseURL>https://example.com/es.vtt</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>

153
node_modules/mpd-parser/test/manifests/webmsegments.js generated vendored Normal file
View file

@ -0,0 +1,153 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
en: {
language: 'en',
autoselect: true,
default: true,
playlists: [
{
attributes: {
'NAME': '2',
'BANDWIDTH': 32000,
'CODECS': 'opus',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 1,
resolvedUri: '',
targetDuration: 4,
segments: [
{
uri: 'audio/segment_0.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/audio/segment_0.chk',
map: {
uri: 'audio/init.hdr',
resolvedUri: 'https://www.example.com/audio/init.hdr'
},
number: 0
},
{
uri: 'audio/segment_1.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/audio/segment_1.chk',
map: {
uri: 'audio/init.hdr',
resolvedUri: 'https://www.example.com/audio/init.hdr'
},
number: 1
},
{
uri: 'audio/segment_2.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/audio/segment_2.chk',
map: {
uri: 'audio/init.hdr',
resolvedUri: 'https://www.example.com/audio/init.hdr'
},
number: 2
},
{
uri: 'audio/segment_3.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/audio/segment_3.chk',
map: {
uri: 'audio/init.hdr',
resolvedUri: 'https://www.example.com/audio/init.hdr'
},
number: 3
}
],
mediaSequence: 0
}
],
uri: ''
}
}
},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
},
uri: '',
duration: 16,
playlists: [
{
attributes: {
'NAME': '1',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 480,
height: 200
},
'CODECS': 'av1',
'BANDWIDTH': 100000,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 1,
resolvedUri: '',
targetDuration: 4,
segments: [
{
uri: 'video/segment_0.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/video/segment_0.chk',
map: {
uri: 'video/init.hdr',
resolvedUri: 'https://www.example.com/video/init.hdr'
},
number: 0
},
{
uri: 'video/segment_1.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/video/segment_1.chk',
map: {
uri: 'video/init.hdr',
resolvedUri: 'https://www.example.com/video/init.hdr'
},
number: 1
},
{
uri: 'video/segment_2.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/video/segment_2.chk',
map: {
uri: 'video/init.hdr',
resolvedUri: 'https://www.example.com/video/init.hdr'
},
number: 2
},
{
uri: 'video/segment_3.chk',
timeline: 1,
duration: 4,
resolvedUri: 'https://www.example.com/video/segment_3.chk',
map: {
uri: 'video/init.hdr',
resolvedUri: 'https://www.example.com/video/init.hdr'
},
number: 3
}
],
mediaSequence: 0
}
]
};

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<MPD id="a467fa27-2820-41d1-89e2-a43216770daa" profiles="urn:mpeg:dash:profile:full:2011" type="static"
mediaPresentationDuration="P0Y0M0DT0H0M16S" minBufferTime="P0Y0M0DT0H0M2.000S"
xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:ns2="http://www.w3.org/1999/xlink">
<Period id="1" start="P0Y0M0DT0H0M0.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<AdaptationSet mimeType="video/webm">
<Representation id="1" bandwidth="100000" width="480" height="200" frameRate="24" codecs="av1">
<SegmentTemplate media="video/segment_$Number$.chk" initialization="video/init.hdr" duration="96000"
startNumber="0" timescale="24000"/>
</Representation>
</AdaptationSet>
<AdaptationSet lang="en" mimeType="audio/webm">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<Representation id="2" bandwidth="32000" audioSamplingRate="48000" codecs="opus">
<SegmentTemplate media="audio/segment_$Number$.chk" initialization="audio/init.hdr" duration="192000"
startNumber="0" timescale="48000"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>

21
node_modules/mpd-parser/test/parseAttributes.test.js generated vendored Normal file
View file

@ -0,0 +1,21 @@
import {JSDOM} from 'jsdom';
import QUnit from 'qunit';
import { parseAttributes } from '../src/parseAttributes';
const document = new JSDOM().window.document;
QUnit.module('parseAttributes');
QUnit.test('simple', function(assert) {
const el = document.createElement('el');
el.setAttribute('foo', 1);
assert.deepEqual(parseAttributes(el), { foo: '1' });
});
QUnit.test('empty', function(assert) {
const el = document.createElement('el');
assert.deepEqual(parseAttributes(el), {});
});

View file

@ -0,0 +1,201 @@
import QUnit from 'qunit';
import {
segmentsFromBase,
addSidxSegmentsToPlaylist
} from '../../src/segment/segmentBase';
import errors from '../../src/errors';
QUnit.module('segmentBase - segmentsFromBase');
QUnit.test('sets segment to baseUrl', function(assert) {
const inputAttributes = {
baseUrl: 'http://www.example.com/i.fmp4',
initialization: { sourceURL: 'http://www.example.com/init.fmp4' }
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4'
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
QUnit.test('sets duration based on sourceDuration', function(assert) {
const inputAttributes = {
baseUrl: 'http://www.example.com/i.fmp4',
initialization: { sourceURL: 'http://www.example.com/init.fmp4' },
sourceDuration: 10
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
duration: 10,
timeline: 0,
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4'
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
// sourceDuration comes from mediaPresentationDuration. The DASH spec defines the type of
// mediaPresentationDuration as xs:duration, which follows ISO 8601. It does not need to
// be adjusted based on timescale.
//
// References:
// https://www.w3.org/TR/xmlschema-2/#duration
// https://en.wikipedia.org/wiki/ISO_8601
QUnit.test('sets duration based on sourceDuration and not @timescale', function(assert) {
const inputAttributes = {
baseUrl: 'http://www.example.com/i.fmp4',
initialization: { sourceURL: 'http://www.example.com/init.fmp4' },
sourceDuration: 10,
timescale: 2
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
duration: 10,
timeline: 0,
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4'
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
QUnit.test('sets duration based on @duration', function(assert) {
const inputAttributes = {
duration: 10,
sourceDuration: 20,
baseUrl: 'http://www.example.com/i.fmp4',
initialization: { sourceURL: 'http://www.example.com/init.fmp4' },
periodIndex: 0
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
duration: 10,
timeline: 0,
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4'
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
QUnit.test('sets duration based on @duration and @timescale', function(assert) {
const inputAttributes = {
duration: 10,
sourceDuration: 20,
timescale: 5,
baseUrl: 'http://www.example.com/i.fmp4',
initialization: { sourceURL: 'http://www.example.com/init.fmp4' },
periodIndex: 0
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
duration: 2,
timeline: 0,
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4'
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
QUnit.test('translates ranges in <Initialization> node', function(assert) {
const inputAttributes = {
duration: 10,
sourceDuration: 20,
timescale: 5,
baseUrl: 'http://www.example.com/i.fmp4',
initialization: {
sourceURL: 'http://www.example.com/init.fmp4',
range: '121-125'
},
periodIndex: 0
};
assert.deepEqual(segmentsFromBase(inputAttributes), [{
duration: 2,
timeline: 0,
map: {
resolvedUri: 'http://www.example.com/init.fmp4',
uri: 'http://www.example.com/init.fmp4',
byterange: {
length: 5,
offset: 121
}
},
resolvedUri: 'http://www.example.com/i.fmp4',
uri: 'http://www.example.com/i.fmp4',
number: 0
}]);
});
QUnit.test('errors if no baseUrl exists', function(assert) {
assert.throws(() => segmentsFromBase({}), new Error(errors.NO_BASE_URL));
});
QUnit.module('segmentBase - addSidxSegmentsToPlaylist');
QUnit.test('generates playlist from sidx references', function(assert) {
const baseUrl = 'http://www.example.com/i.fmp4';
const playlist = {
sidx: {
map: {
byterange: {
offset: 0,
length: 10
}
},
duration: 10,
byterange: {
offset: 9,
length: 11
}
},
segments: []
};
const sidx = {
timescale: 1,
firstOffset: 0,
references: [{
referenceType: 0,
referencedSize: 5,
subsegmentDuration: 2
}]
};
assert.deepEqual(addSidxSegmentsToPlaylist(playlist, sidx, baseUrl).segments, [{
map: {
byterange: {
offset: 0,
length: 10
}
},
uri: 'http://www.example.com/i.fmp4',
resolvedUri: 'http://www.example.com/i.fmp4',
byterange: {
offset: 20,
length: 5
},
duration: 2,
timeline: 0,
number: 0
}]);
});

View file

@ -0,0 +1,553 @@
import QUnit from 'qunit';
import {
segmentsFromList
} from '../../src/segment/segmentList';
import errors from '../../src/errors';
QUnit.module('segmentList - segmentsFromList');
QUnit.test('uses segmentTimeline to set segments', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}, {
media: '3.fmp4'
}, {
media: '4.fmp4'
}, {
media: '5.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
periodIndex: 0,
startNumber: 1,
baseUrl: 'http://example.com/'
};
const inputTimeline = [{
t: 1000,
d: 1000,
r: 4
}];
assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/3.fmp4',
timeline: 0,
uri: '3.fmp4',
number: 3
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/4.fmp4',
timeline: 0,
uri: '4.fmp4',
number: 4
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/5.fmp4',
timeline: 0,
uri: '5.fmp4',
number: 5
}]);
});
QUnit.test(
'truncates if segmentTimeline does not apply for all segments',
function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}, {
media: '3.fmp4'
}, {
media: '4.fmp4'
}, {
media: '5.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
periodIndex: 0,
startNumber: 1,
baseUrl: 'http://example.com/'
};
const inputTimeline = [{
t: 1000,
d: 1000,
r: 1
}];
assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}]);
}
);
QUnit.test(
'if segment timeline is too long does not add extra blank segments',
function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}, {
media: '3.fmp4'
}, {
media: '4.fmp4'
}, {
media: '5.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
periodIndex: 0,
startNumber: 1,
baseUrl: 'http://example.com/'
};
const inputTimeline = [{
t: 1000,
d: 1000,
r: 10
}];
assert.deepEqual(segmentsFromList(inputAttributes, inputTimeline), [{
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/3.fmp4',
timeline: 0,
uri: '3.fmp4',
number: 3
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/4.fmp4',
timeline: 0,
uri: '4.fmp4',
number: 4
}, {
duration: 1000,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/5.fmp4',
timeline: 0,
uri: '5.fmp4',
number: 5
}]);
}
);
QUnit.test('uses duration to set segments', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}, {
media: '3.fmp4'
}, {
media: '4.fmp4'
}, {
media: '5.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
duration: 10,
periodIndex: 0,
startNumber: 1,
sourceDuration: 50,
baseUrl: 'http://example.com/'
};
assert.deepEqual(segmentsFromList(inputAttributes), [{
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}, {
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/3.fmp4',
timeline: 0,
uri: '3.fmp4',
number: 3
}, {
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/4.fmp4',
timeline: 0,
uri: '4.fmp4',
number: 4
}, {
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/5.fmp4',
timeline: 0,
uri: '5.fmp4',
number: 5
}]);
});
QUnit.test('uses timescale to set segment duration', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}, {
media: '3.fmp4'
}, {
media: '4.fmp4'
}, {
media: '5.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
duration: 10,
timescale: 2,
periodIndex: 0,
startNumber: 1,
sourceDuration: 25,
baseUrl: 'http://example.com/'
};
assert.deepEqual(segmentsFromList(inputAttributes), [{
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}, {
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/3.fmp4',
timeline: 0,
uri: '3.fmp4',
number: 3
}, {
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/4.fmp4',
timeline: 0,
uri: '4.fmp4',
number: 4
}, {
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/5.fmp4',
timeline: 0,
uri: '5.fmp4',
number: 5
}]);
});
QUnit.test('timescale sets duration of last segment correctly', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
duration: 10,
timescale: 1,
periodIndex: 0,
startNumber: 1,
sourceDuration: 15,
baseUrl: 'http://example.com/'
};
assert.deepEqual(segmentsFromList(inputAttributes), [{
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 5,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}]);
});
QUnit.test('segmentUrl translates ranges correctly', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4',
mediaRange: '0-200'
}, {
media: '1.fmp4',
mediaRange: '201-400'
}],
initialization: { sourceURL: 'init.fmp4' },
duration: 10,
timescale: 1,
periodIndex: 0,
startNumber: 1,
sourceDuration: 20,
baseUrl: 'http://example.com/'
};
assert.deepEqual(segmentsFromList(inputAttributes), [{
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
byterange: {
length: 201,
offset: 0
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 10,
byterange: {
length: 200,
offset: 201
},
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 2
}]);
});
QUnit.test(
'throws error if more than 1 segment and no duration or timeline',
function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}],
duration: 10,
initialization: { sourceURL: 'init.fmp4' },
timescale: 1,
periodIndex: 0,
startNumber: 1,
sourceDuration: 20,
baseUrl: 'http://example.com/'
};
const inputTimeline = [{
t: 1000,
d: 1000,
r: 4
}];
assert.throws(
() => segmentsFromList(inputAttributes, inputTimeline),
new Error(errors.SEGMENT_TIME_UNSPECIFIED)
);
}
);
QUnit.test('throws error if timeline and duration are both defined', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}],
initialization: { sourceURL: 'init.fmp4' },
timescale: 1,
periodIndex: 0,
startNumber: 1,
sourceDuration: 20,
baseUrl: 'http://example.com/'
};
assert.throws(
() => segmentsFromList(inputAttributes),
new Error(errors.SEGMENT_TIME_UNSPECIFIED)
);
});
QUnit.test('translates ranges in <Initialization> node', function(assert) {
const inputAttributes = {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '1.fmp4'
}],
initialization: { sourceURL: 'init.fmp4', range: '121-125' },
duration: 10,
timescale: 1,
periodIndex: 0,
startNumber: 1,
sourceDuration: 20,
baseUrl: 'http://example.com/'
};
assert.deepEqual(segmentsFromList(inputAttributes), [{
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4',
byterange: {
length: 5,
offset: 121
}
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 10,
map: {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4',
byterange: {
length: 5,
offset: 121
}
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 2
}]);
});

File diff suppressed because it is too large Load diff

94
node_modules/mpd-parser/test/segment/urlType.test.js generated vendored Normal file
View file

@ -0,0 +1,94 @@
import QUnit from 'qunit';
import {
urlTypeToSegment as urlTypeConverter,
byteRangeToString
} from '../../src/segment/urlType';
QUnit.module('urlType - urlTypeConverter');
QUnit.test('returns correct object if given baseUrl only', function(assert) {
assert.deepEqual(urlTypeConverter({ baseUrl: 'http://example.com/' }), {
resolvedUri: 'http://example.com/',
uri: ''
});
});
QUnit.test('returns correct object if given baseUrl and source', function(assert) {
assert.deepEqual(urlTypeConverter({
baseUrl: 'http://example.com',
source: 'init.fmp4'
}), {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4'
});
});
QUnit.test('returns correct object if given baseUrl, source and range', function(assert) {
assert.deepEqual(urlTypeConverter({
baseUrl: 'http://example.com',
source: 'init.fmp4',
range: '101-105'
}), {
resolvedUri: 'http://example.com/init.fmp4',
uri: 'init.fmp4',
byterange: {
offset: 101,
length: 5
}
});
});
QUnit.test('returns correct object if given baseUrl, source and indexRange', function(assert) {
assert.deepEqual(urlTypeConverter({
baseUrl: 'http://example.com',
source: 'sidx.fmp4',
indexRange: '101-105'
}), {
resolvedUri: 'http://example.com/sidx.fmp4',
uri: 'sidx.fmp4',
byterange: {
offset: 101,
length: 5
}
});
});
QUnit.test('returns correct object if given baseUrl and range', function(assert) {
assert.deepEqual(urlTypeConverter({
baseUrl: 'http://example.com/',
range: '101-105'
}), {
resolvedUri: 'http://example.com/',
uri: '',
byterange: {
offset: 101,
length: 5
}
});
});
QUnit.test('returns correct object if given baseUrl and indexRange', function(assert) {
assert.deepEqual(urlTypeConverter({
baseUrl: 'http://example.com/',
indexRange: '101-105'
}), {
resolvedUri: 'http://example.com/',
uri: '',
byterange: {
offset: 101,
length: 5
}
});
});
QUnit.module('urlType - byteRangeToString');
QUnit.test('returns correct string representing byterange object', function(assert) {
assert.strictEqual(
byteRangeToString({
offset: 0,
length: 100
}),
'0-99'
);
});

21
node_modules/mpd-parser/test/stringToMpdXml.test.js generated vendored Normal file
View file

@ -0,0 +1,21 @@
import { stringToMpdXml } from '../src/stringToMpdXml';
import errors from '../src/errors';
import QUnit from 'qunit';
QUnit.module('stringToMpdXml');
QUnit.test('simple mpd', function(assert) {
assert.deepEqual(stringToMpdXml('<MPD></MPD>').tagName, 'MPD');
});
QUnit.test('invalid xml', function(assert) {
assert.throws(() => stringToMpdXml('<test'), new RegExp(errors.DASH_INVALID_XML));
});
QUnit.test('invalid manifest', function(assert) {
assert.throws(() => stringToMpdXml('<test>'), new RegExp(errors.DASH_INVALID_XML));
});
QUnit.test('empty manifest', function(assert) {
assert.throws(() => stringToMpdXml(''), new RegExp(errors.DASH_EMPTY_MANIFEST));
});

935
node_modules/mpd-parser/test/toM3u8.test.js generated vendored Normal file
View file

@ -0,0 +1,935 @@
import { toM3u8, generateSidxKey } from '../src/toM3u8';
import QUnit from 'qunit';
QUnit.module('toM3u8');
QUnit.test('playlists', function(assert) {
const input = [{
attributes: {
id: '1',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: []
}, {
attributes: {
id: '2',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
bandwidth: 20000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
}
}, {
attributes: {
sourceDuration: 100,
id: '1',
bandwidth: 10000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
}
}];
const expected = {
allowCache: true,
discontinuityStarts: [],
duration: 100,
endList: true,
mediaGroups: {
AUDIO: {
audio: {
main: {
autoselect: true,
default: true,
language: '',
playlists: [{
attributes: {
BANDWIDTH: 20000,
CODECS: 'foo;bar',
NAME: '1',
['PROGRAM-ID']: 1
},
mediaSequence: 1,
endList: true,
resolvedUri: '',
segments: [],
timeline: 1,
uri: '',
targetDuration: 0
}, {
attributes: {
BANDWIDTH: 10000,
CODECS: 'foo;bar',
NAME: '2',
['PROGRAM-ID']: 1
},
mediaSequence: 1,
endList: true,
resolvedUri: '',
segments: [],
timeline: 1,
uri: '',
targetDuration: 0
}],
uri: ''
}
}
},
['CLOSED-CAPTIONS']: {},
SUBTITLES: {
subs: {
text: {
autoselect: false,
default: false,
language: 'text',
playlists: [{
attributes: {
BANDWIDTH: 20000,
NAME: '1',
['PROGRAM-ID']: 1
},
mediaSequence: 0,
targetDuration: 100,
endList: true,
resolvedUri: 'https://www.example.com/vtt',
segments: [{
duration: 100,
resolvedUri: 'https://www.example.com/vtt',
timeline: 1,
uri: 'https://www.example.com/vtt',
number: 0
}],
timeline: 1,
uri: ''
}, {
attributes: {
BANDWIDTH: 10000,
NAME: '1',
['PROGRAM-ID']: 1
},
mediaSequence: 0,
targetDuration: 100,
endList: true,
resolvedUri: 'https://www.example.com/vtt',
segments: [{
duration: 100,
resolvedUri: 'https://www.example.com/vtt',
timeline: 1,
uri: 'https://www.example.com/vtt',
number: 0
}],
timeline: 1,
uri: ''
}],
uri: ''
}
}
},
VIDEO: {}
},
playlists: [{
attributes: {
AUDIO: 'audio',
SUBTITLES: 'subs',
BANDWIDTH: 10000,
CODECS: 'foo;bar',
NAME: '1',
['PROGRAM-ID']: 1,
RESOLUTION: {
height: 600,
width: 800
}
},
endList: true,
mediaSequence: 1,
targetDuration: 0,
resolvedUri: '',
segments: [],
timeline: 1,
uri: ''
}],
segments: [],
uri: ''
};
assert.deepEqual(toM3u8(input), expected);
});
QUnit.test('playlists with segments', function(assert) {
const input = [{
attributes: {
id: '1',
codecs: 'foo;bar',
duration: 2,
sourceDuration: 100,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}]
}, {
attributes: {
id: '2',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 2,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}]
}, {
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
duration: 2,
height: 600,
codecs: 'foo;bar',
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}]
}, {
attributes: {
sourceDuration: 100,
id: '1',
duration: 2,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
},
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}]
}, {
attributes: {
sourceDuration: 100,
duration: 2,
id: '2',
bandwidth: 10000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
},
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}]
}];
const expected = {
allowCache: true,
discontinuityStarts: [],
duration: 100,
endList: true,
mediaGroups: {
AUDIO: {
audio: {
main: {
autoselect: true,
default: true,
language: '',
playlists: [{
attributes: {
BANDWIDTH: 20000,
CODECS: 'foo;bar',
NAME: '1',
['PROGRAM-ID']: 1
},
targetDuration: 2,
mediaSequence: 1,
endList: true,
resolvedUri: '',
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}],
timeline: 1,
uri: ''
}, {
attributes: {
BANDWIDTH: 10000,
CODECS: 'foo;bar',
NAME: '2',
['PROGRAM-ID']: 1
},
targetDuration: 2,
mediaSequence: 1,
endList: true,
resolvedUri: '',
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}],
timeline: 1,
uri: ''
}],
uri: ''
}
}
},
['CLOSED-CAPTIONS']: {},
SUBTITLES: {
subs: {
text: {
autoselect: false,
default: false,
language: 'text',
playlists: [{
attributes: {
BANDWIDTH: 20000,
NAME: '1',
['PROGRAM-ID']: 1
},
endList: true,
targetDuration: 2,
mediaSequence: 1,
resolvedUri: 'https://www.example.com/vtt',
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}],
timeline: 1,
uri: ''
}, {
attributes: {
BANDWIDTH: 10000,
NAME: '2',
['PROGRAM-ID']: 1
},
endList: true,
targetDuration: 2,
mediaSequence: 1,
resolvedUri: 'https://www.example.com/vtt',
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}],
timeline: 1,
uri: ''
}],
uri: ''
}
}
},
VIDEO: {}
},
playlists: [{
attributes: {
AUDIO: 'audio',
SUBTITLES: 'subs',
BANDWIDTH: 10000,
CODECS: 'foo;bar',
NAME: '1',
['PROGRAM-ID']: 1,
RESOLUTION: {
height: 600,
width: 800
}
},
endList: true,
resolvedUri: '',
mediaSequence: 1,
targetDuration: 2,
segments: [{
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 1
}, {
uri: '',
timeline: 1,
duration: 2,
resolvedUri: '',
map: {
uri: '',
resolvedUri: ''
},
number: 2
}],
timeline: 1,
uri: ''
}],
segments: [],
uri: ''
};
assert.deepEqual(toM3u8(input), expected);
});
QUnit.test('playlists with sidx and sidxMapping', function(assert) {
const input = [{
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: [],
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 10
},
uri: 'http://example.com/fmp4.mp4'
}];
const mapping = {
'sidx.mp4-10-19': {
sidx: {
timescale: 1,
firstOffset: 0,
references: [{
referenceType: 0,
referencedSize: 5,
subsegmentDuration: 2
}]
}
}
};
const expected = [{
attributes: {
AUDIO: 'audio',
SUBTITLES: 'subs',
BANDWIDTH: 10000,
CODECS: 'foo;bar',
NAME: '1',
['PROGRAM-ID']: 1,
RESOLUTION: {
height: 600,
width: 800
}
},
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 10
},
targetDuration: 0,
timeline: 1,
uri: '',
segments: [{
map: {
resolvedUri: 'http://example.com/sidx.mp4',
uri: ''
},
byterange: {
offset: 20,
length: 5
},
uri: 'http://example.com/sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 2,
number: 0,
timeline: 1
}],
endList: true,
mediaSequence: 1,
resolvedUri: ''
}];
assert.deepEqual(toM3u8(input, null, mapping).playlists, expected);
});
QUnit.test('playlists without minimumUpdatePeriod dont assign default value', function(assert) {
const input = [{
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: [],
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 10
},
uri: 'http://example.com/fmp4.mp4'
}];
assert.equal(toM3u8(input).minimumUpdatePeriod, undefined);
});
QUnit.test('playlists with minimumUpdatePeriod = 0', function(assert) {
const input = [{
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4',
minimumUpdatePeriod: 0
},
segments: [],
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 10
},
uri: 'http://example.com/fmp4.mp4'
}];
assert.equal(toM3u8(input).minimumUpdatePeriod, 0);
});
QUnit.test('playlists with integer value for minimumUpdatePeriod', function(assert) {
const input = [{
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4',
minimumUpdatePeriod: 2
},
segments: [],
sidx: {
byterange: {
offset: 10,
length: 10
},
uri: 'sidx.mp4',
resolvedUri: 'http://example.com/sidx.mp4',
duration: 10
},
uri: 'http://example.com/fmp4.mp4'
}];
assert.equal(toM3u8(input).minimumUpdatePeriod, 2000, 'converts update period to ms');
});
QUnit.test('no playlists', function(assert) {
assert.deepEqual(toM3u8([]), {});
});
QUnit.test('dynamic playlists with suggestedPresentationDelay', function(assert) {
const input = [{
attributes: {
id: '1',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'audio/mp4',
type: 'dynamic',
suggestedPresentationDelay: 18
},
segments: []
}, {
attributes: {
id: '2',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
bandwidth: 20000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
}
}, {
attributes: {
sourceDuration: 100,
id: '1',
bandwidth: 10000,
periodIndex: 1,
mimeType: 'text/vtt',
baseUrl: 'https://www.example.com/vtt'
}
}];
const output = toM3u8(input);
assert.ok('suggestedPresentationDelay' in output);
assert.deepEqual(output.suggestedPresentationDelay, 18);
});
QUnit.test('playlists with label', function(assert) {
const label = 'English with commentary';
const input = [{
attributes: {
id: '1',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'audio/mp4',
type: 'dynamic',
label
},
segments: []
}, {
attributes: {
id: '2',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: []
}];
const output = toM3u8(input);
assert.ok(label in output.mediaGroups.AUDIO.audio, 'label exists');
});
QUnit.test('608 captions', function(assert) {
const input = [{
attributes: {
captionServices: [{
channel: 'CC1',
language: 'CC1'
}, {
channel: 'CC2',
language: 'CC2'
}, {
channel: undefined,
language: 'English'
}, {
channel: 'CC4',
language: 'eng'
}],
id: '1',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 20000,
periodIndex: 1,
mimeType: 'audio/mp4',
type: 'dynamic'
},
segments: []
}, {
attributes: {
id: '2',
codecs: 'foo;bar',
sourceDuration: 100,
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'audio/mp4'
},
segments: []
}, {
attributes: {
sourceDuration: 100,
id: '1',
width: 800,
height: 600,
codecs: 'foo;bar',
duration: 0,
bandwidth: 10000,
periodIndex: 1,
mimeType: 'video/mp4'
},
segments: []
}];
const output = toM3u8(input);
const cc = output.mediaGroups['CLOSED-CAPTIONS'].cc;
Object.keys(cc).forEach((key) => {
assert.notOk(cc[key].autoselect, 'no autoselect');
assert.notOk(cc[key].default, 'no default');
});
assert.deepEqual(Object.keys(cc), ['CC1', 'CC2', 'English', 'eng'], 'we have 4 channels');
assert.equal(cc.CC1.instreamId, 'CC1', 'CC1 has an instreamId of CC1');
assert.equal(cc.CC2.instreamId, 'CC2', 'CC2 has an instreamId of CC1');
assert.equal(cc.English.instreamId, undefined, 'English captions dont have an instreamId');
assert.equal(cc.eng.instreamId, 'CC4', 'eng captions have an instreamId of CC4');
});
QUnit.module('generateSidxKey');
QUnit.test('generates correct key', function(assert) {
const sidxInfo = {
byterange: {
offset: 1,
length: 5
},
uri: 'uri'
};
assert.strictEqual(
generateSidxKey(sidxInfo),
'uri-1-5',
'the key byterange should have a inclusive end'
);
});

211
node_modules/mpd-parser/test/toPlaylists.test.js generated vendored Normal file
View file

@ -0,0 +1,211 @@
import {
toPlaylists
} from '../src/toPlaylists';
import QUnit from 'qunit';
QUnit.module('toPlaylists');
QUnit.test('no representations', function(assert) {
assert.deepEqual(toPlaylists([]), []);
});
QUnit.test('pretty simple', function(assert) {
const representations = [{
attributes: { baseUrl: 'http://example.com/', periodIndex: 0, sourceDuration: 2 },
segmentInfo: {
template: { }
}
}];
const playlists = [{
attributes: {
baseUrl: 'http://example.com/',
periodIndex: 0,
sourceDuration: 2,
duration: 2
},
segments: [{
uri: '',
timeline: 0,
duration: 2,
resolvedUri: 'http://example.com/',
map: {
uri: '',
resolvedUri: 'http://example.com/'
},
number: 1
}]
}];
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('segment base', function(assert) {
const representations = [{
attributes: { baseUrl: 'http://example.com/', periodIndex: 0, sourceDuration: 2 },
segmentInfo: {
base: true
}
}];
const playlists = [{
attributes: {
baseUrl: 'http://example.com/',
periodIndex: 0,
sourceDuration: 2,
duration: 2
},
segments: [{
map: {
resolvedUri: 'http://example.com/',
uri: ''
},
resolvedUri: 'http://example.com/',
uri: 'http://example.com/',
timeline: 0,
duration: 2,
number: 0
}]
}];
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('segment base with sidx', function(assert) {
const representations = [{
attributes: {
baseUrl: 'http://example.com/',
periodIndex: 0,
sourceDuration: 2,
indexRange: '10-19'
},
segmentInfo: {
base: true
}
}];
const playlists = [{
attributes: {
baseUrl: 'http://example.com/',
periodIndex: 0,
sourceDuration: 2,
duration: 2,
indexRange: '10-19'
},
segments: [],
sidx: {
map: {
resolvedUri: 'http://example.com/',
uri: ''
},
resolvedUri: 'http://example.com/',
uri: 'http://example.com/',
byterange: {
offset: 10,
length: 10
},
timeline: 0,
duration: 2,
number: 0
}
}];
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('segment list', function(assert) {
const representations = [{
attributes: {
baseUrl: 'http://example.com/',
duration: 10,
sourceDuration: 11,
periodIndex: 0
},
segmentInfo: {
list: {
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}]
}
}
}];
const playlists = [{
attributes: {
baseUrl: 'http://example.com/',
duration: 10,
sourceDuration: 11,
segmentUrls: [{
media: '1.fmp4'
}, {
media: '2.fmp4'
}],
periodIndex: 0
},
segments: [{
duration: 10,
map: {
resolvedUri: 'http://example.com/',
uri: ''
},
resolvedUri: 'http://example.com/1.fmp4',
timeline: 0,
uri: '1.fmp4',
number: 1
}, {
duration: 1,
map: {
resolvedUri: 'http://example.com/',
uri: ''
},
resolvedUri: 'http://example.com/2.fmp4',
timeline: 0,
uri: '2.fmp4',
number: 2
}]
}];
assert.deepEqual(toPlaylists(representations), playlists);
});
QUnit.test('presentationTimeOffset', function(assert) {
const representations = [{
attributes: { baseUrl: 'http://example.com/', periodIndex: 0, sourceDuration: 2 },
segmentInfo: {
template: {
presentationTimeOffset: 100,
timescale: 4
}
}
}];
// the presentationTimeOffset output should be the value in the template
// divided by the timescale in the template.
// It should be available on segments
const playlists = [{
attributes: {
baseUrl: 'http://example.com/',
periodIndex: 0,
sourceDuration: 2,
duration: 2,
presentationTimeOffset: 25,
timescale: 4
},
segments: [{
uri: '',
timeline: 0,
duration: 2,
presentationTimeOffset: 25,
resolvedUri: 'http://example.com/',
map: {
uri: '',
resolvedUri: 'http://example.com/'
},
number: 1
}]
}];
assert.deepEqual(toPlaylists(representations), playlists);
});

219
node_modules/mpd-parser/test/utils.test.js generated vendored Normal file
View file

@ -0,0 +1,219 @@
import { merge, values } from '../src/utils/object';
import { parseDuration } from '../src/utils/time';
import { flatten, range, from, findIndexes } from '../src/utils/list';
import { findChildren, getContent } from '../src/utils/xml';
import {DOMParser} from 'xmldom';
import {JSDOM} from 'jsdom';
import QUnit from 'qunit';
const document = new JSDOM().window.document;
QUnit.module('utils');
QUnit.module('merge');
QUnit.test('empty', function(assert) {
assert.deepEqual(merge({}, { a: 1 }), { a: 1 });
assert.deepEqual(merge({ a: 1 }, { a: 1 }), { a: 1 });
assert.deepEqual(merge({ a: 1 }, {}), { a: 1 });
});
QUnit.test('append', function(assert) {
assert.deepEqual(merge({ a: 1 }, { b: 3 }), { a: 1, b: 3 });
});
QUnit.test('overwrite', function(assert) {
assert.deepEqual(merge({ a: 1 }, { a: 2 }), { a: 2 });
});
QUnit.test('empty', function(assert) {
assert.deepEqual(merge({}, {}), {});
assert.deepEqual(merge({}, 1), {});
assert.deepEqual(merge(1, {}), {});
});
QUnit.test('Test for checking the merge when multiple segment Information are present', function(assert) {
const adaptationSetInfo = {
base: { duration: '10'}
};
const representationInfo = {
base: { duration: '25', indexRange: '230-252'}
};
const expected = {
base: { duration: '25', indexRange: '230-252'}
};
assert.deepEqual(
merge(adaptationSetInfo, representationInfo), expected,
'Merged SegmentBase info'
);
});
QUnit.test('Test for checking the merge when segment Information is present at a level and is undefined at another', function(assert) {
const periodInfo = {
base: {
initialization: {
range: '0-8888'
}
}
};
const adaptationSetInfo = {
base: { duration: '10', indexRange: '230-252'}
};
const representationInfo = {};
const expected = {
base: { duration: '10', indexRange: '230-252', initialization: {range: '0-8888'}}
};
assert.deepEqual(
merge(periodInfo, adaptationSetInfo, representationInfo), expected,
'Merged SegmentBase info'
);
});
QUnit.module('values');
QUnit.test('empty', function(assert) {
assert.deepEqual(values({}), []);
});
QUnit.test('mixed', function(assert) {
assert.deepEqual(values({ a: 1, b: true, c: 'foo'}), [1, true, 'foo']);
});
QUnit.module('flatten');
QUnit.test('empty', function(assert) {
assert.deepEqual(flatten([]), []);
});
QUnit.test('one item', function(assert) {
assert.deepEqual(flatten([[1]]), [1]);
});
QUnit.test('multiple items', function(assert) {
assert.deepEqual(flatten([[1], [2], [3]]), [1, 2, 3]);
});
QUnit.test('multiple multiple items', function(assert) {
assert.deepEqual(flatten([[1], [2, 3], [4]]), [1, 2, 3, 4]);
});
QUnit.test('nested nests', function(assert) {
assert.deepEqual(flatten([[1], [[2]]]), [1, [2]]);
});
QUnit.test('not a list of lists', function(assert) {
assert.deepEqual(flatten([1, 2]), [1, 2]);
assert.deepEqual(flatten([[1], 2]), [1, 2]);
});
QUnit.module('parseDuration');
QUnit.test('full date', function(assert) {
assert.deepEqual(parseDuration('P10Y10M10DT10H10M10.1S'), 342180610.1);
});
QUnit.test('time only', function(assert) {
assert.deepEqual(parseDuration('PT10H10M10.1S'), 36610.1);
});
QUnit.test('empty', function(assert) {
assert.deepEqual(parseDuration(''), 0);
});
QUnit.test('invalid', function(assert) {
assert.deepEqual(parseDuration('foo'), 0);
});
QUnit.module('range');
QUnit.test('simple', function(assert) {
assert.deepEqual(range(1, 4), [1, 2, 3]);
});
QUnit.test('single number range', function(assert) {
assert.deepEqual(range(1, 1), []);
});
QUnit.test('negative', function(assert) {
assert.deepEqual(range(-1, 2), [-1, 0, 1]);
});
QUnit.module('from');
QUnit.test('simple array', function(assert) {
assert.deepEqual(from([1]), [1]);
});
QUnit.test('empty array', function(assert) {
assert.deepEqual(from([]), []);
});
QUnit.test('non-array', function(assert) {
assert.deepEqual(from(1), []);
});
QUnit.test('array-like', function(assert) {
const fixture = document.createElement('div');
fixture.innerHTML = '<div></div><div></div>';
const result = from(fixture.getElementsByTagName('div'));
assert.ok(result.map);
assert.deepEqual(result.length, 2);
});
QUnit.module('findIndexes');
QUnit.test('index not found', function(assert) {
assert.deepEqual(findIndexes([], 'a'), []);
assert.deepEqual(findIndexes([], ''), []);
assert.deepEqual(findIndexes([{ a: true}], 'b'), []);
});
QUnit.test('indexes found', function(assert) {
assert.deepEqual(findIndexes([{ a: true}], 'a'), [0]);
assert.deepEqual(findIndexes([
{ a: true },
{ b: true },
{ b: true, c: true }
], 'b'), [1, 2]);
});
QUnit.module('xml', {
beforeEach() {
const parser = new DOMParser();
const xmlString = `
<fix>
<test>foo </test>
<div>bar</div>
<div>baz</div>
</fix>`;
this.fixture = parser.parseFromString(xmlString, 'text/xml').documentElement;
}
});
QUnit.test('findChildren', function(assert) {
assert.deepEqual(findChildren(this.fixture, 'test').length, 1, 'single');
assert.deepEqual(findChildren(this.fixture, 'div').length, 2, 'multiple');
assert.deepEqual(findChildren(this.fixture, 'el').length, 0, 'none');
});
QUnit.test('getContent', function(assert) {
const result = findChildren(this.fixture, 'test')[0];
assert.deepEqual(getContent(result), 'foo', 'gets text and trims');
});