Bump @justinribeiro/lite-youtube from 0.9.0 to 0.9.1 in /build/javascript (#273)

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript (#265)

* Trying a new github workflow to install javascript packages

* Bump tailwindcss from 1.9.2 to 1.9.4 in /build/javascript (#266)

Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 1.9.2 to 1.9.4.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v1.9.2...v1.9.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript

Bumps [preact](https://github.com/preactjs/preact) from 10.5.4 to 10.5.5.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.5.4...10.5.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Owncast <owncast@owncast.online>

* Bump @justinribeiro/lite-youtube in /build/javascript

Bumps [@justinribeiro/lite-youtube](https://github.com/justinribeiro/lite-youtube) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/justinribeiro/lite-youtube/releases)
- [Commits](https://github.com/justinribeiro/lite-youtube/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
dependabot[bot]
2020-10-20 15:15:56 -07:00
committed by GitHub
parent fb4a822cd8
commit dab7914eab
6133 changed files with 546543 additions and 1108 deletions

168
build/javascript/node_modules/mpd-parser/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,168 @@
<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)_
--------------------

View File

@@ -0,0 +1,30 @@
# 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.
### 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
build/javascript/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.

164
build/javascript/node_modules/mpd-parser/README.md generated vendored Normal file
View File

@@ -0,0 +1,164 @@
# 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
```
The npm installation is preferred, but Bower works, too.
```sh
bower install --save mpd-parser
```
## Usage
```js
var manifest = [
'<>',
'<>',
].join('\n');
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
```
### 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

48
build/javascript/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 id="test/debug.html">Run unit tests in browser.</a></li>
<li><a id="docs" 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, url.value);
console.log('Parsed ->');
console.log(parsedMpd);
}).catch(error => console.error(error));
return false;
});
}(window, window.mpdParser));
</script>
</body>
</html>

View File

@@ -0,0 +1,36 @@
<a name="1.3.0"></a>
# [1.3.0](https://github.com/videojs/vhs-utils/compare/v1.2.1...v1.3.0) (2020-02-05)
### Features
* add forEachMediaGroup in media-groups module (#8) ([a1eacf4](https://github.com/videojs/vhs-utils/commit/a1eacf4)), closes [#8](https://github.com/videojs/vhs-utils/issues/8)
<a name="1.2.1"></a>
## [1.2.1](https://github.com/videojs/vhs-utils/compare/v1.2.0...v1.2.1) (2020-01-15)
### Bug Fixes
* include videojs in VHS JSON media type (#7) ([da072f0](https://github.com/videojs/vhs-utils/commit/da072f0)), closes [#7](https://github.com/videojs/vhs-utils/issues/7)
<a name="1.2.0"></a>
# [1.2.0](https://github.com/videojs/vhs-utils/compare/v1.1.0...v1.2.0) (2019-12-06)
### Features
* add media-types module with simpleTypeFromSourceType function (#4) ([d3ebd3f](https://github.com/videojs/vhs-utils/commit/d3ebd3f)), closes [#4](https://github.com/videojs/vhs-utils/issues/4)
* add VHS codec parsing and translation functions (#5) ([4fe0e22](https://github.com/videojs/vhs-utils/commit/4fe0e22)), closes [#5](https://github.com/videojs/vhs-utils/issues/5)
<a name="1.1.0"></a>
# [1.1.0](https://github.com/videojs/stream/compare/v1.0.0...v1.1.0) (2019-08-30)
### Features
* node support and more stream tests ([315ab8d](https://github.com/videojs/stream/commit/315ab8d))
<a name="1.0.0"></a>
# 1.0.0 (2019-08-21)
### Features
* clones from mpd-parser, m3u8-parser, mux.js, aes-decrypter, and vhs ([5e89042](https://github.com/videojs/stream/commit/5e89042))

View File

@@ -0,0 +1,30 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have Node.js 8 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.
### 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

View File

@@ -0,0 +1,19 @@
Copyright (c) brandonocasey <brandonocasey@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,29 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [@videojs/vhs-utils](#videojsvhs-utils)
- [Installation](#installation)
- [Usage](#usage)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# @videojs/vhs-utils
vhs-utils serves two purposes:
1. It extracts objects and functions shared throughout @videojs/http-streaming code to save on package size. See [the original @videojs/http-streaming PR](https://github.com/videojs/http-streaming/pull/637) for details.
2. It exports generic functions from VHS that may be useful to plugin authors.
## Installation
```sh
npm install --save @videojs/vhs-utils
```
## Usage
All utility functions are published under dist and can be required/imported like so:
`import resolveUrl from '@videojs/vhs-utils/dist/resolve-url';`
`const resolveUrl = require('@videojs/vhs-utils/dist/resolve-url');`

View File

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-stream Demo</title>
<link href="node_modules/video.js/dist/video-js.css" rel="stylesheet">
</head>
<body>
<video id="videojs-stream-player" class="video-js vjs-default-skin" controls>
<source src="//vjs.zencdn.net/v/oceans.mp4" type='video/mp4'>
<source src="//vjs.zencdn.net/v/oceans.webm" type='video/webm'>
</video>
<ul>
<li><a href="/test/debug.html">Run unit tests in browser.</a></li>
</ul>
<script src="node_modules/video.js/dist/video.js"></script>
<script src="dist/videojs-stream.js"></script>
<script>
(function(window, videojs) {
var examplePlayer = window.examplePlayer = videojs('videojs-stream-player');
var stream = window.stream = examplePlayer.stream();
}(window, window.videojs));
</script>
</body>
</html>

View File

@@ -0,0 +1,120 @@
{
"_args": [
[
"@videojs/vhs-utils@1.3.0",
"/home/runner/work/owncast/owncast/build/javascript"
]
],
"_from": "@videojs/vhs-utils@1.3.0",
"_id": "@videojs/vhs-utils@1.3.0",
"_inBundle": false,
"_integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==",
"_location": "/mpd-parser/@videojs/vhs-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@videojs/vhs-utils@1.3.0",
"name": "@videojs/vhs-utils",
"escapedName": "@videojs%2fvhs-utils",
"scope": "@videojs",
"rawSpec": "1.3.0",
"saveSpec": null,
"fetchSpec": "1.3.0"
},
"_requiredBy": [
"/mpd-parser"
],
"_resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz",
"_spec": "1.3.0",
"_where": "/home/runner/work/owncast/owncast/build/javascript",
"author": {
"name": "brandonocasey",
"email": "brandonocasey@gmail.com"
},
"browserslist": [
"defaults",
"ie 11"
],
"dependencies": {
"@babel/runtime": "^7.5.5",
"global": "^4.3.2",
"url-toolkit": "^2.1.6"
},
"description": "Objects and functions shared throughtout @videojs/http-streaming code",
"devDependencies": {
"@videojs/generator-helpers": "~1.2.0",
"karma": "^4.0.0",
"rollup": "^1.12.0",
"sinon": "^7.2.2",
"videojs-generate-karma-config": "~5.3.0",
"videojs-generate-rollup-config": "~5.0.0",
"videojs-generator-verify": "~1.2.0",
"videojs-standard": "^8.0.3"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"generator-videojs-plugin": {
"version": "7.7.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"keywords": [
"videojs",
"videojs-plugin"
],
"license": "MIT",
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"doctoc --notitle",
"git add"
]
},
"name": "@videojs/vhs-utils",
"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",
"posttest": "shx cat test/dist/coverage/text.txt",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check",
"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.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": "1.3.0",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
}
}

View File

@@ -0,0 +1,12 @@
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!
};

View File

@@ -0,0 +1,42 @@
const generate = require('videojs-generate-rollup-config');
const fs = require('fs');
const path = require('path');
const BASE_DIR = path.join(__dirname, '..');
const SRC_DIR = path.join(BASE_DIR, 'src');
const files = fs.readdirSync(SRC_DIR);
const shared = {
externals(defaults) {
defaults.module.push('url-toolkit');
return defaults;
}
};
const builds = [];
files.forEach(function(file, i) {
const config = generate(Object.assign({}, shared, {
input: path.relative(BASE_DIR, path.join(SRC_DIR, file)),
distName: path.basename(file, path.extname(file))
}));
// gaurd against test only builds
if (config.builds.module) {
const module = config.builds.module;
module.output = module.output.filter((o) => o.format === 'cjs');
module.output[0].file = module.output[0].file.replace('.cjs.js', '.js');
builds.push(module);
}
// gaurd against production only builds
// only add the last test bundle we generate as they are all the same
if (i === (files.length - 1) && config.builds.test) {
builds.push(config.builds.test);
}
});
// export the builds to rollup
export default builds;

View File

@@ -0,0 +1,130 @@
/**
* Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
* `avc1.<hhhhhh>`
*
* @param {string} codec
* Codec string to translate
* @return {string}
* The translated codec string
*/
export const translateLegacyCodec = function(codec) {
if (!codec) {
return codec;
}
return codec.replace(/avc1\.(\d+)\.(\d+)/i, function(orig, profile, avcLevel) {
const profileHex = ('00' + Number(profile).toString(16)).slice(-2);
const avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
return 'avc1.' + profileHex + '00' + avcLevelHex;
});
};
/**
* Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
* `avc1.<hhhhhh>`
*
* @param {string[]} codecs
* An array of codec strings to translate
* @return {string[]}
* The translated array of codec strings
*/
export const translateLegacyCodecs = function(codecs) {
return codecs.map(translateLegacyCodec);
};
/**
* Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
* standard `avc1.<hhhhhh>`.
*
* @param {string} codecString
* The codec string
* @return {string}
* The codec string with old apple-style codecs replaced
*
* @private
*/
export const mapLegacyAvcCodecs = function(codecString) {
return codecString.replace(/avc1\.(\d+)\.(\d+)/i, (match) => {
return translateLegacyCodecs([match])[0];
});
};
/**
* @typedef {Object} ParsedCodecInfo
* @property {number} codecCount
* Number of codecs parsed
* @property {string} [videoCodec]
* Parsed video codec (if found)
* @property {string} [videoObjectTypeIndicator]
* Video object type indicator (if found)
* @property {string|null} audioProfile
* Audio profile
*/
/**
* Parses a codec string to retrieve the number of codecs specified, the video codec and
* object type indicator, and the audio profile.
*
* @param {string} [codecs]
* The codec string to parse
* @return {ParsedCodecInfo}
* Parsed codec info
*/
export const parseCodecs = function(codecs = '') {
const result = {
codecCount: 0
};
result.codecCount = codecs.split(',').length;
result.codecCount = result.codecCount || 2;
// parse the video codec
const parsed = (/(^|\s|,)+(avc[13])([^ ,]*)/i).exec(codecs);
if (parsed) {
result.videoCodec = parsed[2];
result.videoObjectTypeIndicator = parsed[3];
}
// parse the last field of the audio codec
result.audioProfile =
(/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs);
result.audioProfile = result.audioProfile && result.audioProfile[2];
return result;
};
/**
* Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
* a default alternate audio playlist for the provided audio group.
*
* @param {Object} master
* The master playlist
* @param {string} audioGroupId
* ID of the audio group for which to find the default codec info
* @return {ParsedCodecInfo}
* Parsed codec info
*/
export const audioProfileFromDefault = (master, audioGroupId) => {
if (!master.mediaGroups.AUDIO || !audioGroupId) {
return null;
}
const audioGroup = master.mediaGroups.AUDIO[audioGroupId];
if (!audioGroup) {
return null;
}
for (const name in audioGroup) {
const audioType = audioGroup[name];
if (audioType.default && audioType.playlists) {
// codec should be the same for all playlists within the audio type
return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
}
}
return null;
};

View File

@@ -0,0 +1,13 @@
import window from 'global/window';
const atob = (s) => window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
export default function decodeB64ToUint8Array(b64Text) {
const decodedString = atob(b64Text);
const array = new Uint8Array(decodedString.length);
for (let i = 0; i < decodedString.length; i++) {
array[i] = decodedString.charCodeAt(i);
}
return array;
}

View File

@@ -0,0 +1,22 @@
/**
* Loops through all supported media groups in master and calls the provided
* callback for each group
*
* @param {Object} master
* The parsed master manifest object
* @param {string[]} groups
* The media groups to call the callback for
* @param {Function} callback
* Callback to call for each media group
*/
export const forEachMediaGroup = (master, groups, callback) => {
groups.forEach((mediaType) => {
for (const groupKey in master.mediaGroups[mediaType]) {
for (const labelKey in master.mediaGroups[mediaType][groupKey]) {
const mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
callback(mediaProperties, mediaType, groupKey, labelKey);
}
}
});
};

View File

@@ -0,0 +1,36 @@
const MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
const DASH_REGEX = /^application\/dash\+xml/i;
/**
* Returns a string that describes the type of source based on a video source object's
* media type.
*
* @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
*
* @param {string} type
* Video source object media type
* @return {('hls'|'dash'|'vhs-json'|null)}
* VHS source type string
*/
export const simpleTypeFromSourceType = (type) => {
if (MPEGURL_REGEX.test(type)) {
return 'hls';
}
if (DASH_REGEX.test(type)) {
return 'dash';
}
// Denotes the special case of a manifest object passed to http-streaming instead of a
// source URL.
//
// See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
//
// In this case, vnd stands for vendor, video.js for the organization, VHS for this
// project, and the +json suffix identifies the structure of the media type.
if (type === 'application/vnd.videojs.vhs+json') {
return 'vhs-json';
}
return null;
};

View File

@@ -0,0 +1,18 @@
import URLToolkit from 'url-toolkit';
import window from 'global/window';
const resolveUrl = (baseUrl, relativeUrl) => {
// return early if we don't need to resolve
if ((/^[a-z]+:/i).test(relativeUrl)) {
return relativeUrl;
}
// if the base URL is relative then combine with the current location
if (!(/\/\//i).test(baseUrl)) {
baseUrl = URLToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
}
return URLToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
};
export default resolveUrl;

View File

@@ -0,0 +1,108 @@
/**
* @file stream.js
*/
/**
* A lightweight readable stream implemention that handles event dispatching.
*
* @class Stream
*/
export default class Stream {
constructor() {
this.listeners = {};
}
/**
* Add a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener the callback to be invoked when an event of
* the specified type occurs
*/
on(type, listener) {
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* Remove a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener a function previously registered for this
* type of event through `on`
* @return {boolean} if we could turn it off or not
*/
off(type, listener) {
if (!this.listeners[type]) {
return false;
}
const index = this.listeners[type].indexOf(listener);
// TODO: which is better?
// In Video.js we slice listener functions
// on trigger so that it does not mess up the order
// while we loop through.
//
// Here we slice on off so that the loop in trigger
// can continue using it's old reference to loop without
// messing up the order.
this.listeners[type] = this.listeners[type].slice(0);
this.listeners[type].splice(index, 1);
return index > -1;
}
/**
* Trigger an event of the specified type on this stream. Any additional
* arguments to this function are passed as parameters to event listeners.
*
* @param {string} type the event name
*/
trigger(type) {
const callbacks = this.listeners[type];
if (!callbacks) {
return;
}
// Slicing the arguments on every invocation of this method
// can add a significant amount of overhead. Avoid the
// intermediate object creation for the common case of a
// single callback argument
if (arguments.length === 2) {
const length = callbacks.length;
for (let i = 0; i < length; ++i) {
callbacks[i].call(this, arguments[1]);
}
} else {
const args = Array.prototype.slice.call(arguments, 1);
const length = callbacks.length;
for (let i = 0; i < length; ++i) {
callbacks[i].apply(this, args);
}
}
}
/**
* Destroys the stream and cleans up.
*/
dispose() {
this.listeners = {};
}
/**
* Forwards all `data` events on this stream to the destination stream. The
* destination stream should provide a method `push` to receive the data
* events as they arrive.
*
* @param {Stream} destination the stream that will receive all `data` events
* @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
*/
pipe(destination) {
this.on('data', function(data) {
destination.push(data);
});
}
}

View File

@@ -0,0 +1,242 @@
import QUnit from 'qunit';
import {
mapLegacyAvcCodecs,
translateLegacyCodecs,
parseCodecs,
audioProfileFromDefault
} from '../src/codecs';
QUnit.module('Legacy Codecs');
QUnit.test('maps legacy AVC codecs', function(assert) {
assert.equal(
mapLegacyAvcCodecs('avc1.deadbeef'),
'avc1.deadbeef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef, mp4a.something'),
'avc1.dead.beef, mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef,mp4a.something'),
'avc1.dead.beef,mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something,avc1.dead.beef'),
'mp4a.something,avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something, avc1.dead.beef'),
'mp4a.something, avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.42001e'),
'avc1.42001e',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.4d0020,mp4a.40.2'),
'avc1.4d0020,mp4a.40.2',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2,avc1.4d0020'),
'mp4a.40.2,avc1.4d0020',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.40'),
'mp4a.40.40',
'does nothing for non video codecs'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30'),
'avc1.42001e',
'translates legacy video codec alone'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30, mp4a.40.2'),
'avc1.42001e, mp4a.40.2',
'translates legacy video codec when paired with audio'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2, avc1.66.30'),
'mp4a.40.2, avc1.42001e',
'translates video codec when specified second'
);
});
QUnit.test('translates legacy codecs', function(assert) {
assert.deepEqual(
translateLegacyCodecs(['avc1.66.30', 'avc1.66.30']),
['avc1.42001e', 'avc1.42001e'],
'translates legacy avc1.66.30 codec'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.42C01E']),
['avc1.42C01E', 'avc1.42C01E'],
'does not translate modern codecs'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.66.30']),
['avc1.42C01E', 'avc1.42001e'],
'only translates legacy codecs when mixed'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.4d0020', 'avc1.100.41', 'avc1.77.41',
'avc1.77.32', 'avc1.77.31', 'avc1.77.30',
'avc1.66.30', 'avc1.66.21', 'avc1.42C01e']),
['avc1.4d0020', 'avc1.640029', 'avc1.4d0029',
'avc1.4d0020', 'avc1.4d001f', 'avc1.4d001e',
'avc1.42001e', 'avc1.420015', 'avc1.42C01e'],
'translates a whole bunch'
);
});
QUnit.module('parseCodecs');
QUnit.test('parses video only codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e'),
{
codecCount: 1,
videoCodec: 'avc1',
videoObjectTypeIndicator: '.42001e',
audioProfile: null
},
'parsed video only codec string'
);
});
QUnit.test('parses audio only codec string', function(assert) {
assert.deepEqual(
parseCodecs('mp4a.40.2'),
{
codecCount: 1,
audioProfile: '2'
},
'parsed audio only codec string'
);
});
QUnit.test('parses video and audio codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e, mp4a.40.2'),
{
codecCount: 2,
videoCodec: 'avc1',
videoObjectTypeIndicator: '.42001e',
audioProfile: '2'
},
'parsed video and audio codec string'
);
});
QUnit.module('audioProfileFromDefault');
QUnit.test('returns falsey when no audio group ID', function(assert) {
assert.notOk(
audioProfileFromDefault(
{ mediaGroups: { AUDIO: {} } },
'',
),
'returns falsey when no audio group ID'
);
});
QUnit.test('returns falsey when no matching audio group', function(assert) {
assert.notOk(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au2'
),
'returned falsey when no matching audio group'
);
});
QUnit.test('returns falsey when no default for audio group', function(assert) {
assert.notOk(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
'returned falsey when no default for audio group'
);
});
QUnit.test('returns audio profile for default in audio group', function(assert) {
assert.deepEqual(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
'5',
'returned parsed codec audio profile'
);
});

View File

@@ -0,0 +1,13 @@
import QUnit from 'qunit';
import decodeB64ToUint8Array from '../src/decode-b64-to-uint8-array.js';
QUnit.module('decodeB64ToUint8Array');
// slightly modified version of m3u8 test
// 'parses Widevine #EXT-X-KEY attributes and attaches to manifest'
QUnit.test('can decode', function(assert) {
const b64 = 'AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnNoYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY';
const result = decodeB64ToUint8Array(b64);
assert.deepEqual(result.byteLength, 62, 'decoded');
});

View File

@@ -0,0 +1,171 @@
import QUnit from 'qunit';
import { forEachMediaGroup } from '../src/media-groups';
QUnit.module('forEachMediaGroup');
QUnit.test('runs callback for each audio media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated audio media groups'
);
});
QUnit.test('runs callback for each subtitle media group', function(assert) {
const master = {
mediaGroups: {
SUBTITLES: {
sub1: {
en: { en: 'en' },
es: { es: 'es' }
},
sub2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated subtitles media groups'
);
});
QUnit.test('runs callback for each audio and subtitles media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
SUBTITLES: {
sub1: {
enS: { enS: 'enS' },
esS: { esS: 'esS' }
},
sub2: {
deS: { deS: 'deS' },
frS: { frS: 'frS' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' },
{ enS: 'enS' },
{ esS: 'esS' },
{ deS: 'deS' },
{ frS: 'frS' }
],
'iterated audio and subtitles media groups'
);
});
QUnit.test('does not run callback for non specified media groups', function(assert) {
const master = {
mediaGroups: {
'VIDEO': { v1: { en: { en: 'en' } } },
'CLOSED-CAPTIONS': { cc1: { en: { en: 'en' } } }
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(iteratedMediaGroups, [], 'did not iterate non specified media groups');
});

View File

@@ -0,0 +1,41 @@
import QUnit from 'qunit';
import { simpleTypeFromSourceType } from '../src/media-types';
QUnit.module('simpleTypeFromSourceType');
QUnit.test('simpleTypeFromSourceType converts HLS mime types to hls', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/x-MPegUrl'),
'hls',
'supports application/x-mpegurl'
);
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
'hls',
'supports application/vnd.apple.mpegurl'
);
});
QUnit.test('simpleTypeFromSourceType converts DASH mime type to dash', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplication/dAsh+xMl'),
'dash',
'supports application/dash+xml'
);
});
QUnit.test(
'simpleTypeFromSourceType does not convert non HLS/DASH mime types',
function(assert) {
assert.notOk(simpleTypeFromSourceType('video/mp4'), 'does not support video/mp4');
assert.notOk(simpleTypeFromSourceType('video/x-flv'), 'does not support video/x-flv');
}
);
QUnit.test('simpleTypeFromSourceType converts VHS media type to vhs-json', function(assert) {
assert.equal(
simpleTypeFromSourceType('application/vnd.videojs.vhs+json'),
'vhs-json',
'supports application/vnd.videojs.vhs+json'
);
});

View File

@@ -0,0 +1,28 @@
import QUnit from 'qunit';
import window from 'global/window';
import resolveUrl from '../src/resolve-url';
// A modified subset of tests from https://github.com/tjenkinson/url-toolkit
QUnit.module('URL resolver');
QUnit.test('works with a selection of valid urls', function(assert) {
let currentLocation = '';
if (window.location && window.location.protocol) {
currentLocation = window.location.protocol + '//' + window.location.host;
}
assert.equal(
resolveUrl('http://a.com/b/cd/e.m3u8', 'https://example.com/z.ts'),
'https://example.com/z.ts'
);
assert.equal(resolveUrl('http://a.com/b/cd/e.m3u8', 'z.ts'), 'http://a.com/b/cd/z.ts');
assert.equal(resolveUrl('//a.com/b/cd/e.m3u8', 'z.ts'), '//a.com/b/cd/z.ts');
assert.equal(
resolveUrl('/a/b/cd/e.m3u8', 'https://example.com:8080/z.ts'),
'https://example.com:8080/z.ts'
);
assert.equal(resolveUrl('/a/b/cd/e.m3u8', 'z.ts'), currentLocation + '/a/b/cd/z.ts');
assert.equal(resolveUrl('/a/b/cd/e.m3u8', '../../../z.ts'), currentLocation + '/z.ts');
});

View File

@@ -0,0 +1,51 @@
import QUnit from 'qunit';
import Stream from '../src/stream';
QUnit.module('stream', {
beforeEach() {
this.stream = new Stream();
},
afterEach() {
this.stream.dispose();
}
});
QUnit.test('trigger calls listeners', function(assert) {
const args = [];
this.stream.on('test', function(data) {
args.push(data);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args, [1, 2]);
});
QUnit.test('callbacks can remove themselves', function(assert) {
const args1 = [];
const args2 = [];
const args3 = [];
const arg2Fn = (event) => {
args2.push(event);
this.stream.off('test', arg2Fn);
};
this.stream.on('test', (event) => {
args1.push(event);
});
this.stream.on('test', arg2Fn);
this.stream.on('test', (event) => {
args3.push(event);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args1, [1, 2], 'first callback ran all times');
assert.deepEqual(args2, [1], 'second callback removed after first run');
assert.deepEqual(args3, [1, 2], 'third callback ran all times');
});

133
build/javascript/node_modules/mpd-parser/package.json generated vendored Normal file
View File

@@ -0,0 +1,133 @@
{
"_args": [
[
"mpd-parser@0.12.0",
"/home/runner/work/owncast/owncast/build/javascript"
]
],
"_from": "mpd-parser@0.12.0",
"_id": "mpd-parser@0.12.0",
"_inBundle": false,
"_integrity": "sha512-Ov5Oz9bw5X/G8V/6PlO+rHuqKywYYjQ6USyv8fqFMs413HkrzlpDjgUKSBD7C+/J19ID5mWtxzrpMf4Yp++iZg==",
"_location": "/mpd-parser",
"_phantomChildren": {
"@babel/runtime": "7.11.2",
"global": "4.4.0",
"url-toolkit": "2.2.0"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "mpd-parser@0.12.0",
"name": "mpd-parser",
"escapedName": "mpd-parser",
"rawSpec": "0.12.0",
"saveSpec": null,
"fetchSpec": "0.12.0"
},
"_requiredBy": [
"/@videojs/http-streaming"
],
"_resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.12.0.tgz",
"_spec": "0.12.0",
"_where": "/home/runner/work/owncast/owncast/build/javascript",
"author": {
"name": "Brightcove, Inc"
},
"browserslist": [
"defaults",
"ie 11"
],
"bugs": {
"url": "https://github.com/videojs/mpd-parser/issues"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@videojs/vhs-utils": "^1.1.0",
"global": "^4.3.2",
"xmldom": "^0.1.27"
},
"description": "mpd parser",
"devDependencies": {
"@videojs/generator-helpers": "~1.2.0",
"jsdom": "^15.1.1",
"karma": "^4.0.0",
"rollup": "^1.19.4",
"rollup-plugin-string": "^2.0.2",
"sinon": "^7.2.2",
"videojs-generate-karma-config": "~5.3.1",
"videojs-generate-rollup-config": "~5.0.1",
"videojs-generator-verify": "~2.0.0",
"videojs-standard": "^8.0.3"
},
"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",
"git add"
],
"README.md": [
"doctoc --notitle",
"git add"
]
},
"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.12.0",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
}
}

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!
};

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)));

View File

@@ -0,0 +1,56 @@
const generate = require('videojs-generate-rollup-config');
const string = require('rollup-plugin-string');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
const options = {
input: 'src/index.js',
plugins(defaults) {
defaults.test.unshift('string');
return defaults;
},
primedPlugins(defaults) {
defaults.string = string({include: ['test/manifests/*.mpd']});
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);

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'
};

36
build/javascript/node_modules/mpd-parser/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,36 @@
import { version } from '../package.json';
import { toM3u8 } from './toM3u8';
import { toPlaylists } from './toPlaylists';
import { inheritAttributes } from './inheritAttributes';
import { stringToMpdXml } from './stringToMpdXml';
import { parseUTCTimingScheme } from './parseUTCTimingScheme';
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
};

View File

@@ -0,0 +1,335 @@
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/dist/resolve-url';
import decodeB64ToUint8Array from '@videojs/vhs-utils/dist/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;
}, {});
};
/**
* 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 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)))
};
};

View File

@@ -0,0 +1,254 @@
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 aprsed timescale
*/
timescale(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;
}, {});
};

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,132 @@
import { range } from '../utils/list';
/**
* 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;
return {
start: 0,
end: 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 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: 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;
};

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 addSegmentsToPlaylist = (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;
};

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;
};

View File

@@ -0,0 +1,170 @@
import resolveUrl from '@videojs/vhs-utils/dist/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);
return {
uri,
timeline: segment.timeline,
duration: segment.duration,
resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
map: mapSegment,
number: segment.number
};
});
};

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;
};

View File

@@ -0,0 +1,59 @@
import resolveUrl from '@videojs/vhs-utils/dist/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;

View File

@@ -0,0 +1,20 @@
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();
const xml = parser.parseFromString(manifestString, 'application/xml');
const mpd = xml && xml.documentElement.tagName === 'MPD' ?
xml.documentElement : null;
if (!mpd || mpd &&
mpd.getElementsByTagName('parsererror').length > 0) {
throw new Error(errors.DASH_INVALID_XML);
}
return mpd;
};

295
build/javascript/node_modules/mpd-parser/src/toM3u8.js generated vendored Normal file
View File

@@ -0,0 +1,295 @@
import { values } from './utils/object';
import { findIndexes } from './utils/list';
import { addSegmentsToPlaylist } from './segment/segmentBase';
import { byteRangeToString } from './segment/urlType';
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;
});
};
const addSegmentInfoFromSidx = (playlists, sidxMapping = {}) => {
if (!Object.keys(sidxMapping).length) {
return playlists;
}
for (const i in playlists) {
const playlist = playlists[i];
if (!playlist.sidx) {
continue;
}
const sidxKey = playlist.sidx.uri + '-' +
byteRangeToString(playlist.sidx.byterange);
const sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
if (playlist.sidx && sidxMatch) {
addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
}
}
return playlists;
};
export const formatAudioPlaylist = ({ attributes, segments, sidx }) => {
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;
}
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;
}
return {
attributes: {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
['PROGRAM-ID']: 1
},
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 = {}) => {
let mainPlaylist;
const formattedPlaylists = playlists.reduce((a, playlist) => {
const role = playlist.attributes.role &&
playlist.attributes.role.value || '';
const language = playlist.attributes.lang || '';
let label = 'main';
if (language) {
const roleLabel = role ? ` (${role})` : '';
label = `${playlist.attributes.lang}${roleLabel}`;
}
// skip if we already have the highest quality audio for a language
if (a[label] &&
a[label].playlists[0].attributes.BANDWIDTH >
playlist.attributes.bandwidth) {
return a;
}
a[label] = {
language,
autoselect: true,
default: role === 'main',
playlists: addSegmentInfoFromSidx(
[formatAudioPlaylist(playlist)],
sidxMapping
),
uri: ''
};
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';
// skip if we already have subtitles
if (a[label]) {
return a;
}
a[label] = {
language: label,
default: false,
autoselect: false,
playlists: addSegmentInfoFromSidx(
[formatVttPlaylist(playlist)],
sidxMapping
),
uri: ''
};
return a;
}, {});
};
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;
};
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 videoOnly = ({ attributes }) =>
attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
const audioOnly = ({ attributes }) =>
attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
const vttOnly = ({ attributes }) =>
attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
const vttPlaylists = dashPlaylists.filter(vttOnly);
const master = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
AUDIO: {},
VIDEO: {},
['CLOSED-CAPTIONS']: {},
SUBTITLES: {}
},
uri: '',
duration,
playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping)
};
if (minimumUpdatePeriod >= 0) {
master.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
}
if (locations) {
master.locations = locations;
}
if (type === 'dynamic') {
master.suggestedPresentationDelay = suggestedPresentationDelay;
}
if (audioPlaylists.length) {
master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
}
if (vttPlaylists.length) {
master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
}
return master;
};

View File

@@ -0,0 +1,60 @@
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);
} 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);

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;
}, []);

View File

@@ -0,0 +1,23 @@
const isObject = (obj) => {
return !!obj && typeof obj === 'object';
};
export const merge = (...objects) => {
return objects.reduce((result, source) => {
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]);

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);
};

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();

View File

@@ -0,0 +1,78 @@
import { parse, VERSION } from '../src';
import QUnit from 'qunit';
// manifests
import maatVttSegmentTemplate from './manifests/maat_vtt_segmentTemplate.mpd';
import segmentBaseTemplate from './manifests/segmentBase.mpd';
import segmentListTemplate from './manifests/segmentList.mpd';
import locationTemplate from './manifests/location.mpd';
import locationsTemplate from './manifests/locations.mpd';
import multiperiod from './manifests/multiperiod.mpd';
import multiperiodDynamic from './manifests/multiperiod-dynamic.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 multiperiodManifest
} from './manifests/multiperiod.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';
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: 'multiperiod',
input: multiperiod,
expected: multiperiodManifest
}, {
name: 'multiperiod_dynamic',
input: multiperiodDynamic,
expected: multiperiodDynamicManifest
}, {
name: 'location',
input: locationTemplate,
expected: locationManifest
}, {
name: 'locations',
input: locationsTemplate,
expected: locationsManifest
}].forEach(({ name, input, expected }) => {
QUnit.test(`${name} test manifest`, function(assert) {
const actual = parse(input);
assert.deepEqual(actual, expected);
});
});

File diff suppressed because it is too large Load Diff

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: ''
};

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>

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: ''
};

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

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>

View File

@@ -0,0 +1,952 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
'en (main)': {
language: 'en',
autoselect: true,
default: true,
playlists: [{
attributes: {
'NAME': 'default_audio128_2',
'BANDWIDTH': 123000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: false,
timeline: 0,
resolvedUri: '',
targetDuration: 2,
segments: [{
uri: 'https://example.com/default_audio128_2/segment0.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment0.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_audio128_2/segment1.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment1.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_audio128_2/segment2.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment2.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_audio128_2/segment3.m4f',
timeline: 1,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment3.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 3,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment4.m4f',
timeline: 1,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment4.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_audio128_2/segment5.m4f',
timeline: 1,
duration: 0.8591383219954648,
resolvedUri: 'https://example.com/default_audio128_2/segment5.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_audio128_2/segment6.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment6.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 6,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment7.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment7.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_audio128_2/segment8.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment8.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_audio128_2/segment9.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment9.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_audio128_2/segment10.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment10.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_audio128_2/segment11.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment11.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_audio128_2/segment12.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment12.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_audio128_2/segment13.m4f',
timeline: 2,
duration: 0.023219954648526078,
resolvedUri: 'https://example.com/default_audio128_2/segment13.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 13
}, {
uri: 'https://example.com/default_audio128_2/segment14.m4f',
timeline: 3,
duration: 1.1609977324263039,
resolvedUri: 'https://example.com/default_audio128_2/segment14.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init3.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init3.m4f'
},
number: 14,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment15.m4f',
timeline: 3,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment15.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init3.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_audio128_2/segment16.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment16.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment17.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment17.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_audio128_2/segment18.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment18.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_audio128_2/segment19.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment19.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 19
}],
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': {}
},
uri: '',
duration: 36.269,
playlists: [{
attributes: {
'NAME': 'default_video2000_0_1280x720',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'avc1.4d001f',
'BANDWIDTH': 2008E3,
'PROGRAM-ID': 1
},
uri: '',
endList: false,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video2000_0_1280x720/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment0.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment1.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment2.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment3.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 3,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment4.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment5.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment6.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 6,
discontinuity: true
},
{
uri: 'https://example.com/default_video2000_0_1280x720/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment7.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment8.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment9.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment10.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment11.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment12.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment13.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment14.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment15.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment16.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment17.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment18.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment19.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 19
}],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}, {
attributes: {
'NAME': 'default_video1200_1_960x540',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 960,
height: 540
},
'CODECS': 'avc1.4d001f',
'BANDWIDTH': 1195E3,
'PROGRAM-ID': 1
},
uri: '',
endList: false,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video1200_1_960x540/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment0.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment1.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment2.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment3.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 3,
discontinuity: true
},
{
uri: 'https://example.com/default_video1200_1_960x540/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment4.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment5.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment6.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 6,
discontinuity: true
},
{
uri: 'https://example.com/default_video1200_1_960x540/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment7.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment8.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment9.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment10.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment11.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment12.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment13.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment14.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment15.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment16.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment17.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment18.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment19.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 19
}],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}, {
attributes: {
'NAME': 'default_video900_1_640x360',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 640,
height: 360
},
'CODECS': 'avc1.4d001e',
'BANDWIDTH': 884E3,
'PROGRAM-ID': 1
},
uri: '',
endList: false,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video900_1_640x360/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment0.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video900_1_640x360/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment1.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video900_1_640x360/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment2.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video900_1_640x360/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment3.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 3,
discontinuity: true
},
{
uri: 'https://example.com/default_video900_1_640x360/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment4.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video900_1_640x360/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment5.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video900_1_640x360/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment6.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 6,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment7.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video900_1_640x360/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment8.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video900_1_640x360/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment9.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video900_1_640x360/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment10.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video900_1_640x360/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment11.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video900_1_640x360/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment12.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video900_1_640x360/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment13.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment14.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video900_1_640x360/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment15.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video900_1_640x360/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment16.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment17.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video900_1_640x360/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment18.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video900_1_640x360/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment19.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 19
}],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}],
suggestedPresentationDelay: 18
};

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>

View File

@@ -0,0 +1,951 @@
export const parsedManifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
'AUDIO': {
audio: {
'en (main)': {
language: 'en',
autoselect: true,
default: true,
playlists: [{
attributes: {
'NAME': 'default_audio128_2',
'BANDWIDTH': 123000,
'CODECS': 'mp4a.40.2',
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 2,
segments: [{
uri: 'https://example.com/default_audio128_2/segment0.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment0.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_audio128_2/segment1.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment1.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_audio128_2/segment2.m4f',
timeline: 0,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment2.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init0.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_audio128_2/segment3.m4f',
timeline: 1,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment3.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 3,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment4.m4f',
timeline: 1,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment4.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_audio128_2/segment5.m4f',
timeline: 1,
duration: 0.8591383219954648,
resolvedUri: 'https://example.com/default_audio128_2/segment5.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init1.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_audio128_2/segment6.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment6.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 6,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment7.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment7.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_audio128_2/segment8.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment8.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_audio128_2/segment9.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment9.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_audio128_2/segment10.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment10.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_audio128_2/segment11.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment11.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_audio128_2/segment12.m4f',
timeline: 2,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment12.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_audio128_2/segment13.m4f',
timeline: 2,
duration: 0.023219954648526078,
resolvedUri: 'https://example.com/default_audio128_2/segment13.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init2.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init2.m4f'
},
number: 13
}, {
uri: 'https://example.com/default_audio128_2/segment14.m4f',
timeline: 3,
duration: 1.1609977324263039,
resolvedUri: 'https://example.com/default_audio128_2/segment14.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init3.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init3.m4f'
},
number: 14,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment15.m4f',
timeline: 3,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment15.m4f',
map: {
uri: 'https://example.com/default_audio128_2/init3.m4f',
resolvedUri: 'https://example.com/default_audio128_2/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_audio128_2/segment16.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment16.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_audio128_2/segment17.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment17.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_audio128_2/segment18.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment18.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_audio128_2/segment19.m4f',
timeline: 4,
duration: 1.9969160997732427,
resolvedUri: 'https://example.com/default_audio128_2/segment19.m4f',
map: {
uri: 'https://example.com/default_audio128_2/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_audio128_2/segment$Number$.m4f'
},
number: 19
}],
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': {}
},
uri: '',
duration: 36.269,
playlists: [{
attributes: {
'NAME': 'default_video2000_0_1280x720',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 1280,
height: 720
},
'CODECS': 'avc1.4d001f',
'BANDWIDTH': 2008E3,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video2000_0_1280x720/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment0.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment1.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment2.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init0.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment3.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 3,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment4.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment5.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init1.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment6.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 6,
discontinuity: true
},
{
uri: 'https://example.com/default_video2000_0_1280x720/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment7.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment8.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment9.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment10.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment11.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment12.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init2.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment13.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment14.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment15.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/init3.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment16.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment17.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment18.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video2000_0_1280x720/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment19.m4f',
map: {
uri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video2000_0_1280x720/segment$Number$.m4f'
},
number: 19
}],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}, {
attributes: {
'NAME': 'default_video1200_1_960x540',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 960,
height: 540
},
'CODECS': 'avc1.4d001f',
'BANDWIDTH': 1195E3,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video1200_1_960x540/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment0.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment1.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment2.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init0.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment3.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 3,
discontinuity: true
},
{
uri: 'https://example.com/default_video1200_1_960x540/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment4.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment5.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init1.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment6.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 6,
discontinuity: true
},
{
uri: 'https://example.com/default_video1200_1_960x540/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment7.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment8.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment9.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment10.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment11.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment12.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init2.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment13.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment14.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment15.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/init3.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment16.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment17.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment18.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video1200_1_960x540/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment19.m4f',
map: {
uri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video1200_1_960x540/segment$Number$.m4f'
},
number: 19
}],
mediaSequence: 0,
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
}
}, {
attributes: {
'NAME': 'default_video900_1_640x360',
'AUDIO': 'audio',
'SUBTITLES': 'subs',
'RESOLUTION': {
width: 640,
height: 360
},
'CODECS': 'avc1.4d001e',
'BANDWIDTH': 884E3,
'PROGRAM-ID': 1
},
uri: '',
endList: true,
timeline: 0,
resolvedUri: '',
targetDuration: 3,
segments: [{
uri: 'https://example.com/default_video900_1_640x360/segment0.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment0.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 0
}, {
uri: 'https://example.com/default_video900_1_640x360/segment1.m4f',
timeline: 0,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment1.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 1
}, {
uri: 'https://example.com/default_video900_1_640x360/segment2.m4f',
timeline: 0,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment2.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init0.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init0.m4f'
},
number: 2
}, {
uri: 'https://example.com/default_video900_1_640x360/segment3.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment3.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 3,
discontinuity: true
},
{
uri: 'https://example.com/default_video900_1_640x360/segment4.m4f',
timeline: 1,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment4.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 4
}, {
uri: 'https://example.com/default_video900_1_640x360/segment5.m4f',
timeline: 1,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment5.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init1.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init1.m4f'
},
number: 5
}, {
uri: 'https://example.com/default_video900_1_640x360/segment6.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment6.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 6,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment7.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment7.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 7
}, {
uri: 'https://example.com/default_video900_1_640x360/segment8.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment8.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 8
}, {
uri: 'https://example.com/default_video900_1_640x360/segment9.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment9.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 9
}, {
uri: 'https://example.com/default_video900_1_640x360/segment10.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment10.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 10
}, {
uri: 'https://example.com/default_video900_1_640x360/segment11.m4f',
timeline: 2,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment11.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 11
}, {
uri: 'https://example.com/default_video900_1_640x360/segment12.m4f',
timeline: 2,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment12.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init2.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init2.m4f'
},
number: 12
}, {
uri: 'https://example.com/default_video900_1_640x360/segment13.m4f',
timeline: 3,
duration: 0.9676333333333333,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment13.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 13,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment14.m4f',
timeline: 3,
duration: 1.9352666666666667,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment14.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 14
}, {
uri: 'https://example.com/default_video900_1_640x360/segment15.m4f',
timeline: 3,
duration: 0.26693333333333336,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment15.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/init3.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/init3.m4f'
},
number: 15
}, {
uri: 'https://example.com/default_video900_1_640x360/segment16.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment16.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 16,
discontinuity: true
}, {
uri: 'https://example.com/default_video900_1_640x360/segment17.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment17.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 17
}, {
uri: 'https://example.com/default_video900_1_640x360/segment18.m4f',
timeline: 4,
duration: 2.002,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment18.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 18
}, {
uri: 'https://example.com/default_video900_1_640x360/segment19.m4f',
timeline: 4,
duration: 1.9686333333333332,
resolvedUri: 'https://example.com/default_video900_1_640x360/segment19.m4f',
map: {
uri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f',
resolvedUri: 'https://example.com/default_video900_1_640x360/segment$Number$.m4f'
},
number: 19
}],
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,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>

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: ''
};

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>

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: ''
};

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>

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,
addSegmentsToPlaylist
} 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 - addSegmentsToPlaylist');
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(addSegmentsToPlaylist(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

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'
);
});

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));
});

View File

@@ -0,0 +1,703 @@
import { toM3u8 } 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
}],
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: ''
}],
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: '1',
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: ''
}],
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: ''
}],
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);
});

View File

@@ -0,0 +1,171 @@
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);
});

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');
});