Multiple audio track support (#681)
* Support for multiple alternate audio tracks * Separated segment loading logic into a reusable piece of functionality so that we can have more than one segment loader and they can manage the segment fetch behavior unique to each playlist they are loading from * Introduced the MasterPlaylistController to coordinate the loading of the master playlist and separate the behavior of the player from videojs-contrib-hls.js leaving the latter to be the glue between HLS and the video element's events * Added the SourceUpdater to manage the asynchronous bahavior of SourceBuffers and MediaSource objects so that we can treat it as a non-blocking work queue * Added parsing for MediaGroups in master playlists * Added support for AudioTrackList objects and events * Add support for every HLS mime type possible (#684) * Flash live fixes (#682)
Showing
93 changed files
with
2975 additions
and
172 deletions
1 | sudo: false | 1 | sudo: required |
2 | dist: trusty | ||
2 | language: node_js | 3 | language: node_js |
3 | addons: | ||
4 | firefox: "latest" | ||
5 | node_js: | 4 | node_js: |
6 | - "stable" | 5 | - "stable" |
7 | notifications: | 6 | notifications: |
... | @@ -14,6 +13,7 @@ notifications: | ... | @@ -14,6 +13,7 @@ notifications: |
14 | use_notice: true | 13 | use_notice: true |
15 | # Set up a virtual screen for Firefox. | 14 | # Set up a virtual screen for Firefox. |
16 | before_script: | 15 | before_script: |
16 | - export CHROME_BIN=/usr/bin/google-chrome | ||
17 | - export DISPLAY=:99.0 | 17 | - export DISPLAY=:99.0 |
18 | - sh -e /etc/init.d/xvfb start | 18 | - sh -e /etc/init.d/xvfb start |
19 | env: | 19 | env: |
... | @@ -22,4 +22,8 @@ env: | ... | @@ -22,4 +22,8 @@ env: |
22 | - secure: AnduYGXka5ft1x7V3SuVYqvlKLvJGhUaRNFdy4UDJr3ZVuwpQjE4TMDG8REmJIJvXfHbh4qY4N1cFSGnXkZ4bH21Xk0v9DLhsxbarKz+X2BvPgXs+Af9EQ6vLEy/5S1vMLxfT5+y+Ec5bVNGOsdUZby8Y21CRzSg6ADN9kwPGlE= | 22 | - secure: AnduYGXka5ft1x7V3SuVYqvlKLvJGhUaRNFdy4UDJr3ZVuwpQjE4TMDG8REmJIJvXfHbh4qY4N1cFSGnXkZ4bH21Xk0v9DLhsxbarKz+X2BvPgXs+Af9EQ6vLEy/5S1vMLxfT5+y+Ec5bVNGOsdUZby8Y21CRzSg6ADN9kwPGlE= |
23 | addons: | 23 | addons: |
24 | sauce_connect: true | 24 | sauce_connect: true |
25 | firefox: latest | 25 | apt: |
26 | sources: | ||
27 | - google-chrome | ||
28 | packages: | ||
29 | - google-chrome-stable | ... | ... |
... | @@ -9,6 +9,8 @@ Play back HLS with video.js, even where it's not natively supported. | ... | @@ -9,6 +9,8 @@ Play back HLS with video.js, even where it's not natively supported. |
9 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* | 9 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* |
10 | 10 | ||
11 | - [Getting Started](#getting-started) | 11 | - [Getting Started](#getting-started) |
12 | - [Known Issues](#known-issues) | ||
13 | - [IE11](#ie11) | ||
12 | - [Documentation](#documentation) | 14 | - [Documentation](#documentation) |
13 | - [Options](#options) | 15 | - [Options](#options) |
14 | - [withCredentials](#withcredentials) | 16 | - [withCredentials](#withcredentials) |
... | @@ -44,7 +46,7 @@ and include it in your page along with video.js: | ... | @@ -44,7 +46,7 @@ and include it in your page along with video.js: |
44 | type="application/x-mpegURL"> | 46 | type="application/x-mpegURL"> |
45 | </video> | 47 | </video> |
46 | <script src="video.js"></script> | 48 | <script src="video.js"></script> |
47 | <script src="videojs-hls.min.js"></script> | 49 | <script src="videojs-contrib-hls.min.js"></script> |
48 | <script> | 50 | <script> |
49 | var player = videojs('example-video'); | 51 | var player = videojs('example-video'); |
50 | player.play(); | 52 | player.play(); |
... | @@ -53,6 +55,16 @@ player.play(); | ... | @@ -53,6 +55,16 @@ player.play(); |
53 | 55 | ||
54 | Check out our [live example](http://videojs.github.io/videojs-contrib-hls/) if you're having trouble. | 56 | Check out our [live example](http://videojs.github.io/videojs-contrib-hls/) if you're having trouble. |
55 | 57 | ||
58 | ## Known Issues | ||
59 | Issues that are currenty know about with workarounds. If you want to | ||
60 | help find a solution that would be appreciated! | ||
61 | |||
62 | ### IE11 | ||
63 | In some IE11 setups there are issues working with it's native HTML | ||
64 | SourceBuffers functionality. This leads to various issues, such as | ||
65 | videos stopping playback with media decode errors. The known workaround | ||
66 | for this issues is to force the player to use flash when running on IE11. | ||
67 | |||
56 | ## Documentation | 68 | ## Documentation |
57 | [HTTP Live Streaming](https://developer.apple.com/streaming/) (HLS) has | 69 | [HTTP Live Streaming](https://developer.apple.com/streaming/) (HLS) has |
58 | become a de-facto standard for streaming video on mobile devices | 70 | become a de-facto standard for streaming video on mobile devices |
... | @@ -89,8 +101,7 @@ are some highlights: | ... | @@ -89,8 +101,7 @@ are some highlights: |
89 | - mid-segment quality switching | 101 | - mid-segment quality switching |
90 | - AES-128 segment encryption | 102 | - AES-128 segment encryption |
91 | - CEA-608 captions are automatically translated into standard HTML5 | 103 | - CEA-608 captions are automatically translated into standard HTML5 |
92 | [caption text | 104 | [caption text tracks][0] |
93 | tracks](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track) | ||
94 | - Timed ID3 Metadata is automatically translated into HTML5 metedata | 105 | - Timed ID3 Metadata is automatically translated into HTML5 metedata |
95 | text tracks | 106 | text tracks |
96 | - Highly customizable adaptive bitrate selection | 107 | - Highly customizable adaptive bitrate selection |
... | @@ -98,6 +109,10 @@ are some highlights: | ... | @@ -98,6 +109,10 @@ are some highlights: |
98 | - Cross-domain credentials support with CORS | 109 | - Cross-domain credentials support with CORS |
99 | - Tight integration with video.js and a philosophy of exposing as much | 110 | - Tight integration with video.js and a philosophy of exposing as much |
100 | as possible with standard HTML APIs | 111 | as possible with standard HTML APIs |
112 | - Stream with multiple audio tracks and switching to those audio tracks | ||
113 | (see the docs folder) for info | ||
114 | |||
115 | [0]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track | ||
101 | 116 | ||
102 | ### Options | 117 | ### Options |
103 | 118 | ||
... | @@ -106,11 +121,22 @@ initialization. You can pass in options just like you would for other | ... | @@ -106,11 +121,22 @@ initialization. You can pass in options just like you would for other |
106 | parts of video.js: | 121 | parts of video.js: |
107 | 122 | ||
108 | ```javascript | 123 | ```javascript |
109 | videojs(video, { | 124 | // html5 for html hls |
125 | videojs(video, {html5: { | ||
110 | hls: { | 126 | hls: { |
111 | withCredentials: true | 127 | withCredentials: true |
112 | } | 128 | } |
113 | }); | 129 | }}); |
130 | |||
131 | // or | ||
132 | |||
133 | // flash for flash hls | ||
134 | videojs(video, {flash: { | ||
135 | hls: { | ||
136 | withCredentials: true | ||
137 | } | ||
138 | }}); | ||
139 | |||
114 | ``` | 140 | ``` |
115 | 141 | ||
116 | #### withCredentials | 142 | #### withCredentials |
... | @@ -289,26 +315,8 @@ and most CDNs should have no trouble turning CORS on for your account. | ... | @@ -289,26 +315,8 @@ and most CDNs should have no trouble turning CORS on for your account. |
289 | 315 | ||
290 | ### Testing | 316 | ### Testing |
291 | 317 | ||
292 | For testing, you can either run `npm test` or use `grunt` directly. | 318 | For testing, you run `npm run test`. This will run tests using any of the |
293 | If you use `npm test`, it will only run the karma and end-to-end tests using chrome. | 319 | browsers that karma-detect-browsers detects on your machine. |
294 | You can specify which browsers you want the tests to run via grunt's `test` task. | ||
295 | You can use either grunt-style arguments or comma separated arguments: | ||
296 | ``` | ||
297 | grunt test:chrome:firefox # grunt-style | ||
298 | grunt test:chrome,firefox # comma-separated | ||
299 | ``` | ||
300 | Possible options are: | ||
301 | * `chromecanary` | ||
302 | * `phantomjs` | ||
303 | * `opera` | ||
304 | * `chrome`<sup>1</sup> | ||
305 | * `safari`<sup>1, 2</sup> | ||
306 | * `firefox`<sup>1</sup> | ||
307 | * `ie`<sup>1</sup> | ||
308 | |||
309 | |||
310 | _<sup>1</sup>supported end-to-end browsers_<br /> | ||
311 | _<sup>2</sup>requires the [SafariDriver extension]( https://code.google.com/p/selenium/wiki/SafariDriver) to be installed_ | ||
312 | 320 | ||
313 | ## Release History | 321 | ## Release History |
314 | Check out the [changelog](CHANGELOG.md) for a summary of each release. | 322 | Check out the [changelog](CHANGELOG.md) for a summary of each release. | ... | ... |
docs/multiple-alternative-audio-tracks.md
0 → 100644
1 | # Multiple Alternative Audio Tracks | ||
2 | ## General | ||
3 | m3u8 manifests with multiple audio streams will have those streams added to `video.js` in an `AudioTrackList`. The `AudioTrackList` can be accessed using `player.audioTracks()` or `tech.audioTracks()`. | ||
4 | |||
5 | ## Mapping m3u8 metadata to AudioTracks | ||
6 | The mapping between `AudioTrack` and the parsed m3u8 file is fairly straight forward. The table below shows the mapping | ||
7 | |||
8 | | m3u8 | AudioTrack | | ||
9 | |---------|------------| | ||
10 | | label | label | | ||
11 | | lang | language | | ||
12 | | default | enabled | | ||
13 | | ??? | kind | | ||
14 | | ??? | id | | ||
15 | |||
16 | As you can see m3u8's do not have a property for `AudioTrack.id`, which means that we let `video.js` randomly generate the id for `AudioTrack`s. This will have no real impact on any part of the system as we do not use the `id` anywhere. | ||
17 | |||
18 | The other property that does not have a mapping in the m3u8 is `AudioTrack.kind`. It was decided that we would set the `kind` to `main` when `default` is set to `true` and in all other cases we set it to `alternative` | ||
19 | |||
20 | Below is a basic example of a mapping | ||
21 | m3u8 layout | ||
22 | ``` JavaScript | ||
23 | { | ||
24 | 'media-group-1': [{ | ||
25 | 'audio-track-1': { | ||
26 | default: true, | ||
27 | lang: 'eng' | ||
28 | }, | ||
29 | 'audio-track-2': { | ||
30 | default: true, | ||
31 | lang: 'fr' | ||
32 | } | ||
33 | }] | ||
34 | } | ||
35 | ``` | ||
36 | |||
37 | Corresponding AudioTrackList when media-group-1 is used (before any tracks have been changed) | ||
38 | ``` JavaScript | ||
39 | [{ | ||
40 | label: 'audio-tracks-1', | ||
41 | enabled: true, | ||
42 | language: 'eng', | ||
43 | kind: 'main', | ||
44 | id: 'random' | ||
45 | }, { | ||
46 | label: 'audio-tracks-2', | ||
47 | enabled: false, | ||
48 | language: 'fr', | ||
49 | kind: 'alternative', | ||
50 | id: 'random' | ||
51 | }] | ||
52 | ``` | ||
53 | |||
54 | ## Startup (how tracks are added and used) | ||
55 | > AudioTrack & AudioTrackList live in video.js | ||
56 | |||
57 | 1. `HLS` creates a `MasterPlaylistController` and watches for the `loadedmetadata` event | ||
58 | 1. `HLS` parses the m3u8 using the `MasterPlaylistController` | ||
59 | 1. `MasterPlaylistController` creates a `PlaylistLoader` for the master m3u8 | ||
60 | 1. `MasterPlaylistController` creates `PlaylistLoader`s for every audio playlist | ||
61 | 1. `MasterPlaylistController` creates a `SegmentLoader` for the main m3u8 | ||
62 | 1. `MasterPlaylistController` creates a `SegmentLoader` for a potential audio playlist | ||
63 | 1. `HLS` sees the `loadedmetadata` and finds the currently selected MediaGroup and all the metadata | ||
64 | 1. `HLS` removes all `AudioTrack`s from the `AudioTrackList` | ||
65 | 1. `HLS` created `AudioTrack`s for the MediaGroup and adds them to the `AudioTrackList` | ||
66 | 1. `HLS` calls `MasterPlaylistController`s `useAudio` with no arguments (causes it to use the currently enabled audio) | ||
67 | 1. `MasterPlaylistController` turns off the current audio `PlaylistLoader` if it is on | ||
68 | 1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio | ||
69 | 1. `MasterPlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (master or audio only) | ||
70 | 1. `MediaSource`/`mux.js` determine how to mux | ||
71 | |||
72 | ## How tracks are switched | ||
73 | > AudioTrack & AudioTrackList live in video.js | ||
74 | |||
75 | 1. `HLS` is setup to watch for the `changed` event on the `AudioTrackList` | ||
76 | 1. User selects a new `AudioTrack` from a menu (where only one track can be enabled) | ||
77 | 1. `AudioTrackList` enables the new `Audiotrack` and disables all others | ||
78 | 1. `AudioTrackList` triggers a `changed` event | ||
79 | 1. `HLS` sees the `changed` event and finds the newly enabled `AudioTrack` | ||
80 | 1. `HLS` sends the `label` for the new `AudioTrack` to `MasterPlaylistController`s `useAudio` function | ||
81 | 1. `MasterPlaylistController` turns off the current audio `PlaylistLoader` if it is on | ||
82 | 1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio | ||
83 | 1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio | ||
84 | 1. `MasterPlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (master or audio only) | ||
85 | 1. `MediaSource`/`mux.js` determine how to mux | ||
86 |
docs/segment-loader-states.png
0 → 100644
58.2 KB
examples/index.html
0 → 100644
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <meta charset="utf-8"> | ||
5 | <title>Multiple Alternative Audio Tracks - Example</title> | ||
6 | <link href="/node_modules/video.js/dist/video-js.css" rel="stylesheet"> | ||
7 | </head> | ||
8 | <body> | ||
9 | <h1>Multiple Alternative Audio Tracks</h1> | ||
10 | <p>Check the source of this page and the console for detailed information on this example</p> | ||
11 | <video id="maat-player" class="video-js vjs-default-skin" controls> | ||
12 | <source src="https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8" type="application/x-mpegURL"> | ||
13 | </video> | ||
14 | <div id="audioTrackChoice"> | ||
15 | <select id="enabled-audio-track" name="enabled-audio-track"> | ||
16 | </select> | ||
17 | </div> | ||
18 | <script src="/node_modules/video.js/dist/video.js"></script> | ||
19 | <script src="/dist/videojs-contrib-hls.js"></script> | ||
20 | <script> | ||
21 | (function(window, videojs) { | ||
22 | var player = window.player = videojs('maat-player'); | ||
23 | var audioTrackList = player.audioTracks(); | ||
24 | var audioTrackSelect = document.getElementById("enabled-audio-track"); | ||
25 | // watch for a change on the select element | ||
26 | // then change the enabled audio track | ||
27 | // only one can be enabled at a time, but video.js will | ||
28 | // handle that for us, all we need to do is enable the new | ||
29 | // track | ||
30 | audioTrackSelect.addEventListener('change', function() { | ||
31 | var track = audioTrackList[this.selectedIndex]; | ||
32 | console.log('User switched to track ' + track.label); | ||
33 | track.enabled = true; | ||
34 | }); | ||
35 | |||
36 | // watch for changes that will be triggered by any change | ||
37 | // to enabled on any audio track. Manually or through the | ||
38 | // select element | ||
39 | audioTrackList.on('change', function() { | ||
40 | for (var i = 0; i < audioTrackList.length; i++) { | ||
41 | var track = audioTrackList[i]; | ||
42 | if (track.enabled) { | ||
43 | console.log('A new ' + track.label + ' has been enabled!'); | ||
44 | } | ||
45 | } | ||
46 | }); | ||
47 | |||
48 | // will be fired twice in this example | ||
49 | audioTrackList.on('addtrack', function() { | ||
50 | console.log('a track has been added to the audio track list'); | ||
51 | }); | ||
52 | |||
53 | // will not be fired at all unless you call | ||
54 | // audioTrackList.removeTrack(trackObj) | ||
55 | // we typically will not need to do this unless we have to load | ||
56 | // another video for some reason | ||
57 | audioTrackList.on('removetrack', function() { | ||
58 | console.log('a track has been removed from the audio track list'); | ||
59 | }); | ||
60 | |||
61 | // getting all the possible audio tracks from the track list | ||
62 | // get all of thier properties | ||
63 | // add each track to the select on the page | ||
64 | // this is all filled out by HLS when it parses the m3u8 | ||
65 | player.on('loadeddata', function() { | ||
66 | console.log('There are ' + audioTrackList.length + ' audio tracks'); | ||
67 | for (var i = 0; i < audioTrackList.length; i++) { | ||
68 | var track = audioTrackList[i]; | ||
69 | var option = document.createElement("option"); | ||
70 | option.text = track.label; | ||
71 | if (track.enabled) { | ||
72 | option.selected = true; | ||
73 | } | ||
74 | audioTrackSelect.add(option, i); | ||
75 | console.log('Track ' + (i + 1)); | ||
76 | ['label', 'enabled', 'language', 'id', 'kind'].forEach(function(prop) { | ||
77 | console.log(" " + prop + ": " + track[prop]); | ||
78 | }); | ||
79 | } | ||
80 | }); | ||
81 | }(window, window.videojs)); | ||
82 | </script> | ||
83 | </body> | ||
84 | </html> |
... | @@ -43,6 +43,7 @@ | ... | @@ -43,6 +43,7 @@ |
43 | <ul> | 43 | <ul> |
44 | <li><a href="/test/">Run unit tests in browser.</a></li> | 44 | <li><a href="/test/">Run unit tests in browser.</a></li> |
45 | <li><a href="/docs/api/">Read generated docs.</a></li> | 45 | <li><a href="/docs/api/">Read generated docs.</a></li> |
46 | <li><a href="/examples">Browse Examples</a></li> | ||
46 | </ul> | 47 | </ul> |
47 | 48 | ||
48 | <script src="/node_modules/video.js/dist/video.js"></script> | 49 | <script src="/node_modules/video.js/dist/video.js"></script> |
... | @@ -50,6 +51,7 @@ | ... | @@ -50,6 +51,7 @@ |
50 | <script> | 51 | <script> |
51 | (function(window, videojs) { | 52 | (function(window, videojs) { |
52 | var player = window.player = videojs('videojs-contrib-hls-player'); | 53 | var player = window.player = videojs('videojs-contrib-hls-player'); |
54 | |||
53 | // hook up the video switcher | 55 | // hook up the video switcher |
54 | var loadUrl = document.getElementById('load-url'); | 56 | var loadUrl = document.getElementById('load-url'); |
55 | var url = document.getElementById('url'); | 57 | var url = document.getElementById('url'); | ... | ... |
... | @@ -28,8 +28,7 @@ | ... | @@ -28,8 +28,7 @@ |
28 | "docs:api": "jsdoc src -r -d docs/api", | 28 | "docs:api": "jsdoc src -r -d docs/api", |
29 | "docs:toc": "doctoc README.md", | 29 | "docs:toc": "doctoc README.md", |
30 | "lint": "vjsstandard", | 30 | "lint": "vjsstandard", |
31 | "prestart": "npm-run-all docs build", | 31 | "start": "npm-run-all -p watch start:*", |
32 | "start": "npm-run-all -p start:* watch:*", | ||
33 | "start:serve": "babel-node scripts/server.js", | 32 | "start:serve": "babel-node scripts/server.js", |
34 | "pretest": "npm-run-all lint build", | 33 | "pretest": "npm-run-all lint build", |
35 | "test": "karma start test/karma/detected.js", | 34 | "test": "karma start test/karma/detected.js", |
... | @@ -40,7 +39,10 @@ | ... | @@ -40,7 +39,10 @@ |
40 | "preversion": "npm test", | 39 | "preversion": "npm test", |
41 | "version": "npm run build", | 40 | "version": "npm run build", |
42 | "watch": "npm-run-all -p watch:*", | 41 | "watch": "npm-run-all -p watch:*", |
43 | "watch:js": "watchify src/videojs-contrib-hls.js -t babelify -v -o dist/videojs-contrib-hls.js", | 42 | "watch:docs": "nodemon --watch src/ --exec npm run docs", |
43 | "watch:js": "npm-run-all -p watch:js:babel watch:js:browserify", | ||
44 | "watch:js:babel": "npm run build:js:babel -- --watch", | ||
45 | "watch:js:browserify": "watchify . -v -o dist/videojs-contrib-hls.js", | ||
44 | "watch:test": "npm-run-all -p watch:test:*", | 46 | "watch:test": "npm-run-all -p watch:test:*", |
45 | "watch:test:js": "node scripts/watch-test.js", | 47 | "watch:test:js": "node scripts/watch-test.js", |
46 | "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", | 48 | "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", |
... | @@ -73,21 +75,19 @@ | ... | @@ -73,21 +75,19 @@ |
73 | }, | 75 | }, |
74 | "files": [ | 76 | "files": [ |
75 | "CONTRIBUTING.md", | 77 | "CONTRIBUTING.md", |
76 | "dist-test/", | ||
77 | "dist/", | 78 | "dist/", |
78 | "docs/", | 79 | "docs/", |
79 | "es5/", | 80 | "es5/", |
80 | "index.html", | 81 | "index.html", |
81 | "scripts/", | 82 | "scripts/", |
82 | "src/", | 83 | "src/", |
83 | "test/", | 84 | "test/" |
84 | "utils/" | ||
85 | ], | 85 | ], |
86 | "dependencies": { | 86 | "dependencies": { |
87 | "pkcs7": "^0.2.2", | 87 | "pkcs7": "^0.2.2", |
88 | "video.js": "^5.2.1", | 88 | "video.js": "^5.10.1", |
89 | "videojs-contrib-media-sources": "^3.0.0", | 89 | "videojs-contrib-media-sources": "^3.1.0", |
90 | "videojs-swf": "^5.0.0" | 90 | "videojs-swf": "^5.0.2" |
91 | }, | 91 | }, |
92 | "devDependencies": { | 92 | "devDependencies": { |
93 | "babel": "^5.8.0", | 93 | "babel": "^5.8.0", |
... | @@ -111,6 +111,7 @@ | ... | @@ -111,6 +111,7 @@ |
111 | "karma-safari-launcher": "^0.1.0", | 111 | "karma-safari-launcher": "^0.1.0", |
112 | "lodash-compat": "^3.10.0", | 112 | "lodash-compat": "^3.10.0", |
113 | "minimist": "^1.2.0", | 113 | "minimist": "^1.2.0", |
114 | "nodemon": "^1.9.1", | ||
114 | "npm-run-all": "^1.2.0", | 115 | "npm-run-all": "^1.2.0", |
115 | "portscanner": "^1.0.0", | 116 | "portscanner": "^1.0.0", |
116 | "qunitjs": "^1.18.0", | 117 | "qunitjs": "^1.18.0", | ... | ... |
... | @@ -17,7 +17,7 @@ glob('test/**/*.test.js', function(err, files) { | ... | @@ -17,7 +17,7 @@ glob('test/**/*.test.js', function(err, files) { |
17 | }; | 17 | }; |
18 | 18 | ||
19 | b.on('log', function(msg) { | 19 | b.on('log', function(msg) { |
20 | process.stdout.write(msg + '\n'); | 20 | process.stdout.write(msg + ' dist-test/videojs-contrib-hls.js\n'); |
21 | }); | 21 | }); |
22 | 22 | ||
23 | b.on('update', bundle); | 23 | b.on('update', bundle); | ... | ... |
1 | /** | ||
2 | * @file bin-utils.js | ||
3 | */ | ||
4 | |||
5 | /** | ||
6 | * convert a TimeRange to text | ||
7 | * | ||
8 | * @param {TimeRange} range the timerange to use for conversion | ||
9 | * @param {Number} i the iterator on the range to convert | ||
10 | */ | ||
1 | const textRange = function(range, i) { | 11 | const textRange = function(range, i) { |
2 | return range.start(i) + '-' + range.end(i); | 12 | return range.start(i) + '-' + range.end(i); |
3 | }; | 13 | }; |
4 | 14 | ||
15 | /** | ||
16 | * format a number as hex string | ||
17 | * | ||
18 | * @param {Number} e The number | ||
19 | * @param {Number} i the iterator | ||
20 | */ | ||
5 | const formatHexString = function(e, i) { | 21 | const formatHexString = function(e, i) { |
6 | let value = e.toString(16); | 22 | let value = e.toString(16); |
7 | 23 | ||
... | @@ -14,6 +30,9 @@ const formatAsciiString = function(e) { | ... | @@ -14,6 +30,9 @@ const formatAsciiString = function(e) { |
14 | return '.'; | 30 | return '.'; |
15 | }; | 31 | }; |
16 | 32 | ||
33 | /** | ||
34 | * utils to help dump binary data to the console | ||
35 | */ | ||
17 | const utils = { | 36 | const utils = { |
18 | hexDump(data) { | 37 | hexDump(data) { |
19 | let bytes = Array.prototype.slice.call(data); | 38 | let bytes = Array.prototype.slice.call(data); | ... | ... |
1 | /* | 1 | /** |
2 | * aes.js | 2 | * @file decrypter/aes.js |
3 | * | 3 | * |
4 | * This file contains an adaptation of the AES decryption algorithm | 4 | * This file contains an adaptation of the AES decryption algorithm |
5 | * from the Standford Javascript Cryptography Library. That work is | 5 | * from the Standford Javascript Cryptography Library. That work is |
... | @@ -96,7 +96,7 @@ let aesTables = null; | ... | @@ -96,7 +96,7 @@ let aesTables = null; |
96 | * Schedule out an AES key for both encryption and decryption. This | 96 | * Schedule out an AES key for both encryption and decryption. This |
97 | * is a low-level class. Use a cipher mode to do bulk encryption. | 97 | * is a low-level class. Use a cipher mode to do bulk encryption. |
98 | * | 98 | * |
99 | * @constructor | 99 | * @class AES |
100 | * @param key {Array} The key as an array of 4, 6 or 8 words. | 100 | * @param key {Array} The key as an array of 4, 6 or 8 words. |
101 | */ | 101 | */ |
102 | export default class AES { | 102 | export default class AES { |
... | @@ -184,13 +184,14 @@ export default class AES { | ... | @@ -184,13 +184,14 @@ export default class AES { |
184 | 184 | ||
185 | /** | 185 | /** |
186 | * Decrypt 16 bytes, specified as four 32-bit words. | 186 | * Decrypt 16 bytes, specified as four 32-bit words. |
187 | * @param encrypted0 {number} the first word to decrypt | 187 | * |
188 | * @param encrypted1 {number} the second word to decrypt | 188 | * @param {Number} encrypted0 the first word to decrypt |
189 | * @param encrypted2 {number} the third word to decrypt | 189 | * @param {Number} encrypted1 the second word to decrypt |
190 | * @param encrypted3 {number} the fourth word to decrypt | 190 | * @param {Number} encrypted2 the third word to decrypt |
191 | * @param out {Int32Array} the array to write the decrypted words | 191 | * @param {Number} encrypted3 the fourth word to decrypt |
192 | * @param {Int32Array} out the array to write the decrypted words | ||
192 | * into | 193 | * into |
193 | * @param offset {number} the offset into the output array to start | 194 | * @param {Number} offset the offset into the output array to start |
194 | * writing results | 195 | * writing results |
195 | * @return {Array} The plaintext. | 196 | * @return {Array} The plaintext. |
196 | */ | 197 | */ | ... | ... |
1 | /** | ||
2 | * @file decrypter/async-stream.js | ||
3 | */ | ||
1 | import Stream from '../stream'; | 4 | import Stream from '../stream'; |
2 | 5 | ||
3 | /** | 6 | /** |
4 | * A wrapper around the Stream class to use setTiemout | 7 | * A wrapper around the Stream class to use setTiemout |
5 | * and run stream "jobs" Asynchronously | 8 | * and run stream "jobs" Asynchronously |
9 | * | ||
10 | * @class AsyncStream | ||
11 | * @extends Stream | ||
6 | */ | 12 | */ |
7 | export default class AsyncStream extends Stream { | 13 | export default class AsyncStream extends Stream { |
8 | constructor() { | 14 | constructor() { |
... | @@ -11,6 +17,12 @@ export default class AsyncStream extends Stream { | ... | @@ -11,6 +17,12 @@ export default class AsyncStream extends Stream { |
11 | this.delay = 1; | 17 | this.delay = 1; |
12 | this.timeout_ = null; | 18 | this.timeout_ = null; |
13 | } | 19 | } |
20 | |||
21 | /** | ||
22 | * process an async job | ||
23 | * | ||
24 | * @private | ||
25 | */ | ||
14 | processJob_() { | 26 | processJob_() { |
15 | this.jobs.shift()(); | 27 | this.jobs.shift()(); |
16 | if (this.jobs.length) { | 28 | if (this.jobs.length) { |
... | @@ -20,6 +32,12 @@ export default class AsyncStream extends Stream { | ... | @@ -20,6 +32,12 @@ export default class AsyncStream extends Stream { |
20 | this.timeout_ = null; | 32 | this.timeout_ = null; |
21 | } | 33 | } |
22 | } | 34 | } |
35 | |||
36 | /** | ||
37 | * push a job into the stream | ||
38 | * | ||
39 | * @param {Function} job the job to push into the stream | ||
40 | */ | ||
23 | push(job) { | 41 | push(job) { |
24 | this.jobs.push(job); | 42 | this.jobs.push(job); |
25 | if (!this.timeout_) { | 43 | if (!this.timeout_) { | ... | ... |
1 | /* | 1 | /** |
2 | * decrypter.js | 2 | * @file decrypter/decrypter.js |
3 | * | 3 | * |
4 | * An asynchronous implementation of AES-128 CBC decryption with | 4 | * An asynchronous implementation of AES-128 CBC decryption with |
5 | * PKCS#7 padding. | 5 | * PKCS#7 padding. |
... | @@ -20,12 +20,12 @@ const ntoh = function(word) { | ... | @@ -20,12 +20,12 @@ const ntoh = function(word) { |
20 | (word >>> 24); | 20 | (word >>> 24); |
21 | }; | 21 | }; |
22 | 22 | ||
23 | /* eslint-disable max-len */ | ||
24 | /** | 23 | /** |
25 | * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. | 24 | * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. |
26 | * @param encrypted {Uint8Array} the encrypted bytes | 25 | * |
27 | * @param key {Uint32Array} the bytes of the decryption key | 26 | * @param {Uint8Array} encrypted the encrypted bytes |
28 | * @param initVector {Uint32Array} the initialization vector (IV) to | 27 | * @param {Uint32Array} key the bytes of the decryption key |
28 | * @param {Uint32Array} initVector the initialization vector (IV) to | ||
29 | * use for the first round of CBC. | 29 | * use for the first round of CBC. |
30 | * @return {Uint8Array} the decrypted bytes | 30 | * @return {Uint8Array} the decrypted bytes |
31 | * | 31 | * |
... | @@ -33,7 +33,6 @@ const ntoh = function(word) { | ... | @@ -33,7 +33,6 @@ const ntoh = function(word) { |
33 | * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 | 33 | * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 |
34 | * @see https://tools.ietf.org/html/rfc2315 | 34 | * @see https://tools.ietf.org/html/rfc2315 |
35 | */ | 35 | */ |
36 | /* eslint-enable max-len */ | ||
37 | export const decrypt = function(encrypted, key, initVector) { | 36 | export const decrypt = function(encrypted, key, initVector) { |
38 | // word-level access to the encrypted bytes | 37 | // word-level access to the encrypted bytes |
39 | let encrypted32 = new Int32Array(encrypted.buffer, | 38 | let encrypted32 = new Int32Array(encrypted.buffer, |
... | @@ -106,6 +105,12 @@ export const decrypt = function(encrypted, key, initVector) { | ... | @@ -106,6 +105,12 @@ export const decrypt = function(encrypted, key, initVector) { |
106 | * The `Decrypter` class that manages decryption of AES | 105 | * The `Decrypter` class that manages decryption of AES |
107 | * data through `AsyncStream` objects and the `decrypt` | 106 | * data through `AsyncStream` objects and the `decrypt` |
108 | * function | 107 | * function |
108 | * | ||
109 | * @param {Uint8Array} encrypted the encrypted bytes | ||
110 | * @param {Uint32Array} key the bytes of the decryption key | ||
111 | * @param {Uint32Array} initVector the initialization vector (IV) to | ||
112 | * @param {Function} done the function to run when done | ||
113 | * @class Decrypter | ||
109 | */ | 114 | */ |
110 | export class Decrypter { | 115 | export class Decrypter { |
111 | constructor(encrypted, key, initVector, done) { | 116 | constructor(encrypted, key, initVector, done) { |
... | @@ -137,6 +142,20 @@ export class Decrypter { | ... | @@ -137,6 +142,20 @@ export class Decrypter { |
137 | done(null, unpad(decrypted)); | 142 | done(null, unpad(decrypted)); |
138 | }); | 143 | }); |
139 | } | 144 | } |
145 | |||
146 | /** | ||
147 | * a getter for step the maximum number of bytes to process at one time | ||
148 | * | ||
149 | * @return {Number} the value of step 32000 | ||
150 | */ | ||
151 | static get STEP() { | ||
152 | // 4 * 8000; | ||
153 | return 32000; | ||
154 | } | ||
155 | |||
156 | /** | ||
157 | * @private | ||
158 | */ | ||
140 | decryptChunk_(encrypted, key, initVector, decrypted) { | 159 | decryptChunk_(encrypted, key, initVector, decrypted) { |
141 | return function() { | 160 | return function() { |
142 | let bytes = decrypt(encrypted, key, initVector); | 161 | let bytes = decrypt(encrypted, key, initVector); |
... | @@ -146,10 +165,6 @@ export class Decrypter { | ... | @@ -146,10 +165,6 @@ export class Decrypter { |
146 | } | 165 | } |
147 | } | 166 | } |
148 | 167 | ||
149 | // the maximum number of bytes to process at one time | ||
150 | // 4 * 8000; | ||
151 | Decrypter.STEP = 32000; | ||
152 | |||
153 | export default { | 168 | export default { |
154 | Decrypter, | 169 | Decrypter, |
155 | decrypt | 170 | decrypt | ... | ... |
src/hls-audio-track.js
0 → 100644
1 | /** | ||
2 | * @file hls-audio-track.js | ||
3 | */ | ||
4 | import {AudioTrack} from 'video.js'; | ||
5 | import PlaylistLoader from './playlist-loader'; | ||
6 | |||
7 | /** | ||
8 | * HlsAudioTrack extends video.js audio tracks but adds HLS | ||
9 | * specific data storage such as playlist loaders, mediaGroups | ||
10 | * and default/autoselect | ||
11 | * | ||
12 | * @param {Object} options options to create HlsAudioTrack with | ||
13 | * @class HlsAudioTrack | ||
14 | * @extends AudioTrack | ||
15 | */ | ||
16 | export default class HlsAudioTrack extends AudioTrack { | ||
17 | constructor(options) { | ||
18 | super({ | ||
19 | kind: options.default ? 'main' : 'alternative', | ||
20 | enabled: options.default || false, | ||
21 | language: options.language, | ||
22 | label: options.label | ||
23 | }); | ||
24 | |||
25 | this.hls = options.hls; | ||
26 | this.autoselect = options.autoselect || false; | ||
27 | this.default = options.default || false; | ||
28 | this.withCredentials = options.withCredentials || false; | ||
29 | this.mediaGroups_ = []; | ||
30 | this.addLoader(options.mediaGroup, options.resolvedUri); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * get a PlaylistLoader from this track given a mediaGroup name | ||
35 | * | ||
36 | * @param {String} mediaGroup the mediaGroup to get the loader for | ||
37 | * @return {PlaylistLoader|Null} the PlaylistLoader or null | ||
38 | */ | ||
39 | getLoader(mediaGroup) { | ||
40 | for (let i = 0; i < this.mediaGroups_.length; i++) { | ||
41 | let mgl = this.mediaGroups_[i]; | ||
42 | |||
43 | if (mgl.mediaGroup === mediaGroup) { | ||
44 | return mgl.loader; | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * add a PlaylistLoader given a mediaGroup, and a uri. for a combined track | ||
51 | * we store null for the playlistloader | ||
52 | * | ||
53 | * @param {String} mediaGroup the mediaGroup to get the loader for | ||
54 | * @param {String} uri the uri to get the audio track/mediaGroup from | ||
55 | */ | ||
56 | addLoader(mediaGroup, uri = null) { | ||
57 | let loader = null; | ||
58 | |||
59 | if (uri) { | ||
60 | // TODO: this should probably happen upstream in Master Playlist | ||
61 | // Controller when we can switch PlaylistLoader sources | ||
62 | // then we can just store the uri here instead | ||
63 | loader = new PlaylistLoader(uri, this.hls, this.withCredentials); | ||
64 | } | ||
65 | this.mediaGroups_.push({mediaGroup, loader}); | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * remove a playlist loader from a track given the mediaGroup | ||
70 | * | ||
71 | * @param {String} mediaGroup the mediaGroup to remove | ||
72 | */ | ||
73 | removeLoader(mediaGroup) { | ||
74 | for (let i = 0; i < this.mediaGroups_.length; i++) { | ||
75 | let mgl = this.mediaGroups_[i]; | ||
76 | |||
77 | if (mgl.mediaGroup === mediaGroup) { | ||
78 | if (mgl.loader) { | ||
79 | mgl.loader.dispose(); | ||
80 | } | ||
81 | this.mediaGroups_.splice(i, 1); | ||
82 | return; | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * Dispose of this audio track and | ||
89 | * the playlist loader that it holds inside | ||
90 | */ | ||
91 | dispose() { | ||
92 | let i = this.mediaGroups_.length; | ||
93 | |||
94 | while (i--) { | ||
95 | this.removeLoader(this.mediaGroups_[i].mediaGroup); | ||
96 | } | ||
97 | } | ||
98 | } |
1 | /** | 1 | /** |
2 | * @file m3u8/index.js | ||
3 | * | ||
2 | * Utilities for parsing M3U8 files. If the entire manifest is available, | 4 | * Utilities for parsing M3U8 files. If the entire manifest is available, |
3 | * `Parser` will create an object representation with enough detail for managing | 5 | * `Parser` will create an object representation with enough detail for managing |
4 | * playback. `ParseStream` and `LineStream` are lower-level parsing primitives | 6 | * playback. `ParseStream` and `LineStream` are lower-level parsing primitives | ... | ... |
1 | /** | ||
2 | * @file m3u8/line-stream.js | ||
3 | */ | ||
1 | import Stream from '../stream'; | 4 | import Stream from '../stream'; |
5 | |||
2 | /** | 6 | /** |
3 | * A stream that buffers string input and generates a `data` event for each | 7 | * A stream that buffers string input and generates a `data` event for each |
4 | * line. | 8 | * line. |
9 | * | ||
10 | * @class LineStream | ||
11 | * @extends Stream | ||
5 | */ | 12 | */ |
6 | export default class LineStream extends Stream { | 13 | export default class LineStream extends Stream { |
7 | constructor() { | 14 | constructor() { |
... | @@ -11,7 +18,8 @@ export default class LineStream extends Stream { | ... | @@ -11,7 +18,8 @@ export default class LineStream extends Stream { |
11 | 18 | ||
12 | /** | 19 | /** |
13 | * Add new data to be parsed. | 20 | * Add new data to be parsed. |
14 | * @param data {string} the text to process | 21 | * |
22 | * @param {String} data the text to process | ||
15 | */ | 23 | */ |
16 | push(data) { | 24 | push(data) { |
17 | let nextNewline; | 25 | let nextNewline; | ... | ... |
1 | /** | ||
2 | * @file m3u8/parse-stream.js | ||
3 | */ | ||
1 | import Stream from '../stream'; | 4 | import Stream from '../stream'; |
2 | 5 | ||
3 | // "forgiving" attribute list psuedo-grammar: | 6 | /** |
4 | // attributes -> keyvalue (',' keyvalue)* | 7 | * "forgiving" attribute list psuedo-grammar: |
5 | // keyvalue -> key '=' value | 8 | * attributes -> keyvalue (',' keyvalue)* |
6 | // key -> [^=]* | 9 | * keyvalue -> key '=' value |
7 | // value -> '"' [^"]* '"' | [^,]* | 10 | * key -> [^=]* |
11 | * value -> '"' [^"]* '"' | [^,]* | ||
12 | */ | ||
8 | const attributeSeparator = function() { | 13 | const attributeSeparator = function() { |
9 | let key = '[^=]*'; | 14 | let key = '[^=]*'; |
10 | let value = '"[^"]*"|[^,]*'; | 15 | let value = '"[^"]*"|[^,]*'; |
... | @@ -13,6 +18,11 @@ const attributeSeparator = function() { | ... | @@ -13,6 +18,11 @@ const attributeSeparator = function() { |
13 | return new RegExp('(?:^|,)(' + keyvalue + ')'); | 18 | return new RegExp('(?:^|,)(' + keyvalue + ')'); |
14 | }; | 19 | }; |
15 | 20 | ||
21 | /** | ||
22 | * Parse attributes from a line given the seperator | ||
23 | * | ||
24 | * @param {String} attributes the attibute line to parse | ||
25 | */ | ||
16 | const parseAttributes = function(attributes) { | 26 | const parseAttributes = function(attributes) { |
17 | // split the string using attributes as the separator | 27 | // split the string using attributes as the separator |
18 | let attrs = attributes.split(attributeSeparator()); | 28 | let attrs = attributes.split(attributeSeparator()); |
... | @@ -57,6 +67,9 @@ const parseAttributes = function(attributes) { | ... | @@ -57,6 +67,9 @@ const parseAttributes = function(attributes) { |
57 | * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized | 67 | * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized |
58 | * tags are given the tag type `unknown` and a single additional property | 68 | * tags are given the tag type `unknown` and a single additional property |
59 | * `data` with the remainder of the input. | 69 | * `data` with the remainder of the input. |
70 | * | ||
71 | * @class ParseStream | ||
72 | * @extends Stream | ||
60 | */ | 73 | */ |
61 | export default class ParseStream extends Stream { | 74 | export default class ParseStream extends Stream { |
62 | constructor() { | 75 | constructor() { |
... | @@ -65,7 +78,8 @@ export default class ParseStream extends Stream { | ... | @@ -65,7 +78,8 @@ export default class ParseStream extends Stream { |
65 | 78 | ||
66 | /** | 79 | /** |
67 | * Parses an additional line of input. | 80 | * Parses an additional line of input. |
68 | * @param line {string} a single line of an M3U8 file to parse | 81 | * |
82 | * @param {String} line a single line of an M3U8 file to parse | ||
69 | */ | 83 | */ |
70 | push(line) { | 84 | push(line) { |
71 | let match; | 85 | let match; |
... | @@ -254,6 +268,18 @@ export default class ParseStream extends Stream { | ... | @@ -254,6 +268,18 @@ export default class ParseStream extends Stream { |
254 | this.trigger('data', event); | 268 | this.trigger('data', event); |
255 | return; | 269 | return; |
256 | } | 270 | } |
271 | match = (/^#EXT-X-MEDIA:?(.*)$/).exec(line); | ||
272 | if (match) { | ||
273 | event = { | ||
274 | type: 'tag', | ||
275 | tagType: 'media' | ||
276 | }; | ||
277 | if (match[1]) { | ||
278 | event.attributes = parseAttributes(match[1]); | ||
279 | } | ||
280 | this.trigger('data', event); | ||
281 | return; | ||
282 | } | ||
257 | match = (/^#EXT-X-ENDLIST/).exec(line); | 283 | match = (/^#EXT-X-ENDLIST/).exec(line); |
258 | if (match) { | 284 | if (match) { |
259 | this.trigger('data', { | 285 | this.trigger('data', { | ... | ... |
1 | /** | ||
2 | * @file m3u8/parser.js | ||
3 | */ | ||
1 | import Stream from '../stream' ; | 4 | import Stream from '../stream' ; |
2 | import LineStream from './line-stream'; | 5 | import LineStream from './line-stream'; |
3 | import ParseStream from './parse-stream'; | 6 | import ParseStream from './parse-stream'; |
... | @@ -20,6 +23,9 @@ import {mergeOptions} from 'video.js'; | ... | @@ -20,6 +23,9 @@ import {mergeOptions} from 'video.js'; |
20 | * underlying input is somewhat nonsensical. It emits `info` and `warning` | 23 | * underlying input is somewhat nonsensical. It emits `info` and `warning` |
21 | * events during the parse if it encounters input that seems invalid or | 24 | * events during the parse if it encounters input that seems invalid or |
22 | * requires some property of the manifest object to be defaulted. | 25 | * requires some property of the manifest object to be defaulted. |
26 | * | ||
27 | * @class Parser | ||
28 | * @extends Stream | ||
23 | */ | 29 | */ |
24 | export default class Parser extends Stream { | 30 | export default class Parser extends Stream { |
25 | constructor() { | 31 | constructor() { |
... | @@ -34,6 +40,14 @@ export default class Parser extends Stream { | ... | @@ -34,6 +40,14 @@ export default class Parser extends Stream { |
34 | let currentUri = {}; | 40 | let currentUri = {}; |
35 | let key; | 41 | let key; |
36 | let noop = function() {}; | 42 | let noop = function() {}; |
43 | let defaultMediaGroups = { | ||
44 | 'AUDIO': {}, | ||
45 | 'VIDEO': {}, | ||
46 | 'CLOSED-CAPTIONS': {}, | ||
47 | 'SUBTITLES': {} | ||
48 | }; | ||
49 | // group segments into numbered timelines delineated by discontinuities | ||
50 | let currentTimeline = 0; | ||
37 | 51 | ||
38 | // the manifest is empty until the parse stream begins delivering data | 52 | // the manifest is empty until the parse stream begins delivering data |
39 | this.manifest = { | 53 | this.manifest = { |
... | @@ -43,6 +57,9 @@ export default class Parser extends Stream { | ... | @@ -43,6 +57,9 @@ export default class Parser extends Stream { |
43 | 57 | ||
44 | // update the manifest with the m3u8 entry from the parse stream | 58 | // update the manifest with the m3u8 entry from the parse stream |
45 | this.parseStream.on('data', function(entry) { | 59 | this.parseStream.on('data', function(entry) { |
60 | let mediaGroup; | ||
61 | let rendition; | ||
62 | |||
46 | ({ | 63 | ({ |
47 | tag() { | 64 | tag() { |
48 | // switch based on the tag type | 65 | // switch based on the tag type |
... | @@ -96,7 +113,6 @@ export default class Parser extends Stream { | ... | @@ -96,7 +113,6 @@ export default class Parser extends Stream { |
96 | } | 113 | } |
97 | 114 | ||
98 | this.manifest.segments = uris; | 115 | this.manifest.segments = uris; |
99 | |||
100 | }, | 116 | }, |
101 | key() { | 117 | key() { |
102 | if (!entry.attributes) { | 118 | if (!entry.attributes) { |
... | @@ -149,6 +165,7 @@ export default class Parser extends Stream { | ... | @@ -149,6 +165,7 @@ export default class Parser extends Stream { |
149 | return; | 165 | return; |
150 | } | 166 | } |
151 | this.manifest.discontinuitySequence = entry.number; | 167 | this.manifest.discontinuitySequence = entry.number; |
168 | currentTimeline = entry.number; | ||
152 | }, | 169 | }, |
153 | 'playlist-type'() { | 170 | 'playlist-type'() { |
154 | if (!(/VOD|EVENT/).test(entry.playlistType)) { | 171 | if (!(/VOD|EVENT/).test(entry.playlistType)) { |
... | @@ -161,6 +178,8 @@ export default class Parser extends Stream { | ... | @@ -161,6 +178,8 @@ export default class Parser extends Stream { |
161 | }, | 178 | }, |
162 | 'stream-inf'() { | 179 | 'stream-inf'() { |
163 | this.manifest.playlists = uris; | 180 | this.manifest.playlists = uris; |
181 | this.manifest.mediaGroups = | ||
182 | this.manifest.mediaGroups || defaultMediaGroups; | ||
164 | 183 | ||
165 | if (!entry.attributes) { | 184 | if (!entry.attributes) { |
166 | this.trigger('warn', { | 185 | this.trigger('warn', { |
... | @@ -175,7 +194,48 @@ export default class Parser extends Stream { | ... | @@ -175,7 +194,48 @@ export default class Parser extends Stream { |
175 | currentUri.attributes = mergeOptions(currentUri.attributes, | 194 | currentUri.attributes = mergeOptions(currentUri.attributes, |
176 | entry.attributes); | 195 | entry.attributes); |
177 | }, | 196 | }, |
197 | media() { | ||
198 | this.manifest.mediaGroups = | ||
199 | this.manifest.mediaGroups || defaultMediaGroups; | ||
200 | |||
201 | if (!(entry.attributes && | ||
202 | entry.attributes.TYPE && | ||
203 | entry.attributes['GROUP-ID'] && | ||
204 | entry.attributes.NAME)) { | ||
205 | this.trigger('warn', { | ||
206 | message: 'ignoring incomplete or missing media group' | ||
207 | }); | ||
208 | return; | ||
209 | } | ||
210 | |||
211 | // find the media group, creating defaults as necessary | ||
212 | let mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; | ||
213 | |||
214 | mediaGroupType[entry.attributes['GROUP-ID']] = | ||
215 | mediaGroupType[entry.attributes['GROUP-ID']] || {}; | ||
216 | mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; | ||
217 | |||
218 | // collect the rendition metadata | ||
219 | rendition = { | ||
220 | default: (/yes/i).test(entry.attributes.DEFAULT) | ||
221 | }; | ||
222 | if (rendition.default) { | ||
223 | rendition.autoselect = true; | ||
224 | } else { | ||
225 | rendition.autoselect = (/yes/i).test(entry.attributes.AUTOSELECT); | ||
226 | } | ||
227 | if (entry.attributes.LANGUAGE) { | ||
228 | rendition.language = entry.attributes.LANGUAGE; | ||
229 | } | ||
230 | if (entry.attributes.URI) { | ||
231 | rendition.uri = entry.attributes.URI; | ||
232 | } | ||
233 | |||
234 | // insert the new rendition | ||
235 | mediaGroup[entry.attributes.NAME] = rendition; | ||
236 | }, | ||
178 | discontinuity() { | 237 | discontinuity() { |
238 | currentTimeline += 1; | ||
179 | currentUri.discontinuity = true; | 239 | currentUri.discontinuity = true; |
180 | this.manifest.discontinuityStarts.push(uris.length); | 240 | this.manifest.discontinuityStarts.push(uris.length); |
181 | }, | 241 | }, |
... | @@ -215,6 +275,7 @@ export default class Parser extends Stream { | ... | @@ -215,6 +275,7 @@ export default class Parser extends Stream { |
215 | if (key) { | 275 | if (key) { |
216 | currentUri.key = key; | 276 | currentUri.key = key; |
217 | } | 277 | } |
278 | currentUri.timeline = currentTimeline; | ||
218 | 279 | ||
219 | // prepare for the next URI | 280 | // prepare for the next URI |
220 | currentUri = {}; | 281 | currentUri = {}; |
... | @@ -229,7 +290,8 @@ export default class Parser extends Stream { | ... | @@ -229,7 +290,8 @@ export default class Parser extends Stream { |
229 | 290 | ||
230 | /** | 291 | /** |
231 | * Parse the input string and update the manifest object. | 292 | * Parse the input string and update the manifest object. |
232 | * @param chunk {string} a potentially incomplete portion of the manifest | 293 | * |
294 | * @param {String} chunk a potentially incomplete portion of the manifest | ||
233 | */ | 295 | */ |
234 | push(chunk) { | 296 | push(chunk) { |
235 | this.lineStream.push(chunk); | 297 | this.lineStream.push(chunk); |
... | @@ -246,4 +308,3 @@ export default class Parser extends Stream { | ... | @@ -246,4 +308,3 @@ export default class Parser extends Stream { |
246 | } | 308 | } |
247 | 309 | ||
248 | } | 310 | } |
249 | ... | ... |
src/master-playlist-controller.js
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
1 | /** | 1 | /** |
2 | * @file playlist.js | ||
3 | * | ||
2 | * Playlist related utilities. | 4 | * Playlist related utilities. |
3 | */ | 5 | */ |
4 | import {createTimeRange} from 'video.js'; | 6 | import {createTimeRange} from 'video.js'; |
... | @@ -13,6 +15,14 @@ let Playlist = { | ... | @@ -13,6 +15,14 @@ let Playlist = { |
13 | UNSAFE_LIVE_SEGMENTS: 3 | 15 | UNSAFE_LIVE_SEGMENTS: 3 |
14 | }; | 16 | }; |
15 | 17 | ||
18 | /** | ||
19 | * walk backward until we find a duration we can use | ||
20 | * or return a failure | ||
21 | * | ||
22 | * @param {Playlist} playlist the playlist to walk through | ||
23 | * @param {Number} endSequence the mediaSequence to stop walking on | ||
24 | */ | ||
25 | |||
16 | const backwardDuration = function(playlist, endSequence) { | 26 | const backwardDuration = function(playlist, endSequence) { |
17 | let result = 0; | 27 | let result = 0; |
18 | let i = endSequence - playlist.mediaSequence; | 28 | let i = endSequence - playlist.mediaSequence; |
... | @@ -48,6 +58,13 @@ const backwardDuration = function(playlist, endSequence) { | ... | @@ -48,6 +58,13 @@ const backwardDuration = function(playlist, endSequence) { |
48 | return { result, precise: false }; | 58 | return { result, precise: false }; |
49 | }; | 59 | }; |
50 | 60 | ||
61 | /** | ||
62 | * walk forward until we find a duration we can use | ||
63 | * or return a failure | ||
64 | * | ||
65 | * @param {Playlist} playlist the playlist to walk through | ||
66 | * @param {Number} endSequence the mediaSequence to stop walking on | ||
67 | */ | ||
51 | const forwardDuration = function(playlist, endSequence) { | 68 | const forwardDuration = function(playlist, endSequence) { |
52 | let result = 0; | 69 | let result = 0; |
53 | let segment; | 70 | let segment; |
... | @@ -83,13 +100,15 @@ const forwardDuration = function(playlist, endSequence) { | ... | @@ -83,13 +100,15 @@ const forwardDuration = function(playlist, endSequence) { |
83 | * playlist. The duration of a subinterval of the available segments | 100 | * playlist. The duration of a subinterval of the available segments |
84 | * may be calculated by specifying an end index. | 101 | * may be calculated by specifying an end index. |
85 | * | 102 | * |
86 | * @param playlist {object} a media playlist object | 103 | * @param {Object} playlist a media playlist object |
87 | * @param endSequence {number} (optional) an exclusive upper boundary | 104 | * @param {Number=} endSequence an exclusive upper boundary |
88 | * for the playlist. Defaults to playlist length. | 105 | * for the playlist. Defaults to playlist length. |
89 | * @return {number} the duration between the first available segment | 106 | * @param {Number} expired the amount of time that has dropped |
107 | * off the front of the playlist in a live scenario | ||
108 | * @return {Number} the duration between the first available segment | ||
90 | * and end index. | 109 | * and end index. |
91 | */ | 110 | */ |
92 | const intervalDuration = function(playlist, endSequence) { | 111 | const intervalDuration = function(playlist, endSequence, expired) { |
93 | let backward; | 112 | let backward; |
94 | let forward; | 113 | let forward; |
95 | 114 | ||
... | @@ -120,7 +139,7 @@ const intervalDuration = function(playlist, endSequence) { | ... | @@ -120,7 +139,7 @@ const intervalDuration = function(playlist, endSequence) { |
120 | } | 139 | } |
121 | 140 | ||
122 | // return the less-precise, playlist-based duration estimate | 141 | // return the less-precise, playlist-based duration estimate |
123 | return backward.result; | 142 | return backward.result + expired; |
124 | }; | 143 | }; |
125 | 144 | ||
126 | /** | 145 | /** |
... | @@ -128,23 +147,23 @@ const intervalDuration = function(playlist, endSequence) { | ... | @@ -128,23 +147,23 @@ const intervalDuration = function(playlist, endSequence) { |
128 | * are specified, the duration will be for the subset of the media | 147 | * are specified, the duration will be for the subset of the media |
129 | * timeline between those two indices. The total duration for live | 148 | * timeline between those two indices. The total duration for live |
130 | * playlists is always Infinity. | 149 | * playlists is always Infinity. |
131 | * @param playlist {object} a media playlist object | 150 | * |
132 | * @param endSequence {number} (optional) an exclusive upper | 151 | * @param {Object} playlist a media playlist object |
152 | * @param {Number=} endSequence an exclusive upper | ||
133 | * boundary for the playlist. Defaults to the playlist media | 153 | * boundary for the playlist. Defaults to the playlist media |
134 | * sequence number plus its length. | 154 | * sequence number plus its length. |
135 | * @param includeTrailingTime {boolean} (optional) if false, the | 155 | * @param {Number=} expired the amount of time that has |
136 | * interval between the final segment and the subsequent segment | 156 | * dropped off the front of the playlist in a live scenario |
137 | * will not be included in the result | 157 | * @return {Number} the duration between the start index and end |
138 | * @return {number} the duration between the start index and end | ||
139 | * index. | 158 | * index. |
140 | */ | 159 | */ |
141 | export const duration = function(playlist, endSequence, includeTrailingTime) { | 160 | export const duration = function(playlist, endSequence, expired) { |
142 | if (!playlist) { | 161 | if (!playlist) { |
143 | return 0; | 162 | return 0; |
144 | } | 163 | } |
145 | 164 | ||
146 | if (typeof includeTrailingTime === 'undefined') { | 165 | if (typeof expired !== 'number') { |
147 | includeTrailingTime = true; | 166 | expired = 0; |
148 | } | 167 | } |
149 | 168 | ||
150 | // if a slice of the total duration is not requested, use | 169 | // if a slice of the total duration is not requested, use |
... | @@ -164,7 +183,7 @@ export const duration = function(playlist, endSequence, includeTrailingTime) { | ... | @@ -164,7 +183,7 @@ export const duration = function(playlist, endSequence, includeTrailingTime) { |
164 | // calculate the total duration based on the segment durations | 183 | // calculate the total duration based on the segment durations |
165 | return intervalDuration(playlist, | 184 | return intervalDuration(playlist, |
166 | endSequence, | 185 | endSequence, |
167 | includeTrailingTime); | 186 | expired); |
168 | }; | 187 | }; |
169 | 188 | ||
170 | /** | 189 | /** |
... | @@ -174,16 +193,24 @@ export const duration = function(playlist, endSequence, includeTrailingTime) { | ... | @@ -174,16 +193,24 @@ export const duration = function(playlist, endSequence, includeTrailingTime) { |
174 | * seekable implementation for live streams would need to offset | 193 | * seekable implementation for live streams would need to offset |
175 | * these values by the duration of content that has expired from the | 194 | * these values by the duration of content that has expired from the |
176 | * stream. | 195 | * stream. |
177 | * @param playlist {object} a media playlist object | 196 | * |
197 | * @param {Object} playlist a media playlist object | ||
198 | * @param {Number=} expired the amount of time that has | ||
199 | * dropped off the front of the playlist in a live scenario | ||
178 | * @return {TimeRanges} the periods of time that are valid targets | 200 | * @return {TimeRanges} the periods of time that are valid targets |
179 | * for seeking | 201 | * for seeking |
180 | */ | 202 | */ |
181 | export const seekable = function(playlist) { | 203 | export const seekable = function(playlist, expired) { |
182 | let start; | 204 | let start; |
183 | let end; | 205 | let end; |
206 | let endSequence; | ||
207 | |||
208 | if (typeof expired !== 'number') { | ||
209 | expired = 0; | ||
210 | } | ||
184 | 211 | ||
185 | // without segments, there are no seekable ranges | 212 | // without segments, there are no seekable ranges |
186 | if (!playlist.segments) { | 213 | if (!playlist || !playlist.segments) { |
187 | return createTimeRange(); | 214 | return createTimeRange(); |
188 | } | 215 | } |
189 | // when the playlist is complete, the entire duration is seekable | 216 | // when the playlist is complete, the entire duration is seekable |
... | @@ -194,15 +221,142 @@ export const seekable = function(playlist) { | ... | @@ -194,15 +221,142 @@ export const seekable = function(playlist) { |
194 | // live playlists should not expose three segment durations worth | 221 | // live playlists should not expose three segment durations worth |
195 | // of content from the end of the playlist | 222 | // of content from the end of the playlist |
196 | // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3 | 223 | // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3 |
197 | start = intervalDuration(playlist, playlist.mediaSequence); | 224 | start = intervalDuration(playlist, playlist.mediaSequence, expired); |
225 | endSequence = Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS); | ||
198 | end = intervalDuration(playlist, | 226 | end = intervalDuration(playlist, |
199 | playlist.mediaSequence + | 227 | playlist.mediaSequence + endSequence, |
200 | Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS)); | 228 | expired); |
201 | return createTimeRange(start, end); | 229 | return createTimeRange(start, end); |
202 | }; | 230 | }; |
203 | 231 | ||
232 | /** | ||
233 | * Determine the index of the segment that contains a specified | ||
234 | * playback position in a media playlist. | ||
235 | * | ||
236 | * @param {Object} playlist the media playlist to query | ||
237 | * @param {Number} time The number of seconds since the earliest | ||
238 | * possible position to determine the containing segment for | ||
239 | * @param {Number=} expired the duration of content, in | ||
240 | * seconds, that has been removed from this playlist because it | ||
241 | * expired | ||
242 | * @return {Number} The number of the media segment that contains | ||
243 | * that time position. | ||
244 | */ | ||
245 | export const getMediaIndexForTime_ = function(playlist, time, expired) { | ||
246 | let i; | ||
247 | let segment; | ||
248 | let originalTime = time; | ||
249 | let numSegments = playlist.segments.length; | ||
250 | let lastSegment = numSegments - 1; | ||
251 | let startIndex; | ||
252 | let endIndex; | ||
253 | let knownStart; | ||
254 | let knownEnd; | ||
255 | |||
256 | if (!playlist) { | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | // when the requested position is earlier than the current set of | ||
261 | // segments, return the earliest segment index | ||
262 | if (time < 0) { | ||
263 | return 0; | ||
264 | } | ||
265 | |||
266 | expired = expired || 0; | ||
267 | |||
268 | // find segments with known timing information that bound the | ||
269 | // target time | ||
270 | for (i = 0; i < numSegments; i++) { | ||
271 | segment = playlist.segments[i]; | ||
272 | if (segment.end) { | ||
273 | if (segment.end > time) { | ||
274 | knownEnd = segment.end; | ||
275 | endIndex = i; | ||
276 | break; | ||
277 | } else { | ||
278 | knownStart = segment.end; | ||
279 | startIndex = i + 1; | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | |||
284 | // time was equal to or past the end of the last segment in the playlist | ||
285 | if (startIndex === numSegments) { | ||
286 | return numSegments; | ||
287 | } | ||
288 | |||
289 | // use the bounds we just found and playlist information to | ||
290 | // estimate the segment that contains the time we are looking for | ||
291 | if (typeof startIndex !== 'undefined') { | ||
292 | // We have a known-start point that is before our desired time so | ||
293 | // walk from that point forwards | ||
294 | time = time - knownStart; | ||
295 | for (i = startIndex; i < (endIndex || numSegments); i++) { | ||
296 | segment = playlist.segments[i]; | ||
297 | time -= segment.duration; | ||
298 | |||
299 | if (time < 0) { | ||
300 | return i; | ||
301 | } | ||
302 | } | ||
303 | |||
304 | if (i >= endIndex) { | ||
305 | // We haven't found a segment but we did hit a known end point | ||
306 | // so fallback to interpolating between the segment index | ||
307 | // based on the known span of the timeline we are dealing with | ||
308 | // and the number of segments inside that span | ||
309 | return startIndex + Math.floor( | ||
310 | ((originalTime - knownStart) / (knownEnd - knownStart)) * | ||
311 | (endIndex - startIndex)); | ||
312 | } | ||
313 | |||
314 | // We _still_ haven't found a segment so load the last one | ||
315 | return lastSegment; | ||
316 | } else if (typeof endIndex !== 'undefined') { | ||
317 | // We _only_ have a known-end point that is after our desired time so | ||
318 | // walk from that point backwards | ||
319 | time = knownEnd - time; | ||
320 | for (i = endIndex; i >= 0; i--) { | ||
321 | segment = playlist.segments[i]; | ||
322 | time -= segment.duration; | ||
323 | |||
324 | if (time < 0) { | ||
325 | return i; | ||
326 | } | ||
327 | } | ||
328 | |||
329 | // We haven't found a segment so load the first one if time is zero | ||
330 | if (time === 0) { | ||
331 | return 0; | ||
332 | } | ||
333 | return -1; | ||
334 | } | ||
335 | // We known nothing so walk from the front of the playlist, | ||
336 | // subtracting durations until we find a segment that contains | ||
337 | // time and return it | ||
338 | time = time - expired; | ||
339 | |||
340 | if (time < 0) { | ||
341 | return -1; | ||
342 | } | ||
343 | |||
344 | for (i = 0; i < numSegments; i++) { | ||
345 | segment = playlist.segments[i]; | ||
346 | time -= segment.duration; | ||
347 | if (time < 0) { | ||
348 | return i; | ||
349 | } | ||
350 | } | ||
351 | // We are out of possible candidates so load the last one... | ||
352 | // The last one is the least likely to overlap a buffer and therefore | ||
353 | // the one most likely to tell us something about the timeline | ||
354 | return lastSegment; | ||
355 | }; | ||
356 | |||
204 | Playlist.duration = duration; | 357 | Playlist.duration = duration; |
205 | Playlist.seekable = seekable; | 358 | Playlist.seekable = seekable; |
359 | Playlist.getMediaIndexForTime_ = getMediaIndexForTime_; | ||
206 | 360 | ||
207 | // exports | 361 | // exports |
208 | export default Playlist; | 362 | export default Playlist; | ... | ... |
src/ranges.js
0 → 100644
1 | /** | ||
2 | * ranges | ||
3 | * | ||
4 | * Utilities for working with TimeRanges. | ||
5 | * | ||
6 | */ | ||
7 | |||
8 | import videojs from 'video.js'; | ||
9 | |||
10 | // Fudge factor to account for TimeRanges rounding | ||
11 | const TIME_FUDGE_FACTOR = 1 / 30; | ||
12 | |||
13 | const filterRanges = function(timeRanges, predicate) { | ||
14 | let results = []; | ||
15 | let i; | ||
16 | |||
17 | if (timeRanges && timeRanges.length) { | ||
18 | // Search for ranges that match the predicate | ||
19 | for (i = 0; i < timeRanges.length; i++) { | ||
20 | if (predicate(timeRanges.start(i), timeRanges.end(i))) { | ||
21 | results.push([timeRanges.start(i), timeRanges.end(i)]); | ||
22 | } | ||
23 | } | ||
24 | } | ||
25 | |||
26 | return videojs.createTimeRanges(results); | ||
27 | }; | ||
28 | |||
29 | /** | ||
30 | * Attempts to find the buffered TimeRange that contains the specified | ||
31 | * time. | ||
32 | * @param {TimeRanges} buffered - the TimeRanges object to query | ||
33 | * @param {number} time - the time to filter on. | ||
34 | * @returns {TimeRanges} a new TimeRanges object | ||
35 | */ | ||
36 | const findRange = function(buffered, time) { | ||
37 | return filterRanges(buffered, function(start, end) { | ||
38 | return start - TIME_FUDGE_FACTOR <= time && | ||
39 | end + TIME_FUDGE_FACTOR >= time; | ||
40 | }); | ||
41 | }; | ||
42 | |||
43 | /** | ||
44 | * Returns the TimeRanges that begin at or later than the specified | ||
45 | * time. | ||
46 | * @param {TimeRanges} timeRanges - the TimeRanges object to query | ||
47 | * @param {number} time - the time to filter on. | ||
48 | * @returns {TimeRanges} a new TimeRanges object. | ||
49 | */ | ||
50 | const findNextRange = function(timeRanges, time) { | ||
51 | return filterRanges(timeRanges, function(start) { | ||
52 | return start - TIME_FUDGE_FACTOR >= time; | ||
53 | }); | ||
54 | }; | ||
55 | |||
56 | /** | ||
57 | * Search for a likely end time for the segment that was just appened | ||
58 | * based on the state of the `buffered` property before and after the | ||
59 | * append. If we fin only one such uncommon end-point return it. | ||
60 | * @param {TimeRanges} original - the buffered time ranges before the update | ||
61 | * @param {TimeRanges} update - the buffered time ranges after the update | ||
62 | * @returns {Number|null} the end time added between `original` and `update`, | ||
63 | * or null if one cannot be unambiguously determined. | ||
64 | */ | ||
65 | const findSoleUncommonTimeRangesEnd = function(original, update) { | ||
66 | let i; | ||
67 | let start; | ||
68 | let end; | ||
69 | let result = []; | ||
70 | let edges = []; | ||
71 | |||
72 | // In order to qualify as a possible candidate, the end point must: | ||
73 | // 1) Not have already existed in the `original` ranges | ||
74 | // 2) Not result from the shrinking of a range that already existed | ||
75 | // in the `original` ranges | ||
76 | // 3) Not be contained inside of a range that existed in `original` | ||
77 | const overlapsCurrentEnd = function(span) { | ||
78 | return (span[0] <= end && span[1] >= end); | ||
79 | }; | ||
80 | |||
81 | if (original) { | ||
82 | // Save all the edges in the `original` TimeRanges object | ||
83 | for (i = 0; i < original.length; i++) { | ||
84 | start = original.start(i); | ||
85 | end = original.end(i); | ||
86 | |||
87 | edges.push([start, end]); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | if (update) { | ||
92 | // Save any end-points in `update` that are not in the `original` | ||
93 | // TimeRanges object | ||
94 | for (i = 0; i < update.length; i++) { | ||
95 | start = update.start(i); | ||
96 | end = update.end(i); | ||
97 | |||
98 | if (edges.some(overlapsCurrentEnd)) { | ||
99 | continue; | ||
100 | } | ||
101 | |||
102 | // at this point it must be a unique non-shrinking end edge | ||
103 | result.push(end); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | // we err on the side of caution and return null if didn't find | ||
108 | // exactly *one* differing end edge in the search above | ||
109 | if (result.length !== 1) { | ||
110 | return null; | ||
111 | } | ||
112 | |||
113 | return result[0]; | ||
114 | }; | ||
115 | |||
116 | /** | ||
117 | * Calculate the intersection of two TimeRanges | ||
118 | * @param {TimeRanges} bufferA | ||
119 | * @param {TimeRanges} bufferB | ||
120 | * @returns {TimeRanges} The interesection of `bufferA` with `bufferB` | ||
121 | */ | ||
122 | const bufferIntersection = function(bufferA, bufferB) { | ||
123 | let start = null; | ||
124 | let end = null; | ||
125 | let arity = 0; | ||
126 | let extents = []; | ||
127 | let ranges = []; | ||
128 | |||
129 | if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { | ||
130 | return videojs.createTimeRange(); | ||
131 | } | ||
132 | |||
133 | // Handle the case where we have both buffers and create an | ||
134 | // intersection of the two | ||
135 | let count = bufferA.length; | ||
136 | |||
137 | // A) Gather up all start and end times | ||
138 | while (count--) { | ||
139 | extents.push({time: bufferA.start(count), type: 'start'}); | ||
140 | extents.push({time: bufferA.end(count), type: 'end'}); | ||
141 | } | ||
142 | count = bufferB.length; | ||
143 | while (count--) { | ||
144 | extents.push({time: bufferB.start(count), type: 'start'}); | ||
145 | extents.push({time: bufferB.end(count), type: 'end'}); | ||
146 | } | ||
147 | // B) Sort them by time | ||
148 | extents.sort(function(a, b) { | ||
149 | return a.time - b.time; | ||
150 | }); | ||
151 | |||
152 | // C) Go along one by one incrementing arity for start and decrementing | ||
153 | // arity for ends | ||
154 | for (count = 0; count < extents.length; count++) { | ||
155 | if (extents[count].type === 'start') { | ||
156 | arity++; | ||
157 | |||
158 | // D) If arity is ever incremented to 2 we are entering an | ||
159 | // overlapping range | ||
160 | if (arity === 2) { | ||
161 | start = extents[count].time; | ||
162 | } | ||
163 | } else if (extents[count].type === 'end') { | ||
164 | arity--; | ||
165 | |||
166 | // E) If arity is ever decremented to 1 we leaving an | ||
167 | // overlapping range | ||
168 | if (arity === 1) { | ||
169 | end = extents[count].time; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // F) Record overlapping ranges | ||
174 | if (start !== null && end !== null) { | ||
175 | ranges.push([start, end]); | ||
176 | start = null; | ||
177 | end = null; | ||
178 | } | ||
179 | } | ||
180 | |||
181 | return videojs.createTimeRanges(ranges); | ||
182 | }; | ||
183 | |||
184 | /** | ||
185 | * Calculates the percentage of `segmentRange` that overlaps the | ||
186 | * `buffered` time ranges. | ||
187 | * @param {TimeRanges} segmentRange - the time range that the segment covers | ||
188 | * @param {TimeRanges} buffered - the currently buffered time ranges | ||
189 | * @returns {Number} percent of the segment currently buffered | ||
190 | */ | ||
191 | const calculateBufferedPercent = function(segmentRange, buffered) { | ||
192 | let segmentDuration = segmentRange.end(0) - segmentRange.start(0); | ||
193 | let intersection = bufferIntersection(segmentRange, buffered); | ||
194 | let overlapDuration = 0; | ||
195 | let count = intersection.length; | ||
196 | |||
197 | while (count--) { | ||
198 | overlapDuration += intersection.end(count) - intersection.start(count); | ||
199 | } | ||
200 | |||
201 | return (overlapDuration / segmentDuration) * 100; | ||
202 | }; | ||
203 | |||
204 | export default { | ||
205 | findRange, | ||
206 | findNextRange, | ||
207 | findSoleUncommonTimeRangesEnd, | ||
208 | calculateBufferedPercent, | ||
209 | TIME_FUDGE_FACTOR | ||
210 | }; |
1 | /** | ||
2 | * @file resolve-url.js | ||
3 | */ | ||
1 | import document from 'global/document'; | 4 | import document from 'global/document'; |
2 | /* eslint-disable max-len */ | ||
3 | /** | 5 | /** |
4 | * Constructs a new URI by interpreting a path relative to another | 6 | * Constructs a new URI by interpreting a path relative to another |
5 | * URI. | 7 | * URI. |
6 | * @param basePath {string} a relative or absolute URI | 8 | * |
7 | * @param path {string} a path part to combine with the base | ||
8 | * @return {string} a URI that is equivalent to composing `base` | ||
9 | * with `path` | ||
10 | * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue | 9 | * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue |
10 | * @param {String} basePath a relative or absolute URI | ||
11 | * @param {String} path a path part to combine with the base | ||
12 | * @return {String} a URI that is equivalent to composing `base` | ||
13 | * with `path` | ||
11 | */ | 14 | */ |
12 | /* eslint-enable max-len */ | ||
13 | const resolveUrl = function(basePath, path) { | 15 | const resolveUrl = function(basePath, path) { |
14 | // use the base element to get the browser to handle URI resolution | 16 | // use the base element to get the browser to handle URI resolution |
15 | let oldBase = document.querySelector('base'); | 17 | let oldBase = document.querySelector('base'); | ... | ... |
src/segment-loader.js
0 → 100644
This diff is collapsed.
Click to expand it.
src/source-updater.js
0 → 100644
1 | /** | ||
2 | * @file source-updater.js | ||
3 | */ | ||
4 | import videojs from 'video.js'; | ||
5 | |||
6 | /** | ||
7 | * A queue of callbacks to be serialized and applied when a | ||
8 | * MediaSource and its associated SourceBuffers are not in the | ||
9 | * updating state. It is used by the segment loader to update the | ||
10 | * underlying SourceBuffers when new data is loaded, for instance. | ||
11 | * | ||
12 | * @class SourceUpdater | ||
13 | * @param {MediaSource} mediaSource the MediaSource to create the | ||
14 | * SourceBuffer from | ||
15 | * @param {String} mimeType the desired MIME type of the underlying | ||
16 | * SourceBuffer | ||
17 | */ | ||
18 | export default class SourceUpdater { | ||
19 | constructor(mediaSource, mimeType) { | ||
20 | let createSourceBuffer = () => { | ||
21 | this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType); | ||
22 | |||
23 | // run completion handlers and process callbacks as updateend | ||
24 | // events fire | ||
25 | this.sourceBuffer_.addEventListener('updateend', () => { | ||
26 | let pendingCallback = this.pendingCallback_; | ||
27 | |||
28 | this.pendingCallback_ = null; | ||
29 | |||
30 | if (pendingCallback) { | ||
31 | pendingCallback(); | ||
32 | } | ||
33 | }); | ||
34 | this.sourceBuffer_.addEventListener('updateend', | ||
35 | this.runCallback_.bind(this)); | ||
36 | |||
37 | this.runCallback_(); | ||
38 | }; | ||
39 | |||
40 | this.callbacks_ = []; | ||
41 | this.pendingCallback_ = null; | ||
42 | this.timestampOffset_ = 0; | ||
43 | this.mediaSource = mediaSource; | ||
44 | |||
45 | if (mediaSource.readyState === 'closed') { | ||
46 | mediaSource.addEventListener('sourceopen', createSourceBuffer); | ||
47 | } else { | ||
48 | createSourceBuffer(); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Aborts the current segment and resets the segment parser. | ||
54 | * | ||
55 | * @param {Function} done function to call when done | ||
56 | * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void | ||
57 | */ | ||
58 | abort(done) { | ||
59 | this.queueCallback_(() => { | ||
60 | this.sourceBuffer_.abort(); | ||
61 | }, done); | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * Queue an update to append an ArrayBuffer. | ||
66 | * | ||
67 | * @param {ArrayBuffer} bytes | ||
68 | * @param {Function} done the function to call when done | ||
69 | * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data | ||
70 | */ | ||
71 | appendBuffer(bytes, done) { | ||
72 | this.queueCallback_(() => { | ||
73 | this.sourceBuffer_.appendBuffer(bytes); | ||
74 | }, done); | ||
75 | } | ||
76 | |||
77 | /** | ||
78 | * Indicates what TimeRanges are buffered in the managed SourceBuffer. | ||
79 | * | ||
80 | * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered | ||
81 | */ | ||
82 | buffered() { | ||
83 | if (!this.sourceBuffer_) { | ||
84 | return videojs.createTimeRanges(); | ||
85 | } | ||
86 | return this.sourceBuffer_.buffered; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Queue an update to set the duration. | ||
91 | * | ||
92 | * @param {Double} duration what to set the duration to | ||
93 | * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration | ||
94 | */ | ||
95 | duration(duration) { | ||
96 | this.queueCallback_(() => { | ||
97 | this.sourceBuffer_.duration = duration; | ||
98 | }); | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * Queue an update to remove a time range from the buffer. | ||
103 | * | ||
104 | * @param {Number} start where to start the removal | ||
105 | * @param {Number} end where to end the removal | ||
106 | * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end | ||
107 | */ | ||
108 | remove(start, end) { | ||
109 | this.queueCallback_(() => { | ||
110 | this.sourceBuffer_.remove(start, end); | ||
111 | }); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * wether the underlying sourceBuffer is updating or not | ||
116 | * | ||
117 | * @return {Boolean} the updating status of the SourceBuffer | ||
118 | */ | ||
119 | updating() { | ||
120 | return !this.sourceBuffer_ || this.sourceBuffer_.updating; | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * Set/get the timestampoffset on the SourceBuffer | ||
125 | * | ||
126 | * @return {Number} the timestamp offset | ||
127 | */ | ||
128 | timestampOffset(offset) { | ||
129 | if (typeof offset !== 'undefined') { | ||
130 | this.queueCallback_(() => { | ||
131 | this.sourceBuffer_.timestampOffset = offset; | ||
132 | }); | ||
133 | this.timestampOffset_ = offset; | ||
134 | } | ||
135 | return this.timestampOffset_; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * que a callback to run | ||
140 | */ | ||
141 | queueCallback_(callback, done) { | ||
142 | this.callbacks_.push([callback.bind(this), done]); | ||
143 | this.runCallback_(); | ||
144 | } | ||
145 | |||
146 | /** | ||
147 | * run a queued callback | ||
148 | */ | ||
149 | runCallback_() { | ||
150 | let callbacks; | ||
151 | |||
152 | if (this.sourceBuffer_ && | ||
153 | !this.sourceBuffer_.updating && | ||
154 | this.callbacks_.length) { | ||
155 | callbacks = this.callbacks_.shift(); | ||
156 | this.pendingCallback_ = callbacks[1]; | ||
157 | callbacks[0](); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * dispose of the source updater and the underlying sourceBuffer | ||
163 | */ | ||
164 | dispose() { | ||
165 | if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { | ||
166 | this.sourceBuffer_.abort(); | ||
167 | } | ||
168 | } | ||
169 | } |
1 | /** | 1 | /** |
2 | * @file stream.js | ||
3 | */ | ||
4 | /** | ||
2 | * A lightweight readable stream implemention that handles event dispatching. | 5 | * A lightweight readable stream implemention that handles event dispatching. |
6 | * | ||
7 | * @class Stream | ||
3 | */ | 8 | */ |
4 | export default class Stream { | 9 | export default class Stream { |
5 | constructor() { | 10 | constructor() { |
... | @@ -8,8 +13,9 @@ export default class Stream { | ... | @@ -8,8 +13,9 @@ export default class Stream { |
8 | 13 | ||
9 | /** | 14 | /** |
10 | * Add a listener for a specified event type. | 15 | * Add a listener for a specified event type. |
11 | * @param type {string} the event name | 16 | * |
12 | * @param listener {function} the callback to be invoked when an event of | 17 | * @param {String} type the event name |
18 | * @param {Function} listener the callback to be invoked when an event of | ||
13 | * the specified type occurs | 19 | * the specified type occurs |
14 | */ | 20 | */ |
15 | on(type, listener) { | 21 | on(type, listener) { |
... | @@ -21,9 +27,11 @@ export default class Stream { | ... | @@ -21,9 +27,11 @@ export default class Stream { |
21 | 27 | ||
22 | /** | 28 | /** |
23 | * Remove a listener for a specified event type. | 29 | * Remove a listener for a specified event type. |
24 | * @param type {string} the event name | 30 | * |
25 | * @param listener {function} a function previously registered for this | 31 | * @param {String} type the event name |
32 | * @param {Function} listener a function previously registered for this | ||
26 | * type of event through `on` | 33 | * type of event through `on` |
34 | * @return {Boolean} if we could turn it off or not | ||
27 | */ | 35 | */ |
28 | off(type, listener) { | 36 | off(type, listener) { |
29 | let index; | 37 | let index; |
... | @@ -39,7 +47,8 @@ export default class Stream { | ... | @@ -39,7 +47,8 @@ export default class Stream { |
39 | /** | 47 | /** |
40 | * Trigger an event of the specified type on this stream. Any additional | 48 | * Trigger an event of the specified type on this stream. Any additional |
41 | * arguments to this function are passed as parameters to event listeners. | 49 | * arguments to this function are passed as parameters to event listeners. |
42 | * @param type {string} the event name | 50 | * |
51 | * @param {String} type the event name | ||
43 | */ | 52 | */ |
44 | trigger(type) { | 53 | trigger(type) { |
45 | let callbacks; | 54 | let callbacks; |
... | @@ -79,7 +88,8 @@ export default class Stream { | ... | @@ -79,7 +88,8 @@ export default class Stream { |
79 | * Forwards all `data` events on this stream to the destination stream. The | 88 | * Forwards all `data` events on this stream to the destination stream. The |
80 | * destination stream should provide a method `push` to receive the data | 89 | * destination stream should provide a method `push` to receive the data |
81 | * events as they arrive. | 90 | * events as they arrive. |
82 | * @param destination {stream} the stream that will receive all `data` events | 91 | * |
92 | * @param {Stream} destination the stream that will receive all `data` events | ||
83 | * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options | 93 | * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options |
84 | */ | 94 | */ |
85 | pipe(destination) { | 95 | pipe(destination) { | ... | ... |
This diff is collapsed.
Click to expand it.
1 | /** | 1 | /** |
2 | * @file xhr.js | ||
3 | */ | ||
4 | |||
5 | /** | ||
2 | * A wrapper for videojs.xhr that tracks bandwidth. | 6 | * A wrapper for videojs.xhr that tracks bandwidth. |
7 | * | ||
8 | * @param {Object} options options for the XHR | ||
9 | * @param {Function} callback the callback to call when done | ||
10 | * @return {Request} the xhr request that is going to be made | ||
3 | */ | 11 | */ |
4 | import {xhr as videojsXHR, mergeOptions} from 'video.js'; | 12 | import {xhr as videojsXHR, mergeOptions} from 'video.js'; |
5 | 13 | ... | ... |
test/hls-audio-track.test.js
0 → 100644
1 | import HlsAudioTrack from '../src/hls-audio-track'; | ||
2 | import QUnit from 'qunit'; | ||
3 | |||
4 | // Most of these tests will be done in video.js.AudioTrack unit tests | ||
5 | QUnit.module('HlsAudioTrack - Props'); | ||
6 | |||
7 | QUnit.test('verify that props are readonly and can be set', function() { | ||
8 | let props = { | ||
9 | default: true, | ||
10 | language: 'en', | ||
11 | label: 'English', | ||
12 | autoselect: true, | ||
13 | withCredentials: true, | ||
14 | // below props won't be used, its used for checking | ||
15 | kind: 'main' | ||
16 | }; | ||
17 | |||
18 | let track = new HlsAudioTrack(props); | ||
19 | |||
20 | for (let k in props) { | ||
21 | QUnit.equal(track[k], props[k], `${k} should be stored in track`); | ||
22 | } | ||
23 | }); | ||
24 | |||
25 | QUnit.test('can start with a mediaGroup that has a uri', function() { | ||
26 | let props = { | ||
27 | default: true, | ||
28 | language: 'en', | ||
29 | label: 'English', | ||
30 | autoselect: true, | ||
31 | mediaGroup: 'foo', | ||
32 | withCredentials: true, | ||
33 | resolvedUri: 'http://some.test.url/playlist.m3u8', | ||
34 | // below props won't be used, its used for checking | ||
35 | enabled: true, | ||
36 | kind: 'main' | ||
37 | }; | ||
38 | let track = new HlsAudioTrack(props); | ||
39 | |||
40 | QUnit.equal(track.mediaGroups_.length, 1, 'loader was created'); | ||
41 | let loader = track.getLoader('foo'); | ||
42 | |||
43 | QUnit.ok(loader, 'can getLoader on foo'); | ||
44 | |||
45 | track.dispose(); | ||
46 | QUnit.equal(track.mediaGroups_.length, 0, 'loader disposed'); | ||
47 | }); | ||
48 | |||
49 | QUnit.test('can start with a mediaGroup that has no uri', function() { | ||
50 | let props = { | ||
51 | default: true, | ||
52 | language: 'en', | ||
53 | label: 'English', | ||
54 | autoselect: true, | ||
55 | mediaGroup: 'foo', | ||
56 | withCredentials: true, | ||
57 | // below props won't be used, its used for checking | ||
58 | enabled: true, | ||
59 | kind: 'main' | ||
60 | }; | ||
61 | let track = new HlsAudioTrack(props); | ||
62 | |||
63 | QUnit.equal(track.mediaGroups_.length, 1, 'mediaGroupLoader was created for foo'); | ||
64 | QUnit.ok(!track.getLoader('foo'), 'can getLoader on foo, but it is undefined'); | ||
65 | |||
66 | track.dispose(); | ||
67 | QUnit.equal(track.mediaGroups_.length, 0, 'loaders disposed'); | ||
68 | }); | ||
69 | |||
70 | QUnit.module('HlsAudioTrack - Loader', { | ||
71 | beforeEach() { | ||
72 | this.track = new HlsAudioTrack({ | ||
73 | mediaGroup: 'default', | ||
74 | default: true, | ||
75 | language: 'en', | ||
76 | label: 'English', | ||
77 | autoselect: true, | ||
78 | withCredentials: true | ||
79 | }); | ||
80 | }, | ||
81 | afterEach() { | ||
82 | this.track.dispose(); | ||
83 | QUnit.equal(this.track.mediaGroups_.length, 0, 'zero loaders after dispose'); | ||
84 | } | ||
85 | }); | ||
86 | |||
87 | QUnit.test('can add a playlist loader', function() { | ||
88 | QUnit.equal(this.track.mediaGroups_.length, 1, '1 loader to start'); | ||
89 | |||
90 | this.track.addLoader('foo', 'someurl'); | ||
91 | this.track.addLoader('bar', 'someurl'); | ||
92 | this.track.addLoader('baz', 'someurl'); | ||
93 | |||
94 | QUnit.equal(this.track.mediaGroups_.length, 4, 'now has four loaders'); | ||
95 | }); | ||
96 | |||
97 | QUnit.test('can remove playlist loader', function() { | ||
98 | QUnit.equal(this.track.mediaGroups_.length, 1, 'one loaders to start'); | ||
99 | |||
100 | this.track.addLoader('foo', 'someurl'); | ||
101 | this.track.addLoader('baz', 'someurl'); | ||
102 | |||
103 | QUnit.equal(this.track.mediaGroups_.length, 3, 'now has three loaders'); | ||
104 | |||
105 | this.track.removeLoader('baz'); | ||
106 | QUnit.equal(this.track.mediaGroups_.length, 2, 'now has two loaders'); | ||
107 | |||
108 | }); |
... | @@ -10,13 +10,10 @@ var DEFAULTS = { | ... | @@ -10,13 +10,10 @@ var DEFAULTS = { |
10 | 'node_modules/sinon/pkg/sinon-ie.js', | 10 | 'node_modules/sinon/pkg/sinon-ie.js', |
11 | 'node_modules/video.js/dist/video.js', | 11 | 'node_modules/video.js/dist/video.js', |
12 | 'node_modules/video.js/dist/video-js.css', | 12 | 'node_modules/video.js/dist/video-js.css', |
13 | |||
14 | 'test/**/*.test.js' | 13 | 'test/**/*.test.js' |
15 | ], | 14 | ], |
16 | 15 | ||
17 | exclude: [ | 16 | exclude: [], |
18 | 'test/data/**' | ||
19 | ], | ||
20 | 17 | ||
21 | plugins: [ | 18 | plugins: [ |
22 | 'karma-browserify', | 19 | 'karma-browserify', |
... | @@ -43,6 +40,13 @@ var DEFAULTS = { | ... | @@ -43,6 +40,13 @@ var DEFAULTS = { |
43 | noParse: [ | 40 | noParse: [ |
44 | 'test/data/**', | 41 | 'test/data/**', |
45 | ] | 42 | ] |
43 | }, | ||
44 | |||
45 | customLaunchers: { | ||
46 | travisChrome: { | ||
47 | base: 'Chrome', | ||
48 | flags: ['--no-sandbox'] | ||
49 | } | ||
46 | } | 50 | } |
47 | }; | 51 | }; |
48 | 52 | ... | ... |
... | @@ -4,12 +4,10 @@ var common = require('./common'); | ... | @@ -4,12 +4,10 @@ var common = require('./common'); |
4 | 4 | ||
5 | module.exports = function(config) { | 5 | module.exports = function(config) { |
6 | 6 | ||
7 | // Travis CI should run in its available Firefox headless browser. | ||
8 | if (process.env.TRAVIS) { | 7 | if (process.env.TRAVIS) { |
9 | |||
10 | config.set(common({ | 8 | config.set(common({ |
11 | browsers: ['Firefox'], | 9 | browsers: ['travisChrome'], |
12 | plugins: ['karma-firefox-launcher'] | 10 | plugins: ['karma-chrome-launcher'] |
13 | })) | 11 | })) |
14 | } else { | 12 | } else { |
15 | config.set(common({ | 13 | config.set(common({ | ... | ... |
test/master-playlist-controller.test.js
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
1 | import Playlist from '../src/playlist'; | 1 | import Playlist from '../src/playlist'; |
2 | import PlaylistLoader from '../src/playlist-loader'; | ||
2 | import QUnit from 'qunit'; | 3 | import QUnit from 'qunit'; |
4 | import xhrFactory from '../src/xhr'; | ||
5 | import { useFakeEnvironment } from './test-helpers'; | ||
6 | |||
3 | QUnit.module('Playlist Duration'); | 7 | QUnit.module('Playlist Duration'); |
4 | 8 | ||
5 | QUnit.test('total duration for live playlists is Infinity', function() { | 9 | QUnit.test('total duration for live playlists is Infinity', function() { |
... | @@ -376,3 +380,251 @@ QUnit.test('seekable end accounts for non-standard target durations', function() | ... | @@ -376,3 +380,251 @@ QUnit.test('seekable end accounts for non-standard target durations', function() |
376 | 9 - (2 + 2 + 1), | 380 | 9 - (2 + 2 + 1), |
377 | 'allows seeking no further than three segments from the end'); | 381 | 'allows seeking no further than three segments from the end'); |
378 | }); | 382 | }); |
383 | |||
384 | QUnit.module('Playlist Media Index For Time', { | ||
385 | beforeEach() { | ||
386 | this.env = useFakeEnvironment(); | ||
387 | this.clock = this.env.clock; | ||
388 | this.requests = this.env.requests; | ||
389 | this.fakeHls = { | ||
390 | xhr: xhrFactory() | ||
391 | }; | ||
392 | }, | ||
393 | afterEach() { | ||
394 | this.env.restore(); | ||
395 | } | ||
396 | }); | ||
397 | |||
398 | QUnit.test('can get media index by playback position for non-live videos', function() { | ||
399 | let media; | ||
400 | let loader = new PlaylistLoader('media.m3u8', this.fakeHls); | ||
401 | |||
402 | loader.load(); | ||
403 | |||
404 | this.requests.shift().respond(200, null, | ||
405 | '#EXTM3U\n' + | ||
406 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
407 | '#EXTINF:4,\n' + | ||
408 | '0.ts\n' + | ||
409 | '#EXTINF:5,\n' + | ||
410 | '1.ts\n' + | ||
411 | '#EXTINF:6,\n' + | ||
412 | '2.ts\n' + | ||
413 | '#EXT-X-ENDLIST\n' | ||
414 | ); | ||
415 | |||
416 | media = loader.media(); | ||
417 | |||
418 | QUnit.equal(Playlist.getMediaIndexForTime_(media, -1), 0, | ||
419 | 'the index is never less than zero'); | ||
420 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 0), 0, 'time zero is index zero'); | ||
421 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 3), 0, 'time three is index zero'); | ||
422 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 10), 2, 'time 10 is index 2'); | ||
423 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 22), 2, | ||
424 | 'time greater than the length is index 2'); | ||
425 | }); | ||
426 | |||
427 | QUnit.test('returns the lower index when calculating for a segment boundary', function() { | ||
428 | let media; | ||
429 | let loader = new PlaylistLoader('media.m3u8', this.fakeHls); | ||
430 | |||
431 | loader.load(); | ||
432 | |||
433 | this.requests.shift().respond(200, null, | ||
434 | '#EXTM3U\n' + | ||
435 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
436 | '#EXTINF:4,\n' + | ||
437 | '0.ts\n' + | ||
438 | '#EXTINF:5,\n' + | ||
439 | '1.ts\n' + | ||
440 | '#EXT-X-ENDLIST\n' | ||
441 | ); | ||
442 | |||
443 | media = loader.media(); | ||
444 | |||
445 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 4), 1, 'rounds up exact matches'); | ||
446 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 3.7), 0, 'rounds down'); | ||
447 | QUnit.equal(Playlist.getMediaIndexForTime_(media, 4.5), 1, 'rounds up at 0.5'); | ||
448 | }); | ||
449 | |||
450 | QUnit.test( | ||
451 | 'accounts for non-zero starting segment time when calculating media index', | ||
452 | function() { | ||
453 | let media; | ||
454 | let loader = new PlaylistLoader('media.m3u8', this.fakeHls); | ||
455 | |||
456 | loader.load(); | ||
457 | |||
458 | this.requests.shift().respond(200, null, | ||
459 | '#EXTM3U\n' + | ||
460 | '#EXT-X-MEDIA-SEQUENCE:1001\n' + | ||
461 | '#EXTINF:4,\n' + | ||
462 | '1001.ts\n' + | ||
463 | '#EXTINF:5,\n' + | ||
464 | '1002.ts\n' | ||
465 | ); | ||
466 | loader.media().segments[0].end = 154; | ||
467 | |||
468 | media = loader.media(); | ||
469 | |||
470 | QUnit.equal( | ||
471 | Playlist.getMediaIndexForTime_(media, 0), | ||
472 | -1, | ||
473 | 'the lowest returned value is negative one' | ||
474 | ); | ||
475 | QUnit.equal( | ||
476 | Playlist.getMediaIndexForTime_(media, 45), | ||
477 | -1, | ||
478 | 'expired content returns negative one' | ||
479 | ); | ||
480 | QUnit.equal( | ||
481 | Playlist.getMediaIndexForTime_(media, 75), | ||
482 | -1, | ||
483 | 'expired content returns negative one' | ||
484 | ); | ||
485 | QUnit.equal( | ||
486 | Playlist.getMediaIndexForTime_(media, 50 + 100), | ||
487 | 0, | ||
488 | 'calculates the earliest available position' | ||
489 | ); | ||
490 | QUnit.equal( | ||
491 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 2), | ||
492 | 0, | ||
493 | 'calculates within the first segment' | ||
494 | ); | ||
495 | QUnit.equal( | ||
496 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 2), | ||
497 | 0, | ||
498 | 'calculates within the first segment' | ||
499 | ); | ||
500 | QUnit.equal( | ||
501 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 4), | ||
502 | 1, | ||
503 | 'calculates within the second segment' | ||
504 | ); | ||
505 | QUnit.equal( | ||
506 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 4.5), | ||
507 | 1, | ||
508 | 'calculates within the second segment' | ||
509 | ); | ||
510 | QUnit.equal( | ||
511 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 6), | ||
512 | 1, | ||
513 | 'calculates within the second segment' | ||
514 | ); | ||
515 | |||
516 | loader.media().segments[1].end = 159; | ||
517 | QUnit.equal( | ||
518 | Playlist.getMediaIndexForTime_(media, 159), | ||
519 | 2, | ||
520 | 'returns number of segments when time is equal to end of last segment' | ||
521 | ); | ||
522 | QUnit.equal( | ||
523 | Playlist.getMediaIndexForTime_(media, 159.1), | ||
524 | 2, | ||
525 | 'returns number of segments when time is past end of last segment' | ||
526 | ); | ||
527 | }); | ||
528 | |||
529 | QUnit.test('prefers precise segment timing when tracking expired time', function() { | ||
530 | let media; | ||
531 | let loader = new PlaylistLoader('media.m3u8', this.fakeHls); | ||
532 | |||
533 | loader.load(); | ||
534 | |||
535 | loader.trigger('firstplay'); | ||
536 | this.requests.shift().respond(200, null, | ||
537 | '#EXTM3U\n' + | ||
538 | '#EXT-X-MEDIA-SEQUENCE:1001\n' + | ||
539 | '#EXTINF:4,\n' + | ||
540 | '1001.ts\n' + | ||
541 | '#EXTINF:5,\n' + | ||
542 | '1002.ts\n' | ||
543 | ); | ||
544 | // setup the loader with an "imprecise" value as if it had been | ||
545 | // accumulating segment durations as they expire | ||
546 | loader.expired_ = 160; | ||
547 | // annotate the first segment with a start time | ||
548 | // this number would be coming from the Source Buffer in practice | ||
549 | loader.media().segments[0].end = 150; | ||
550 | |||
551 | media = loader.media(); | ||
552 | |||
553 | QUnit.equal( | ||
554 | Playlist.getMediaIndexForTime_(media, 149), | ||
555 | 0, | ||
556 | 'prefers the value on the first segment' | ||
557 | ); | ||
558 | |||
559 | // trigger a playlist refresh | ||
560 | this.clock.tick(10 * 1000); | ||
561 | this.requests.shift().respond(200, null, | ||
562 | '#EXTM3U\n' + | ||
563 | '#EXT-X-MEDIA-SEQUENCE:1002\n' + | ||
564 | '#EXTINF:5,\n' + | ||
565 | '1002.ts\n' | ||
566 | ); | ||
567 | |||
568 | media = loader.media(); | ||
569 | |||
570 | QUnit.equal( | ||
571 | Playlist.getMediaIndexForTime_(media, 150 + 4 + 1), | ||
572 | 0, | ||
573 | 'tracks precise expired times' | ||
574 | ); | ||
575 | }); | ||
576 | |||
577 | QUnit.test('accounts for expired time when calculating media index', function() { | ||
578 | let media; | ||
579 | let loader = new PlaylistLoader('media.m3u8', this.fakeHls); | ||
580 | let expired = 150; | ||
581 | |||
582 | loader.load(); | ||
583 | |||
584 | this.requests.shift().respond(200, null, | ||
585 | '#EXTM3U\n' + | ||
586 | '#EXT-X-MEDIA-SEQUENCE:1001\n' + | ||
587 | '#EXTINF:4,\n' + | ||
588 | '1001.ts\n' + | ||
589 | '#EXTINF:5,\n' + | ||
590 | '1002.ts\n' | ||
591 | ); | ||
592 | |||
593 | media = loader.media(); | ||
594 | |||
595 | QUnit.equal( | ||
596 | Playlist.getMediaIndexForTime_(media, 0, expired), | ||
597 | -1, | ||
598 | 'expired content returns a negative index' | ||
599 | ); | ||
600 | QUnit.equal( | ||
601 | Playlist.getMediaIndexForTime_(media, 75, expired), | ||
602 | -1, | ||
603 | 'expired content returns a negative index' | ||
604 | ); | ||
605 | QUnit.equal( | ||
606 | Playlist.getMediaIndexForTime_(media, 50 + 100, expired), | ||
607 | 0, | ||
608 | 'calculates the earliest available position' | ||
609 | ); | ||
610 | QUnit.equal( | ||
611 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 2, expired), | ||
612 | 0, | ||
613 | 'calculates within the first segment' | ||
614 | ); | ||
615 | QUnit.equal( | ||
616 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 2, expired), | ||
617 | 0, | ||
618 | 'calculates within the first segment' | ||
619 | ); | ||
620 | QUnit.equal( | ||
621 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 4.5, expired), | ||
622 | 1, | ||
623 | 'calculates within the second segment' | ||
624 | ); | ||
625 | QUnit.equal( | ||
626 | Playlist.getMediaIndexForTime_(media, 50 + 100 + 6, expired), | ||
627 | 1, | ||
628 | 'calculates within the second segment' | ||
629 | ); | ||
630 | }); | ... | ... |
test/ranges.test.js
0 → 100644
1 | import Ranges from '../src/ranges'; | ||
2 | import {createTimeRanges} from 'video.js'; | ||
3 | import QUnit from 'qunit'; | ||
4 | |||
5 | QUnit.module('TimeRanges Utilities'); | ||
6 | |||
7 | QUnit.test('finds the overlapping time range', function() { | ||
8 | let range = Ranges.findRange(createTimeRanges([[0, 5], [6, 12]]), 3); | ||
9 | |||
10 | QUnit.equal(range.length, 1, 'found one range'); | ||
11 | QUnit.equal(range.end(0), 5, 'inside the first buffered region'); | ||
12 | |||
13 | range = Ranges.findRange(createTimeRanges([[0, 5], [6, 12]]), 6); | ||
14 | QUnit.equal(range.length, 1, 'found one range'); | ||
15 | QUnit.equal(range.end(0), 12, 'inside the second buffered region'); | ||
16 | }); | ||
17 | |||
18 | QUnit.module('Buffer Inpsection'); | ||
19 | |||
20 | QUnit.test('detects time range end-point changed by updates', function() { | ||
21 | let edge; | ||
22 | |||
23 | // Single-range changes | ||
24 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10]]), | ||
25 | createTimeRanges([[0, 11]])); | ||
26 | QUnit.strictEqual(edge, 11, 'detected a forward addition'); | ||
27 | |||
28 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[5, 10]]), | ||
29 | createTimeRanges([[0, 10]])); | ||
30 | QUnit.strictEqual(edge, null, 'ignores backward addition'); | ||
31 | |||
32 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[5, 10]]), | ||
33 | createTimeRanges([[0, 11]])); | ||
34 | QUnit.strictEqual(edge, 11, | ||
35 | 'detected a forward addition & ignores a backward addition'); | ||
36 | |||
37 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10]]), | ||
38 | createTimeRanges([[0, 9]])); | ||
39 | QUnit.strictEqual(edge, null, | ||
40 | 'ignores a backwards addition resulting from a shrinking range'); | ||
41 | |||
42 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10]]), | ||
43 | createTimeRanges([[2, 7]])); | ||
44 | QUnit.strictEqual(edge, null, | ||
45 | 'ignores a forward & backwards addition resulting from a shrinking ' + | ||
46 | 'range'); | ||
47 | |||
48 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[2, 10]]), | ||
49 | createTimeRanges([[0, 7]])); | ||
50 | QUnit.strictEqual( | ||
51 | edge, | ||
52 | null, | ||
53 | 'ignores a forward & backwards addition resulting from a range shifted backward' | ||
54 | ); | ||
55 | |||
56 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[2, 10]]), | ||
57 | createTimeRanges([[5, 15]])); | ||
58 | QUnit.strictEqual(edge, 15, | ||
59 | 'detected a forwards addition resulting from a range shifted foward'); | ||
60 | |||
61 | // Multiple-range changes | ||
62 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10]]), | ||
63 | createTimeRanges([[0, 11], [12, 15]])); | ||
64 | QUnit.strictEqual(edge, null, 'ignores multiple new forward additions'); | ||
65 | |||
66 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10], [20, 40]]), | ||
67 | createTimeRanges([[20, 50]])); | ||
68 | QUnit.strictEqual(edge, 50, 'detected a forward addition & ignores range removal'); | ||
69 | |||
70 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10], [20, 40]]), | ||
71 | createTimeRanges([[0, 50]])); | ||
72 | QUnit.strictEqual(edge, 50, 'detected a forward addition & ignores merges'); | ||
73 | |||
74 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 10], [20, 40]]), | ||
75 | createTimeRanges([[0, 40]])); | ||
76 | QUnit.strictEqual(edge, null, 'ignores merges'); | ||
77 | |||
78 | // Empty input | ||
79 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges(), | ||
80 | createTimeRanges([[0, 11]])); | ||
81 | QUnit.strictEqual(edge, 11, 'handle an empty original TimeRanges object'); | ||
82 | |||
83 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 11]]), | ||
84 | createTimeRanges()); | ||
85 | QUnit.strictEqual(edge, null, 'handle an empty update TimeRanges object'); | ||
86 | |||
87 | // Null input | ||
88 | edge = Ranges.findSoleUncommonTimeRangesEnd(null, createTimeRanges([[0, 11]])); | ||
89 | QUnit.strictEqual(edge, 11, 'treat null original buffer as an empty TimeRanges object'); | ||
90 | |||
91 | edge = Ranges.findSoleUncommonTimeRangesEnd(createTimeRanges([[0, 11]]), null); | ||
92 | QUnit.strictEqual(edge, null, 'treat null update buffer as an empty TimeRanges object'); | ||
93 | }); |
test/segment-loader.test.js
0 → 100644
This diff is collapsed.
Click to expand it.
test/source-updater.test.js
0 → 100644
1 | import SourceUpdater from '../src/source-updater'; | ||
2 | import QUnit from 'qunit'; | ||
3 | import videojs from 'video.js'; | ||
4 | import { useFakeMediaSource } from './test-helpers'; | ||
5 | |||
6 | QUnit.module('Source Updater', { | ||
7 | beforeEach() { | ||
8 | this.mse = useFakeMediaSource(); | ||
9 | this.mediaSource = new videojs.MediaSource(); | ||
10 | }, | ||
11 | afterEach() { | ||
12 | this.mse.restore(); | ||
13 | } | ||
14 | }); | ||
15 | |||
16 | QUnit.test('waits for sourceopen to create a source buffer', function() { | ||
17 | new SourceUpdater(this.mediaSource, 'video/mp2t'); // eslint-disable-line no-new | ||
18 | |||
19 | QUnit.equal(this.mediaSource.sourceBuffers.length, 0, | ||
20 | 'waited to create the source buffer'); | ||
21 | |||
22 | this.mediaSource.trigger('sourceopen'); | ||
23 | |||
24 | QUnit.equal(this.mediaSource.sourceBuffers.length, 1, 'created one source buffer'); | ||
25 | QUnit.equal(this.mediaSource.sourceBuffers[0].mimeType_, 'video/mp2t', | ||
26 | 'assigned the correct MIME type'); | ||
27 | }); | ||
28 | |||
29 | QUnit.test('runs a callback when the source buffer is created', function() { | ||
30 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
31 | let sourceBuffer; | ||
32 | |||
33 | updater.appendBuffer(new Uint8Array([0, 1, 2])); | ||
34 | |||
35 | this.mediaSource.trigger('sourceopen'); | ||
36 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
37 | QUnit.equal(sourceBuffer.updates_.length, 1, 'called the source buffer once'); | ||
38 | QUnit.deepEqual(sourceBuffer.updates_[0].append, new Uint8Array([0, 1, 2]), | ||
39 | 'appended the bytes'); | ||
40 | }); | ||
41 | |||
42 | QUnit.test('runs the completion callback when updateend fires', function() { | ||
43 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
44 | let updateends = 0; | ||
45 | let sourceBuffer; | ||
46 | |||
47 | this.mediaSource.trigger('sourceopen'); | ||
48 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
49 | updater.appendBuffer(new Uint8Array([0, 1, 2]), function() { | ||
50 | updateends++; | ||
51 | }); | ||
52 | updater.appendBuffer(new Uint8Array([2, 3, 4]), function() { | ||
53 | throw new Error('Wrong completion callback invoked!'); | ||
54 | }); | ||
55 | |||
56 | QUnit.equal(updateends, 0, 'no completions yet'); | ||
57 | sourceBuffer.trigger('updateend'); | ||
58 | QUnit.equal(updateends, 1, 'ran the completion callback'); | ||
59 | }); | ||
60 | |||
61 | QUnit.test('runs the next callback after updateend fires', function() { | ||
62 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
63 | let sourceBuffer; | ||
64 | |||
65 | updater.appendBuffer(new Uint8Array([0, 1, 2])); | ||
66 | this.mediaSource.trigger('sourceopen'); | ||
67 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
68 | |||
69 | updater.appendBuffer(new Uint8Array([2, 3, 4])); | ||
70 | QUnit.equal(sourceBuffer.updates_.length, 1, 'delayed the update'); | ||
71 | |||
72 | sourceBuffer.trigger('updateend'); | ||
73 | QUnit.equal(sourceBuffer.updates_.length, 2, 'updated twice'); | ||
74 | QUnit.deepEqual(sourceBuffer.updates_[1].append, new Uint8Array([2, 3, 4]), | ||
75 | 'appended the bytes'); | ||
76 | }); | ||
77 | |||
78 | QUnit.test('runs only one callback at a time', function() { | ||
79 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
80 | let sourceBuffer; | ||
81 | |||
82 | updater.appendBuffer(new Uint8Array([0])); | ||
83 | updater.appendBuffer(new Uint8Array([1])); | ||
84 | this.mediaSource.trigger('sourceopen'); | ||
85 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
86 | |||
87 | updater.appendBuffer(new Uint8Array([2])); | ||
88 | QUnit.equal(sourceBuffer.updates_.length, 1, 'queued some updates'); | ||
89 | QUnit.deepEqual(sourceBuffer.updates_[0].append, new Uint8Array([0]), | ||
90 | 'ran the first update'); | ||
91 | |||
92 | sourceBuffer.trigger('updateend'); | ||
93 | QUnit.equal(sourceBuffer.updates_.length, 2, 'queued some updates'); | ||
94 | QUnit.deepEqual(sourceBuffer.updates_[1].append, new Uint8Array([1]), | ||
95 | 'ran the second update'); | ||
96 | |||
97 | updater.appendBuffer(new Uint8Array([3])); | ||
98 | sourceBuffer.trigger('updateend'); | ||
99 | QUnit.equal(sourceBuffer.updates_.length, 3, 'queued the updates'); | ||
100 | QUnit.deepEqual(sourceBuffer.updates_[2].append, new Uint8Array([2]), | ||
101 | 'ran the third update'); | ||
102 | |||
103 | sourceBuffer.trigger('updateend'); | ||
104 | QUnit.equal(sourceBuffer.updates_.length, 4, 'finished the updates'); | ||
105 | QUnit.deepEqual(sourceBuffer.updates_[3].append, new Uint8Array([3]), | ||
106 | 'ran the fourth update'); | ||
107 | }); | ||
108 | |||
109 | QUnit.test('runs updates immediately if possible', function() { | ||
110 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
111 | let sourceBuffer; | ||
112 | |||
113 | this.mediaSource.trigger('sourceopen'); | ||
114 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
115 | updater.appendBuffer(new Uint8Array([0])); | ||
116 | QUnit.equal(sourceBuffer.updates_.length, 1, 'ran an update'); | ||
117 | QUnit.deepEqual(sourceBuffer.updates_[0].append, new Uint8Array([0]), | ||
118 | 'appended the bytes'); | ||
119 | }); | ||
120 | |||
121 | QUnit.test('supports abort', function() { | ||
122 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
123 | let sourceBuffer; | ||
124 | |||
125 | updater.abort(); | ||
126 | this.mediaSource.trigger('sourceopen'); | ||
127 | |||
128 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
129 | QUnit.ok(sourceBuffer.updates_[0].abort, 'aborted the source buffer'); | ||
130 | }); | ||
131 | |||
132 | QUnit.test('supports buffered', function() { | ||
133 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
134 | |||
135 | QUnit.equal(updater.buffered().length, 0, 'buffered is empty'); | ||
136 | |||
137 | this.mediaSource.trigger('sourceopen'); | ||
138 | QUnit.ok(updater.buffered(), 'buffered is defined'); | ||
139 | }); | ||
140 | |||
141 | QUnit.test('supports removeBuffer', function() { | ||
142 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
143 | let sourceBuffer; | ||
144 | |||
145 | this.mediaSource.trigger('sourceopen'); | ||
146 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
147 | updater.remove(1, 14); | ||
148 | |||
149 | QUnit.equal(sourceBuffer.updates_.length, 1, 'ran an update'); | ||
150 | QUnit.deepEqual(sourceBuffer.updates_[0].remove, [1, 14], 'removed the time range'); | ||
151 | }); | ||
152 | |||
153 | QUnit.test('supports setting duration', function() { | ||
154 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
155 | let sourceBuffer; | ||
156 | |||
157 | this.mediaSource.trigger('sourceopen'); | ||
158 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
159 | updater.duration(21); | ||
160 | |||
161 | QUnit.equal(sourceBuffer.updates_.length, 1, 'ran an update'); | ||
162 | QUnit.deepEqual(sourceBuffer.updates_[0].duration, 21, 'changed duration'); | ||
163 | }); | ||
164 | |||
165 | QUnit.test('supports timestampOffset', function() { | ||
166 | let updater = new SourceUpdater(this.mediaSource, 'video/mp2t'); | ||
167 | let sourceBuffer; | ||
168 | |||
169 | this.mediaSource.trigger('sourceopen'); | ||
170 | sourceBuffer = this.mediaSource.sourceBuffers[0]; | ||
171 | |||
172 | QUnit.equal(updater.timestampOffset(), 0, 'intialized to zero'); | ||
173 | updater.timestampOffset(21); | ||
174 | QUnit.equal(updater.timestampOffset(), 21, 'reflects changes immediately'); | ||
175 | QUnit.equal(sourceBuffer.timestampOffset, 21, 'applied the update'); | ||
176 | |||
177 | updater.appendBuffer(new Uint8Array(2)); | ||
178 | updater.timestampOffset(14); | ||
179 | QUnit.equal(updater.timestampOffset(), 14, 'reflects changes immediately'); | ||
180 | QUnit.equal(sourceBuffer.timestampOffset, 21, 'queues application after updates'); | ||
181 | |||
182 | sourceBuffer.trigger('updateend'); | ||
183 | QUnit.equal(sourceBuffer.timestampOffset, 14, 'applied the update'); | ||
184 | }); |
test/test-helpers.js
0 → 100644
1 | import document from 'global/document'; | ||
2 | import sinon from 'sinon'; | ||
3 | import videojs from 'video.js'; | ||
4 | import QUnit from 'qunit'; | ||
5 | /* eslint-disable no-unused-vars */ | ||
6 | // needed so MediaSource can be registered with videojs | ||
7 | import MediaSource from 'videojs-contrib-media-sources'; | ||
8 | /* eslint-enable */ | ||
9 | import testDataManifests from './test-manifests.js'; | ||
10 | import xhrFactory from '../src/xhr'; | ||
11 | |||
12 | // a SourceBuffer that tracks updates but otherwise is a noop | ||
13 | class MockSourceBuffer extends videojs.EventTarget { | ||
14 | constructor() { | ||
15 | super(); | ||
16 | this.updates_ = []; | ||
17 | |||
18 | this.updating = false; | ||
19 | this.on('updateend', function() { | ||
20 | this.updating = false; | ||
21 | }); | ||
22 | |||
23 | this.buffered = videojs.createTimeRanges(); | ||
24 | this.duration_ = NaN; | ||
25 | |||
26 | Object.defineProperty(this, 'duration', { | ||
27 | get() { | ||
28 | return this.duration_; | ||
29 | }, | ||
30 | set(duration) { | ||
31 | this.updates_.push({ | ||
32 | duration | ||
33 | }); | ||
34 | this.duration_ = duration; | ||
35 | } | ||
36 | }); | ||
37 | } | ||
38 | |||
39 | abort() { | ||
40 | this.updates_.push({ | ||
41 | abort: true | ||
42 | }); | ||
43 | } | ||
44 | |||
45 | appendBuffer(bytes) { | ||
46 | this.updates_.push({ | ||
47 | append: bytes | ||
48 | }); | ||
49 | this.updating = true; | ||
50 | } | ||
51 | |||
52 | remove(start, end) { | ||
53 | this.updates_.push({ | ||
54 | remove: [start, end] | ||
55 | }); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | class MockMediaSource extends videojs.EventTarget { | ||
60 | constructor() { | ||
61 | super(); | ||
62 | this.readyState = 'closed'; | ||
63 | this.on('sourceopen', function() { | ||
64 | this.readyState = 'open'; | ||
65 | }); | ||
66 | |||
67 | this.sourceBuffers = []; | ||
68 | this.duration = NaN; | ||
69 | this.seekable = videojs.createTimeRange(); | ||
70 | } | ||
71 | |||
72 | addSeekableRange_(start, end) { | ||
73 | this.seekable = videojs.createTimeRange(start, end); | ||
74 | } | ||
75 | |||
76 | addSourceBuffer(mime) { | ||
77 | let sourceBuffer = new MockSourceBuffer(); | ||
78 | |||
79 | sourceBuffer.mimeType_ = mime; | ||
80 | this.sourceBuffers.push(sourceBuffer); | ||
81 | return sourceBuffer; | ||
82 | } | ||
83 | |||
84 | endOfStream(error) { | ||
85 | this.readyState = 'closed'; | ||
86 | this.error_ = error; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | export const useFakeMediaSource = function() { | ||
91 | let RealMediaSource = videojs.MediaSource; | ||
92 | let realCreateObjectURL = window.URL.createObjectURL; | ||
93 | let id = 0; | ||
94 | |||
95 | videojs.MediaSource = MockMediaSource; | ||
96 | videojs.MediaSource.supportsNativeMediaSources = | ||
97 | RealMediaSource.supportsNativeMediaSources; | ||
98 | videojs.URL.createObjectURL = function() { | ||
99 | id++; | ||
100 | return 'blob:videojs-contrib-hls-mock-url' + id; | ||
101 | }; | ||
102 | |||
103 | return { | ||
104 | restore() { | ||
105 | videojs.MediaSource = RealMediaSource; | ||
106 | videojs.URL.createObjectURL = realCreateObjectURL; | ||
107 | } | ||
108 | }; | ||
109 | }; | ||
110 | |||
111 | let fakeEnvironment = { | ||
112 | requests: [], | ||
113 | restore() { | ||
114 | this.clock.restore(); | ||
115 | videojs.xhr.XMLHttpRequest = window.XMLHttpRequest; | ||
116 | this.xhr.restore(); | ||
117 | ['warn', 'error'].forEach((level) => { | ||
118 | if (this.log && this.log[level] && this.log[level].restore) { | ||
119 | QUnit.equal(this.log[level].callCount, 0, `no unexpected logs on ${level}`); | ||
120 | this.log[level].restore(); | ||
121 | } | ||
122 | }); | ||
123 | } | ||
124 | }; | ||
125 | |||
126 | export const useFakeEnvironment = function() { | ||
127 | fakeEnvironment.log = {}; | ||
128 | ['warn', 'error'].forEach((level) => { | ||
129 | // you can use .log[level].args to get args | ||
130 | sinon.stub(videojs.log, level); | ||
131 | fakeEnvironment.log[level] = videojs.log[level]; | ||
132 | Object.defineProperty(videojs.log[level], 'calls', { | ||
133 | get() { | ||
134 | // reset callCount to 0 so they don't have to | ||
135 | let callCount = this.callCount; | ||
136 | |||
137 | this.callCount = 0; | ||
138 | return callCount; | ||
139 | } | ||
140 | }); | ||
141 | }); | ||
142 | fakeEnvironment.clock = sinon.useFakeTimers(); | ||
143 | fakeEnvironment.xhr = sinon.useFakeXMLHttpRequest(); | ||
144 | fakeEnvironment.requests.length = 0; | ||
145 | fakeEnvironment.xhr.onCreate = function(xhr) { | ||
146 | fakeEnvironment.requests.push(xhr); | ||
147 | }; | ||
148 | videojs.xhr.XMLHttpRequest = fakeEnvironment.xhr; | ||
149 | |||
150 | return fakeEnvironment; | ||
151 | }; | ||
152 | |||
153 | // patch over some methods of the provided tech so it can be tested | ||
154 | // synchronously with sinon's fake timers | ||
155 | export const mockTech = function(tech) { | ||
156 | if (tech.isMocked_) { | ||
157 | // make this function idempotent because HTML and Flash based | ||
158 | // playback have very different lifecycles. For HTML, the tech | ||
159 | // is available on player creation. For Flash, the tech isn't | ||
160 | // ready until the source has been loaded and one tick has | ||
161 | // expired. | ||
162 | return; | ||
163 | } | ||
164 | |||
165 | tech.isMocked_ = true; | ||
166 | tech.src_ = null; | ||
167 | tech.time_ = null; | ||
168 | |||
169 | tech.paused_ = !tech.autoplay(); | ||
170 | tech.paused = function() { | ||
171 | return tech.paused_; | ||
172 | }; | ||
173 | |||
174 | if (!tech.currentTime_) { | ||
175 | tech.currentTime_ = tech.currentTime; | ||
176 | } | ||
177 | tech.currentTime = function() { | ||
178 | return tech.time_ === null ? tech.currentTime_() : tech.time_; | ||
179 | }; | ||
180 | |||
181 | tech.setSrc = function(src) { | ||
182 | tech.src_ = src; | ||
183 | }; | ||
184 | tech.src = function(src) { | ||
185 | if (src !== null) { | ||
186 | return tech.setSrc(src); | ||
187 | } | ||
188 | return tech.src_ === null ? tech.src : tech.src_; | ||
189 | }; | ||
190 | tech.currentSrc_ = tech.currentSrc; | ||
191 | tech.currentSrc = function() { | ||
192 | return tech.src_ === null ? tech.currentSrc_() : tech.src_; | ||
193 | }; | ||
194 | |||
195 | tech.play_ = tech.play; | ||
196 | tech.play = function() { | ||
197 | tech.play_(); | ||
198 | tech.paused_ = false; | ||
199 | tech.trigger('play'); | ||
200 | }; | ||
201 | tech.pause_ = tech.pause_; | ||
202 | tech.pause = function() { | ||
203 | tech.pause_(); | ||
204 | tech.paused_ = true; | ||
205 | tech.trigger('pause'); | ||
206 | }; | ||
207 | |||
208 | tech.setCurrentTime = function(time) { | ||
209 | tech.time_ = time; | ||
210 | |||
211 | setTimeout(function() { | ||
212 | tech.trigger('seeking'); | ||
213 | setTimeout(function() { | ||
214 | tech.trigger('seeked'); | ||
215 | }, 1); | ||
216 | }, 1); | ||
217 | }; | ||
218 | }; | ||
219 | |||
220 | export const createPlayer = function(options) { | ||
221 | let video; | ||
222 | let player; | ||
223 | |||
224 | video = document.createElement('video'); | ||
225 | video.className = 'video-js'; | ||
226 | document.querySelector('#qunit-fixture').appendChild(video); | ||
227 | player = videojs(video, options || { | ||
228 | flash: { | ||
229 | swf: '' | ||
230 | } | ||
231 | }); | ||
232 | |||
233 | player.buffered = function() { | ||
234 | return videojs.createTimeRange(0, 0); | ||
235 | }; | ||
236 | mockTech(player.tech_); | ||
237 | |||
238 | return player; | ||
239 | }; | ||
240 | |||
241 | export const openMediaSource = function(player, clock) { | ||
242 | // ensure the Flash tech is ready | ||
243 | player.tech_.triggerReady(); | ||
244 | clock.tick(1); | ||
245 | // mock the tech *after* it has finished loading so that we don't | ||
246 | // mock a tech that will be unloaded on the next tick | ||
247 | mockTech(player.tech_); | ||
248 | player.tech_.hls.xhr = xhrFactory(); | ||
249 | |||
250 | // simulate the sourceopen event | ||
251 | player.tech_.hls.mediaSource.readyState = 'open'; | ||
252 | player.tech_.hls.mediaSource.dispatchEvent({ | ||
253 | type: 'sourceopen', | ||
254 | swfId: player.tech_.el().id | ||
255 | }); | ||
256 | }; | ||
257 | |||
258 | export const standardXHRResponse = function(request) { | ||
259 | if (!request.url) { | ||
260 | return; | ||
261 | } | ||
262 | |||
263 | let contentType = 'application/json'; | ||
264 | // contents off the global object | ||
265 | let manifestName = (/(?:.*\/)?(.*)\.m3u8/).exec(request.url); | ||
266 | |||
267 | if (manifestName) { | ||
268 | manifestName = manifestName[1]; | ||
269 | } else { | ||
270 | manifestName = request.url; | ||
271 | } | ||
272 | |||
273 | if (/\.m3u8?/.test(request.url)) { | ||
274 | contentType = 'application/vnd.apple.mpegurl'; | ||
275 | } else if (/\.ts/.test(request.url)) { | ||
276 | contentType = 'video/MP2T'; | ||
277 | } | ||
278 | |||
279 | request.response = new Uint8Array(16).buffer; | ||
280 | request.respond(200, { 'Content-Type': contentType }, | ||
281 | testDataManifests[manifestName]); | ||
282 | }; | ||
283 | |||
284 | // return an absolute version of a page-relative URL | ||
285 | export const absoluteUrl = function(relativeUrl) { | ||
286 | return window.location.protocol + '//' + | ||
287 | window.location.host + | ||
288 | (window.location.pathname | ||
289 | .split('/') | ||
290 | .slice(0, -1) | ||
291 | .concat(relativeUrl) | ||
292 | .join('/') | ||
293 | ); | ||
294 | }; |
This diff could not be displayed because it is too large.
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "http://example.com/00001.ts" | 9 | "uri": "http://example.com/00001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "https://example.com/00002.ts" | 14 | "uri": "https://example.com/00002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "//example.com/00003.ts" | 19 | "uri": "//example.com/00003.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 10, | 22 | "duration": 10, |
23 | "timeline": 0, | ||
20 | "uri": "http://example.com/00004.ts" | 24 | "uri": "http://example.com/00004.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -9,6 +9,7 @@ | ... | @@ -9,6 +9,7 @@ |
9 | "offset": 0 | 9 | "offset": 0 |
10 | }, | 10 | }, |
11 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
12 | "uri": "hls_450k_video.ts" | 13 | "uri": "hls_450k_video.ts" |
13 | }, | 14 | }, |
14 | { | 15 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "offset": 522828 | 18 | "offset": 522828 |
18 | }, | 19 | }, |
19 | "duration": 10, | 20 | "duration": 10, |
21 | "timeline": 0, | ||
20 | "uri": "hls_450k_video.ts" | 22 | "uri": "hls_450k_video.ts" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -25,6 +27,7 @@ | ... | @@ -25,6 +27,7 @@ |
25 | "offset": 1110328 | 27 | "offset": 1110328 |
26 | }, | 28 | }, |
27 | "duration": 10, | 29 | "duration": 10, |
30 | "timeline": 0, | ||
28 | "uri": "hls_450k_video.ts" | 31 | "uri": "hls_450k_video.ts" |
29 | }, | 32 | }, |
30 | { | 33 | { |
... | @@ -33,6 +36,7 @@ | ... | @@ -33,6 +36,7 @@ |
33 | "offset": 1823412 | 36 | "offset": 1823412 |
34 | }, | 37 | }, |
35 | "duration": 10, | 38 | "duration": 10, |
39 | "timeline": 0, | ||
36 | "uri": "hls_450k_video.ts" | 40 | "uri": "hls_450k_video.ts" |
37 | }, | 41 | }, |
38 | { | 42 | { |
... | @@ -41,6 +45,7 @@ | ... | @@ -41,6 +45,7 @@ |
41 | "offset": 2299992 | 45 | "offset": 2299992 |
42 | }, | 46 | }, |
43 | "duration": 10, | 47 | "duration": 10, |
48 | "timeline": 0, | ||
44 | "uri": "hls_450k_video.ts" | 49 | "uri": "hls_450k_video.ts" |
45 | }, | 50 | }, |
46 | { | 51 | { |
... | @@ -49,6 +54,7 @@ | ... | @@ -49,6 +54,7 @@ |
49 | "offset": 2835604 | 54 | "offset": 2835604 |
50 | }, | 55 | }, |
51 | "duration": 10, | 56 | "duration": 10, |
57 | "timeline": 0, | ||
52 | "uri": "hls_450k_video.ts" | 58 | "uri": "hls_450k_video.ts" |
53 | }, | 59 | }, |
54 | { | 60 | { |
... | @@ -57,6 +63,7 @@ | ... | @@ -57,6 +63,7 @@ |
57 | "offset": 3042780 | 63 | "offset": 3042780 |
58 | }, | 64 | }, |
59 | "duration": 10, | 65 | "duration": 10, |
66 | "timeline": 0, | ||
60 | "uri": "hls_450k_video.ts" | 67 | "uri": "hls_450k_video.ts" |
61 | }, | 68 | }, |
62 | { | 69 | { |
... | @@ -65,6 +72,7 @@ | ... | @@ -65,6 +72,7 @@ |
65 | "offset": 3498680 | 72 | "offset": 3498680 |
66 | }, | 73 | }, |
67 | "duration": 10, | 74 | "duration": 10, |
75 | "timeline": 0, | ||
68 | "uri": "hls_450k_video.ts" | 76 | "uri": "hls_450k_video.ts" |
69 | }, | 77 | }, |
70 | { | 78 | { |
... | @@ -73,6 +81,7 @@ | ... | @@ -73,6 +81,7 @@ |
73 | "offset": 4155928 | 81 | "offset": 4155928 |
74 | }, | 82 | }, |
75 | "duration": 10, | 83 | "duration": 10, |
84 | "timeline": 0, | ||
76 | "uri": "hls_450k_video.ts" | 85 | "uri": "hls_450k_video.ts" |
77 | }, | 86 | }, |
78 | { | 87 | { |
... | @@ -81,6 +90,7 @@ | ... | @@ -81,6 +90,7 @@ |
81 | "offset": 4727636 | 90 | "offset": 4727636 |
82 | }, | 91 | }, |
83 | "duration": 10, | 92 | "duration": 10, |
93 | "timeline": 0, | ||
84 | "uri": "hls_450k_video.ts" | 94 | "uri": "hls_450k_video.ts" |
85 | }, | 95 | }, |
86 | { | 96 | { |
... | @@ -89,6 +99,7 @@ | ... | @@ -89,6 +99,7 @@ |
89 | "offset": 5212676 | 99 | "offset": 5212676 |
90 | }, | 100 | }, |
91 | "duration": 10, | 101 | "duration": 10, |
102 | "timeline": 0, | ||
92 | "uri": "hls_450k_video.ts" | 103 | "uri": "hls_450k_video.ts" |
93 | }, | 104 | }, |
94 | { | 105 | { |
... | @@ -97,6 +108,7 @@ | ... | @@ -97,6 +108,7 @@ |
97 | "offset": 5921812 | 108 | "offset": 5921812 |
98 | }, | 109 | }, |
99 | "duration": 10, | 110 | "duration": 10, |
111 | "timeline": 0, | ||
100 | "uri": "hls_450k_video.ts" | 112 | "uri": "hls_450k_video.ts" |
101 | }, | 113 | }, |
102 | { | 114 | { |
... | @@ -105,6 +117,7 @@ | ... | @@ -105,6 +117,7 @@ |
105 | "offset": 6651816 | 117 | "offset": 6651816 |
106 | }, | 118 | }, |
107 | "duration": 10, | 119 | "duration": 10, |
120 | "timeline": 0, | ||
108 | "uri": "hls_450k_video.ts" | 121 | "uri": "hls_450k_video.ts" |
109 | }, | 122 | }, |
110 | { | 123 | { |
... | @@ -113,6 +126,7 @@ | ... | @@ -113,6 +126,7 @@ |
113 | "offset": 7108092 | 126 | "offset": 7108092 |
114 | }, | 127 | }, |
115 | "duration": 10, | 128 | "duration": 10, |
129 | "timeline": 0, | ||
116 | "uri": "hls_450k_video.ts" | 130 | "uri": "hls_450k_video.ts" |
117 | }, | 131 | }, |
118 | { | 132 | { |
... | @@ -121,6 +135,7 @@ | ... | @@ -121,6 +135,7 @@ |
121 | "offset": 7576776 | 135 | "offset": 7576776 |
122 | }, | 136 | }, |
123 | "duration": 10, | 137 | "duration": 10, |
138 | "timeline": 0, | ||
124 | "uri": "hls_450k_video.ts" | 139 | "uri": "hls_450k_video.ts" |
125 | }, | 140 | }, |
126 | { | 141 | { |
... | @@ -129,6 +144,7 @@ | ... | @@ -129,6 +144,7 @@ |
129 | "offset": 8021772 | 144 | "offset": 8021772 |
130 | }, | 145 | }, |
131 | "duration": 10, | 146 | "duration": 10, |
147 | "timeline": 0, | ||
132 | "uri": "hls_450k_video.ts" | 148 | "uri": "hls_450k_video.ts" |
133 | }, | 149 | }, |
134 | { | 150 | { |
... | @@ -137,6 +153,7 @@ | ... | @@ -137,6 +153,7 @@ |
137 | "offset": 8353216 | 153 | "offset": 8353216 |
138 | }, | 154 | }, |
139 | "duration": 1.4167, | 155 | "duration": 1.4167, |
156 | "timeline": 0, | ||
140 | "uri": "hls_450k_video.ts" | 157 | "uri": "hls_450k_video.ts" |
141 | } | 158 | } |
142 | ], | 159 | ], | ... | ... |
utils/manifest/alternateAudio.js
0 → 100644
1 | { | ||
2 | allowCache: true, | ||
3 | discontinuityStarts: [], | ||
4 | mediaGroups: { | ||
5 | // TYPE | ||
6 | AUDIO: { | ||
7 | // GROUP-ID | ||
8 | "audio": { | ||
9 | // NAME | ||
10 | "English": { | ||
11 | language: 'eng', | ||
12 | autoselect: true, | ||
13 | default: true, | ||
14 | uri: "eng/prog_index.m3u8" | ||
15 | }, | ||
16 | // NAME | ||
17 | "Français": { | ||
18 | language: "fre", | ||
19 | autoselect: true, | ||
20 | default: false, | ||
21 | uri: "fre/prog_index.m3u8" | ||
22 | }, | ||
23 | // NAME | ||
24 | "Espanol": { | ||
25 | language: "sp", | ||
26 | autoselect: true, | ||
27 | default: false, | ||
28 | uri: "sp/prog_index.m3u8" | ||
29 | } | ||
30 | } | ||
31 | }, | ||
32 | VIDEO: {}, | ||
33 | "CLOSED-CAPTIONS": {}, | ||
34 | SUBTITLES: {} | ||
35 | }, | ||
36 | playlists: [{ | ||
37 | attributes: { | ||
38 | "PROGRAM-ID": 1, | ||
39 | BANDWIDTH: 195023, | ||
40 | CODECS: "avc1.42e00a,mp4a.40.2", | ||
41 | AUDIO: 'audio' | ||
42 | }, | ||
43 | timeline: 0, | ||
44 | uri: "lo/prog_index.m3u8" | ||
45 | }, { | ||
46 | attributes: { | ||
47 | "PROGRAM-ID": 1, | ||
48 | BANDWIDTH: 591680, | ||
49 | CODECS: "avc1.42e01e,mp4a.40.2", | ||
50 | AUDIO: 'audio' | ||
51 | }, | ||
52 | timeline: 0, | ||
53 | uri: "hi/prog_index.m3u8" | ||
54 | }] | ||
55 | } |
utils/manifest/alternateAudio.m3u8
0 → 100644
1 | #EXTM3U | ||
2 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="eng",NAME="English",AUTOSELECT=YES, DEFAULT=YES,URI="eng/prog_index.m3u8" | ||
3 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="fre/prog_index.m3u8" | ||
4 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="sp/prog_index.m3u8" | ||
5 | |||
6 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="avc1.42e00a,mp4a.40.2",AUDIO="audio" | ||
7 | lo/prog_index.m3u8 | ||
8 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=591680,CODECS="avc1.42e01e,mp4a.40.2",AUDIO="audio" | ||
9 | hi/prog_index.m3u8 | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
utils/manifest/alternateVideo.js
0 → 100644
1 | { | ||
2 | allowCache: true, | ||
3 | discontinuityStarts: [], | ||
4 | mediaGroups: { | ||
5 | AUDIO: { | ||
6 | aac: { | ||
7 | English: { | ||
8 | autoselect: true, | ||
9 | default: true, | ||
10 | language: "eng", | ||
11 | uri: "eng/prog_index.m3u8" | ||
12 | } | ||
13 | } | ||
14 | }, | ||
15 | VIDEO: { | ||
16 | "500kbs": { | ||
17 | Angle1: { | ||
18 | autoselect: true, | ||
19 | default: true | ||
20 | }, | ||
21 | Angle2: { | ||
22 | autoselect: true, | ||
23 | default: false, | ||
24 | uri: "Angle2/500kbs/prog_index.m3u8" | ||
25 | }, | ||
26 | Angle3: { | ||
27 | autoselect: true, | ||
28 | default: false, | ||
29 | uri: "Angle3/500kbs/prog_index.m3u8" | ||
30 | } | ||
31 | } | ||
32 | }, | ||
33 | "CLOSED-CAPTIONS": {}, | ||
34 | SUBTITLES: {} | ||
35 | }, | ||
36 | playlists: [{ | ||
37 | attributes: { | ||
38 | "PROGRAM-ID": 1, | ||
39 | BANDWIDTH: 754857, | ||
40 | CODECS: "mp4a.40.2,avc1.4d401e", | ||
41 | AUDIO: "aac", | ||
42 | VIDEO: "500kbs" | ||
43 | }, | ||
44 | timeline: 0, | ||
45 | uri: "Angle1/500kbs/prog_index.m3u8" | ||
46 | }] | ||
47 | } |
utils/manifest/alternateVideo.m3u8
0 → 100644
1 | #EXTM3U | ||
2 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle1",AUTOSELECT=YES,DEFAULT=YES | ||
3 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle2",AUTOSELECT=YES,DEFAULT=NO,URI="Angle2/500kbs/prog_index.m3u8" | ||
4 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle3",AUTOSELECT=YES,DEFAULT=NO,URI="Angle3/500kbs/prog_index.m3u8" | ||
5 | |||
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="eng",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8" | ||
7 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=754857,CODECS="mp4a.40.2,avc1.4d401e",VIDEO="500kbs",AUDIO="aac" | ||
8 | Angle1/500kbs/prog_index.m3u8 | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -10,6 +10,7 @@ | ... | @@ -10,6 +10,7 @@ |
10 | "height": 224 | 10 | "height": 224 |
11 | } | 11 | } |
12 | }, | 12 | }, |
13 | "timeline": 0, | ||
13 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" | 14 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" |
14 | }, | 15 | }, |
15 | { | 16 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "PROGRAM-ID": 1, | 18 | "PROGRAM-ID": 1, |
18 | "BANDWIDTH": 40000 | 19 | "BANDWIDTH": 40000 |
19 | }, | 20 | }, |
21 | "timeline": 0, | ||
20 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" | 22 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -28,6 +30,7 @@ | ... | @@ -28,6 +30,7 @@ |
28 | "height": 224 | 30 | "height": 224 |
29 | } | 31 | } |
30 | }, | 32 | }, |
33 | "timeline": 0, | ||
31 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" | 34 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" |
32 | }, | 35 | }, |
33 | { | 36 | { |
... | @@ -39,8 +42,15 @@ | ... | @@ -39,8 +42,15 @@ |
39 | "height": 540 | 42 | "height": 540 |
40 | } | 43 | } |
41 | }, | 44 | }, |
45 | "timeline": 0, | ||
42 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" | 46 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" |
43 | } | 47 | } |
44 | ], | 48 | ], |
45 | "discontinuityStarts": [] | 49 | "discontinuityStarts": [], |
50 | "mediaGroups": { | ||
51 | "VIDEO": {}, | ||
52 | "AUDIO": {}, | ||
53 | "CLOSED-CAPTIONS": {}, | ||
54 | "SUBTITLES": {} | ||
55 | } | ||
46 | } | 56 | } | ... | ... |
... | @@ -5,6 +5,7 @@ | ... | @@ -5,6 +5,7 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "hls_450k_video.ts" | 9 | "uri": "hls_450k_video.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
... | @@ -13,6 +14,7 @@ | ... | @@ -13,6 +14,7 @@ |
13 | "offset": 522828 | 14 | "offset": 522828 |
14 | }, | 15 | }, |
15 | "duration": 10, | 16 | "duration": 10, |
17 | "timeline": 0, | ||
16 | "uri": "hls_450k_video.ts" | 18 | "uri": "hls_450k_video.ts" |
17 | }, | 19 | }, |
18 | { | 20 | { |
... | @@ -21,6 +23,7 @@ | ... | @@ -21,6 +23,7 @@ |
21 | "offset": 0 | 23 | "offset": 0 |
22 | }, | 24 | }, |
23 | "duration": 10, | 25 | "duration": 10, |
26 | "timeline": 0, | ||
24 | "uri": "hls_450k_video2.ts" | 27 | "uri": "hls_450k_video2.ts" |
25 | }, | 28 | }, |
26 | { | 29 | { |
... | @@ -29,6 +32,7 @@ | ... | @@ -29,6 +32,7 @@ |
29 | "offset": 1823412 | 32 | "offset": 1823412 |
30 | }, | 33 | }, |
31 | "duration": 10, | 34 | "duration": 10, |
35 | "timeline": 0, | ||
32 | "uri": "hls_450k_video.ts" | 36 | "uri": "hls_450k_video.ts" |
33 | }, | 37 | }, |
34 | { | 38 | { |
... | @@ -37,6 +41,7 @@ | ... | @@ -37,6 +41,7 @@ |
37 | "offset": 2299992 | 41 | "offset": 2299992 |
38 | }, | 42 | }, |
39 | "duration": 10, | 43 | "duration": 10, |
44 | "timeline": 0, | ||
40 | "uri": "hls_450k_video.ts" | 45 | "uri": "hls_450k_video.ts" |
41 | }, | 46 | }, |
42 | { | 47 | { |
... | @@ -45,6 +50,7 @@ | ... | @@ -45,6 +50,7 @@ |
45 | "offset": 2835604 | 50 | "offset": 2835604 |
46 | }, | 51 | }, |
47 | "duration": 10, | 52 | "duration": 10, |
53 | "timeline": 0, | ||
48 | "uri": "hls_450k_video.ts" | 54 | "uri": "hls_450k_video.ts" |
49 | }, | 55 | }, |
50 | { | 56 | { |
... | @@ -53,6 +59,7 @@ | ... | @@ -53,6 +59,7 @@ |
53 | "offset": 3042780 | 59 | "offset": 3042780 |
54 | }, | 60 | }, |
55 | "duration": 10, | 61 | "duration": 10, |
62 | "timeline": 0, | ||
56 | "uri": "hls_450k_video.ts" | 63 | "uri": "hls_450k_video.ts" |
57 | }, | 64 | }, |
58 | { | 65 | { |
... | @@ -61,6 +68,7 @@ | ... | @@ -61,6 +68,7 @@ |
61 | "offset": 3498680 | 68 | "offset": 3498680 |
62 | }, | 69 | }, |
63 | "duration": 10, | 70 | "duration": 10, |
71 | "timeline": 0, | ||
64 | "uri": "hls_450k_video.ts" | 72 | "uri": "hls_450k_video.ts" |
65 | }, | 73 | }, |
66 | { | 74 | { |
... | @@ -69,6 +77,7 @@ | ... | @@ -69,6 +77,7 @@ |
69 | "offset": 4155928 | 77 | "offset": 4155928 |
70 | }, | 78 | }, |
71 | "duration": 10, | 79 | "duration": 10, |
80 | "timeline": 0, | ||
72 | "uri": "hls_450k_video.ts" | 81 | "uri": "hls_450k_video.ts" |
73 | }, | 82 | }, |
74 | { | 83 | { |
... | @@ -77,6 +86,7 @@ | ... | @@ -77,6 +86,7 @@ |
77 | "offset": 4727636 | 86 | "offset": 4727636 |
78 | }, | 87 | }, |
79 | "duration": 10, | 88 | "duration": 10, |
89 | "timeline": 0, | ||
80 | "uri": "hls_450k_video.ts" | 90 | "uri": "hls_450k_video.ts" |
81 | }, | 91 | }, |
82 | { | 92 | { |
... | @@ -85,6 +95,7 @@ | ... | @@ -85,6 +95,7 @@ |
85 | "offset": 5212676 | 95 | "offset": 5212676 |
86 | }, | 96 | }, |
87 | "duration": 10, | 97 | "duration": 10, |
98 | "timeline": 0, | ||
88 | "uri": "hls_450k_video.ts" | 99 | "uri": "hls_450k_video.ts" |
89 | }, | 100 | }, |
90 | { | 101 | { |
... | @@ -93,6 +104,7 @@ | ... | @@ -93,6 +104,7 @@ |
93 | "offset": 5921812 | 104 | "offset": 5921812 |
94 | }, | 105 | }, |
95 | "duration": 10, | 106 | "duration": 10, |
107 | "timeline": 0, | ||
96 | "uri": "hls_450k_video.ts" | 108 | "uri": "hls_450k_video.ts" |
97 | }, | 109 | }, |
98 | { | 110 | { |
... | @@ -101,6 +113,7 @@ | ... | @@ -101,6 +113,7 @@ |
101 | "offset": 6651816 | 113 | "offset": 6651816 |
102 | }, | 114 | }, |
103 | "duration": 10, | 115 | "duration": 10, |
116 | "timeline": 0, | ||
104 | "uri": "hls_450k_video.ts" | 117 | "uri": "hls_450k_video.ts" |
105 | }, | 118 | }, |
106 | { | 119 | { |
... | @@ -109,6 +122,7 @@ | ... | @@ -109,6 +122,7 @@ |
109 | "offset": 7108092 | 122 | "offset": 7108092 |
110 | }, | 123 | }, |
111 | "duration": 10, | 124 | "duration": 10, |
125 | "timeline": 0, | ||
112 | "uri": "hls_450k_video.ts" | 126 | "uri": "hls_450k_video.ts" |
113 | }, | 127 | }, |
114 | { | 128 | { |
... | @@ -117,6 +131,7 @@ | ... | @@ -117,6 +131,7 @@ |
117 | "offset": 7576776 | 131 | "offset": 7576776 |
118 | }, | 132 | }, |
119 | "duration": 10, | 133 | "duration": 10, |
134 | "timeline": 0, | ||
120 | "uri": "hls_450k_video.ts" | 135 | "uri": "hls_450k_video.ts" |
121 | }, | 136 | }, |
122 | { | 137 | { |
... | @@ -125,6 +140,7 @@ | ... | @@ -125,6 +140,7 @@ |
125 | "offset": 8021772 | 140 | "offset": 8021772 |
126 | }, | 141 | }, |
127 | "duration": 10, | 142 | "duration": 10, |
143 | "timeline": 0, | ||
128 | "uri": "hls_450k_video.ts" | 144 | "uri": "hls_450k_video.ts" |
129 | }, | 145 | }, |
130 | { | 146 | { |
... | @@ -133,6 +149,7 @@ | ... | @@ -133,6 +149,7 @@ |
133 | "offset": 8353216 | 149 | "offset": 8353216 |
134 | }, | 150 | }, |
135 | "duration": 1.4167, | 151 | "duration": 1.4167, |
152 | "timeline": 0, | ||
136 | "uri": "hls_450k_video.ts" | 153 | "uri": "hls_450k_video.ts" |
137 | } | 154 | } |
138 | ], | 155 | ], | ... | ... |
... | @@ -5,19 +5,23 @@ | ... | @@ -5,19 +5,23 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 3, | ||
8 | "uri": "001.ts" | 9 | "uri": "001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 19, | 12 | "duration": 19, |
13 | "timeline": 3, | ||
12 | "uri": "002.ts" | 14 | "uri": "002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "discontinuity": true, | 17 | "discontinuity": true, |
16 | "duration": 10, | 18 | "duration": 10, |
19 | "timeline": 4, | ||
17 | "uri": "003.ts" | 20 | "uri": "003.ts" |
18 | }, | 21 | }, |
19 | { | 22 | { |
20 | "duration": 11, | 23 | "duration": 11, |
24 | "timeline": 4, | ||
21 | "uri": "004.ts" | 25 | "uri": "004.ts" |
22 | } | 26 | } |
23 | ], | 27 | ], | ... | ... |
... | @@ -5,41 +5,50 @@ | ... | @@ -5,41 +5,50 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "001.ts" | 9 | "uri": "001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 19, | 12 | "duration": 19, |
13 | "timeline": 0, | ||
12 | "uri": "002.ts" | 14 | "uri": "002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "discontinuity": true, | 17 | "discontinuity": true, |
16 | "duration": 10, | 18 | "duration": 10, |
19 | "timeline": 1, | ||
17 | "uri": "003.ts" | 20 | "uri": "003.ts" |
18 | }, | 21 | }, |
19 | { | 22 | { |
20 | "duration": 11, | 23 | "duration": 11, |
24 | "timeline": 1, | ||
21 | "uri": "004.ts" | 25 | "uri": "004.ts" |
22 | }, | 26 | }, |
23 | { | 27 | { |
24 | "discontinuity": true, | 28 | "discontinuity": true, |
25 | "duration": 10, | 29 | "duration": 10, |
30 | "timeline": 2, | ||
26 | "uri": "005.ts" | 31 | "uri": "005.ts" |
27 | }, | 32 | }, |
28 | { | 33 | { |
29 | "duration": 10, | 34 | "duration": 10, |
35 | "timeline": 2, | ||
30 | "uri": "006.ts" | 36 | "uri": "006.ts" |
31 | }, | 37 | }, |
32 | { | 38 | { |
33 | "duration": 10, | 39 | "duration": 10, |
40 | "timeline": 2, | ||
34 | "uri": "007.ts" | 41 | "uri": "007.ts" |
35 | }, | 42 | }, |
36 | { | 43 | { |
37 | "discontinuity": true, | 44 | "discontinuity": true, |
38 | "duration": 10, | 45 | "duration": 10, |
46 | "timeline": 3, | ||
39 | "uri": "008.ts" | 47 | "uri": "008.ts" |
40 | }, | 48 | }, |
41 | { | 49 | { |
42 | "duration": 16, | 50 | "duration": 16, |
51 | "timeline": 3, | ||
43 | "uri": "009.ts" | 52 | "uri": "009.ts" |
44 | } | 53 | } |
45 | ], | 54 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "/00001.ts" | 9 | "uri": "/00001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "/subdir/00002.ts" | 14 | "uri": "/subdir/00002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "/00003.ts" | 19 | "uri": "/00003.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 10, | 22 | "duration": 10, |
23 | "timeline": 0, | ||
20 | "uri": "/00004.ts" | 24 | "uri": "/00004.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -4,26 +4,32 @@ | ... | @@ -4,26 +4,32 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 10, | 6 | "duration": 10, |
7 | "timeline": 0, | ||
7 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" | 8 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" |
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
11 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" | 13 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" |
12 | }, | 14 | }, |
13 | { | 15 | { |
14 | "duration": 10, | 16 | "duration": 10, |
17 | "timeline": 0, | ||
15 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" | 18 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" |
16 | }, | 19 | }, |
17 | { | 20 | { |
18 | "duration": 10, | 21 | "duration": 10, |
22 | "timeline": 0, | ||
19 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" | 23 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" |
20 | }, | 24 | }, |
21 | { | 25 | { |
22 | "duration": 10, | 26 | "duration": 10, |
27 | "timeline": 0, | ||
23 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" | 28 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" |
24 | }, | 29 | }, |
25 | { | 30 | { |
26 | "duration": 8, | 31 | "duration": 8, |
32 | "timeline": 0, | ||
27 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" | 33 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" |
28 | } | 34 | } |
29 | ], | 35 | ], | ... | ... |
... | @@ -10,6 +10,7 @@ | ... | @@ -10,6 +10,7 @@ |
10 | "height": 224 | 10 | "height": 224 |
11 | } | 11 | } |
12 | }, | 12 | }, |
13 | "timeline": 0, | ||
13 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" | 14 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" |
14 | }, | 15 | }, |
15 | { | 16 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "PROGRAM-ID": 1, | 18 | "PROGRAM-ID": 1, |
18 | "BANDWIDTH": 40000 | 19 | "BANDWIDTH": 40000 |
19 | }, | 20 | }, |
21 | "timeline": 0, | ||
20 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" | 22 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -28,6 +30,7 @@ | ... | @@ -28,6 +30,7 @@ |
28 | "height": 224 | 30 | "height": 224 |
29 | } | 31 | } |
30 | }, | 32 | }, |
33 | "timeline": 0, | ||
31 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" | 34 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" |
32 | }, | 35 | }, |
33 | { | 36 | { |
... | @@ -39,8 +42,15 @@ | ... | @@ -39,8 +42,15 @@ |
39 | "height": 540 | 42 | "height": 540 |
40 | } | 43 | } |
41 | }, | 44 | }, |
45 | "timeline": 0, | ||
42 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" | 46 | "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" |
43 | } | 47 | } |
44 | ], | 48 | ], |
45 | "discontinuityStarts": [] | 49 | "discontinuityStarts": [], |
50 | "mediaGroups": { | ||
51 | "VIDEO": {}, | ||
52 | "AUDIO": {}, | ||
53 | "CLOSED-CAPTIONS": {}, | ||
54 | "SUBTITLES": {} | ||
55 | } | ||
46 | } | 56 | } | ... | ... |
... | @@ -6,6 +6,7 @@ | ... | @@ -6,6 +6,7 @@ |
6 | "segments": [ | 6 | "segments": [ |
7 | { | 7 | { |
8 | "duration": 2.833, | 8 | "duration": 2.833, |
9 | "timeline": 0, | ||
9 | "key": { | 10 | "key": { |
10 | "method": "AES-128", | 11 | "method": "AES-128", |
11 | "uri": "https://priv.example.com/key.php?r=52" | 12 | "uri": "https://priv.example.com/key.php?r=52" |
... | @@ -14,6 +15,7 @@ | ... | @@ -14,6 +15,7 @@ |
14 | }, | 15 | }, |
15 | { | 16 | { |
16 | "duration": 15, | 17 | "duration": 15, |
18 | "timeline": 0, | ||
17 | "key": { | 19 | "key": { |
18 | "method": "AES-128", | 20 | "method": "AES-128", |
19 | "uri": "https://priv.example.com/key.php?r=52" | 21 | "uri": "https://priv.example.com/key.php?r=52" |
... | @@ -22,6 +24,7 @@ | ... | @@ -22,6 +24,7 @@ |
22 | }, | 24 | }, |
23 | { | 25 | { |
24 | "duration": 13.333, | 26 | "duration": 13.333, |
27 | "timeline": 0, | ||
25 | "key": { | 28 | "key": { |
26 | "method": "AES-128", | 29 | "method": "AES-128", |
27 | "uri": "https://priv.example.com/key.php?r=52" | 30 | "uri": "https://priv.example.com/key.php?r=52" |
... | @@ -30,6 +33,7 @@ | ... | @@ -30,6 +33,7 @@ |
30 | }, | 33 | }, |
31 | { | 34 | { |
32 | "duration": 15, | 35 | "duration": 15, |
36 | "timeline": 0, | ||
33 | "key": { | 37 | "key": { |
34 | "method": "AES-128", | 38 | "method": "AES-128", |
35 | "uri": "https://priv.example.com/key.php?r=53" | 39 | "uri": "https://priv.example.com/key.php?r=53" |
... | @@ -38,6 +42,7 @@ | ... | @@ -38,6 +42,7 @@ |
38 | }, | 42 | }, |
39 | { | 43 | { |
40 | "duration": 14, | 44 | "duration": 14, |
45 | "timeline": 0, | ||
41 | "key": { | 46 | "key": { |
42 | "method": "AES-128", | 47 | "method": "AES-128", |
43 | "uri": "https://priv.example.com/key.php?r=54", | 48 | "uri": "https://priv.example.com/key.php?r=54", |
... | @@ -47,6 +52,7 @@ | ... | @@ -47,6 +52,7 @@ |
47 | }, | 52 | }, |
48 | { | 53 | { |
49 | "duration": 15, | 54 | "duration": 15, |
55 | "timeline": 0, | ||
50 | "uri": "http://media.example.com/fileSequence53-B.ts" | 56 | "uri": "http://media.example.com/fileSequence53-B.ts" |
51 | } | 57 | } |
52 | ], | 58 | ], | ... | ... |
... | @@ -5,26 +5,32 @@ | ... | @@ -5,26 +5,32 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" | 9 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" | 14 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" | 19 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 10, | 22 | "duration": 10, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" | 24 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" |
21 | }, | 25 | }, |
22 | { | 26 | { |
23 | "duration": 10, | 27 | "duration": 10, |
28 | "timeline": 0, | ||
24 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" | 29 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" |
25 | }, | 30 | }, |
26 | { | 31 | { |
27 | "duration": 8, | 32 | "duration": 8, |
33 | "timeline": 0, | ||
28 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" | 34 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" |
29 | } | 35 | } |
30 | ], | 36 | ], | ... | ... |
... | @@ -4,6 +4,7 @@ | ... | @@ -4,6 +4,7 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 6.64, | 6 | "duration": 6.64, |
7 | "timeline": 0, | ||
7 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
8 | } | 9 | } |
9 | ], | 10 | ], | ... | ... |
... | @@ -9,6 +9,7 @@ | ... | @@ -9,6 +9,7 @@ |
9 | "offset": 0 | 9 | "offset": 0 |
10 | }, | 10 | }, |
11 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
12 | "uri": "hls_450k_video.ts" | 13 | "uri": "hls_450k_video.ts" |
13 | }, | 14 | }, |
14 | { | 15 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "offset": 522828 | 18 | "offset": 522828 |
18 | }, | 19 | }, |
19 | "duration": 10, | 20 | "duration": 10, |
21 | "timeline": 0, | ||
20 | "uri": "hls_450k_video.ts" | 22 | "uri": "hls_450k_video.ts" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -25,6 +27,7 @@ | ... | @@ -25,6 +27,7 @@ |
25 | "offset": 1110328 | 27 | "offset": 1110328 |
26 | }, | 28 | }, |
27 | "duration": 5, | 29 | "duration": 5, |
30 | "timeline": 0, | ||
28 | "uri": "hls_450k_video.ts" | 31 | "uri": "hls_450k_video.ts" |
29 | }, | 32 | }, |
30 | { | 33 | { |
... | @@ -33,6 +36,7 @@ | ... | @@ -33,6 +36,7 @@ |
33 | "offset": 1823412 | 36 | "offset": 1823412 |
34 | }, | 37 | }, |
35 | "duration": 9.7, | 38 | "duration": 9.7, |
39 | "timeline": 0, | ||
36 | "uri": "hls_450k_video.ts" | 40 | "uri": "hls_450k_video.ts" |
37 | }, | 41 | }, |
38 | { | 42 | { |
... | @@ -41,6 +45,7 @@ | ... | @@ -41,6 +45,7 @@ |
41 | "offset": 2299992 | 45 | "offset": 2299992 |
42 | }, | 46 | }, |
43 | "duration": 10, | 47 | "duration": 10, |
48 | "timeline": 0, | ||
44 | "uri": "hls_450k_video.ts" | 49 | "uri": "hls_450k_video.ts" |
45 | }, | 50 | }, |
46 | { | 51 | { |
... | @@ -49,6 +54,7 @@ | ... | @@ -49,6 +54,7 @@ |
49 | "offset": 2835604 | 54 | "offset": 2835604 |
50 | }, | 55 | }, |
51 | "duration": 10, | 56 | "duration": 10, |
57 | "timeline": 0, | ||
52 | "uri": "hls_450k_video.ts" | 58 | "uri": "hls_450k_video.ts" |
53 | }, | 59 | }, |
54 | { | 60 | { |
... | @@ -57,6 +63,7 @@ | ... | @@ -57,6 +63,7 @@ |
57 | "offset": 3042780 | 63 | "offset": 3042780 |
58 | }, | 64 | }, |
59 | "duration": 10, | 65 | "duration": 10, |
66 | "timeline": 0, | ||
60 | "uri": "hls_450k_video.ts" | 67 | "uri": "hls_450k_video.ts" |
61 | }, | 68 | }, |
62 | { | 69 | { |
... | @@ -65,6 +72,7 @@ | ... | @@ -65,6 +72,7 @@ |
65 | "offset": 3498680 | 72 | "offset": 3498680 |
66 | }, | 73 | }, |
67 | "duration": 10, | 74 | "duration": 10, |
75 | "timeline": 0, | ||
68 | "uri": "hls_450k_video.ts" | 76 | "uri": "hls_450k_video.ts" |
69 | }, | 77 | }, |
70 | { | 78 | { |
... | @@ -73,6 +81,7 @@ | ... | @@ -73,6 +81,7 @@ |
73 | "offset": 4155928 | 81 | "offset": 4155928 |
74 | }, | 82 | }, |
75 | "duration": 10, | 83 | "duration": 10, |
84 | "timeline": 0, | ||
76 | "uri": "hls_450k_video.ts" | 85 | "uri": "hls_450k_video.ts" |
77 | }, | 86 | }, |
78 | { | 87 | { |
... | @@ -81,6 +90,7 @@ | ... | @@ -81,6 +90,7 @@ |
81 | "offset": 4727636 | 90 | "offset": 4727636 |
82 | }, | 91 | }, |
83 | "duration": 10, | 92 | "duration": 10, |
93 | "timeline": 0, | ||
84 | "uri": "hls_450k_video.ts" | 94 | "uri": "hls_450k_video.ts" |
85 | }, | 95 | }, |
86 | { | 96 | { |
... | @@ -89,6 +99,7 @@ | ... | @@ -89,6 +99,7 @@ |
89 | "offset": 5212676 | 99 | "offset": 5212676 |
90 | }, | 100 | }, |
91 | "duration": 10, | 101 | "duration": 10, |
102 | "timeline": 0, | ||
92 | "uri": "hls_450k_video.ts" | 103 | "uri": "hls_450k_video.ts" |
93 | }, | 104 | }, |
94 | { | 105 | { |
... | @@ -97,6 +108,7 @@ | ... | @@ -97,6 +108,7 @@ |
97 | "offset": 5921812 | 108 | "offset": 5921812 |
98 | }, | 109 | }, |
99 | "duration": 10, | 110 | "duration": 10, |
111 | "timeline": 0, | ||
100 | "uri": "hls_450k_video.ts" | 112 | "uri": "hls_450k_video.ts" |
101 | }, | 113 | }, |
102 | { | 114 | { |
... | @@ -105,6 +117,7 @@ | ... | @@ -105,6 +117,7 @@ |
105 | "offset": 6651816 | 117 | "offset": 6651816 |
106 | }, | 118 | }, |
107 | "duration": 10, | 119 | "duration": 10, |
120 | "timeline": 0, | ||
108 | "uri": "hls_450k_video.ts" | 121 | "uri": "hls_450k_video.ts" |
109 | }, | 122 | }, |
110 | { | 123 | { |
... | @@ -113,6 +126,7 @@ | ... | @@ -113,6 +126,7 @@ |
113 | "offset": 7108092 | 126 | "offset": 7108092 |
114 | }, | 127 | }, |
115 | "duration": 10, | 128 | "duration": 10, |
129 | "timeline": 0, | ||
116 | "uri": "hls_450k_video.ts" | 130 | "uri": "hls_450k_video.ts" |
117 | }, | 131 | }, |
118 | { | 132 | { |
... | @@ -121,6 +135,7 @@ | ... | @@ -121,6 +135,7 @@ |
121 | "offset": 7576776 | 135 | "offset": 7576776 |
122 | }, | 136 | }, |
123 | "duration": 10, | 137 | "duration": 10, |
138 | "timeline": 0, | ||
124 | "uri": "hls_450k_video.ts" | 139 | "uri": "hls_450k_video.ts" |
125 | }, | 140 | }, |
126 | { | 141 | { |
... | @@ -129,6 +144,7 @@ | ... | @@ -129,6 +144,7 @@ |
129 | "offset": 8021772 | 144 | "offset": 8021772 |
130 | }, | 145 | }, |
131 | "duration": 10, | 146 | "duration": 10, |
147 | "timeline": 0, | ||
132 | "uri": "hls_450k_video.ts" | 148 | "uri": "hls_450k_video.ts" |
133 | }, | 149 | }, |
134 | { | 150 | { |
... | @@ -137,6 +153,7 @@ | ... | @@ -137,6 +153,7 @@ |
137 | "offset": 8353216 | 153 | "offset": 8353216 |
138 | }, | 154 | }, |
139 | "duration": 10, | 155 | "duration": 10, |
156 | "timeline": 0, | ||
140 | "uri": "hls_450k_video.ts" | 157 | "uri": "hls_450k_video.ts" |
141 | } | 158 | } |
142 | ], | 159 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -4,26 +4,32 @@ | ... | @@ -4,26 +4,32 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 10, | 6 | "duration": 10, |
7 | "timeline": 0, | ||
7 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" | 8 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00001.ts" |
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
11 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" | 13 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00002.ts" |
12 | }, | 14 | }, |
13 | { | 15 | { |
14 | "duration": 10, | 16 | "duration": 10, |
17 | "timeline": 0, | ||
15 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" | 18 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00003.ts" |
16 | }, | 19 | }, |
17 | { | 20 | { |
18 | "duration": 10, | 21 | "duration": 10, |
22 | "timeline": 0, | ||
19 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" | 23 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00004.ts" |
20 | }, | 24 | }, |
21 | { | 25 | { |
22 | "duration": 10, | 26 | "duration": 10, |
27 | "timeline": 0, | ||
23 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" | 28 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00005.ts" |
24 | }, | 29 | }, |
25 | { | 30 | { |
26 | "duration": 8, | 31 | "duration": 8, |
32 | "timeline": 0, | ||
27 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" | 33 | "uri": "/test/ts-files/zencoder/haze/Haze_Mantel_President_encoded_1200-00006.ts" |
28 | } | 34 | } |
29 | ], | 35 | ], | ... | ... |
... | @@ -9,6 +9,7 @@ | ... | @@ -9,6 +9,7 @@ |
9 | "offset": 0 | 9 | "offset": 0 |
10 | }, | 10 | }, |
11 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
12 | "uri": "hls_450k_video.ts" | 13 | "uri": "hls_450k_video.ts" |
13 | }, | 14 | }, |
14 | { | 15 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "offset": 522828 | 18 | "offset": 522828 |
18 | }, | 19 | }, |
19 | "duration": 10, | 20 | "duration": 10, |
21 | "timeline": 0, | ||
20 | "uri": "hls_450k_video.ts" | 22 | "uri": "hls_450k_video.ts" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -25,6 +27,7 @@ | ... | @@ -25,6 +27,7 @@ |
25 | "offset": 1110328 | 27 | "offset": 1110328 |
26 | }, | 28 | }, |
27 | "duration": 10, | 29 | "duration": 10, |
30 | "timeline": 0, | ||
28 | "uri": "hls_450k_video.ts" | 31 | "uri": "hls_450k_video.ts" |
29 | }, | 32 | }, |
30 | { | 33 | { |
... | @@ -33,6 +36,7 @@ | ... | @@ -33,6 +36,7 @@ |
33 | "offset": 1823412 | 36 | "offset": 1823412 |
34 | }, | 37 | }, |
35 | "duration": 10, | 38 | "duration": 10, |
39 | "timeline": 0, | ||
36 | "uri": "hls_450k_video.ts" | 40 | "uri": "hls_450k_video.ts" |
37 | }, | 41 | }, |
38 | { | 42 | { |
... | @@ -41,6 +45,7 @@ | ... | @@ -41,6 +45,7 @@ |
41 | "offset": 2299992 | 45 | "offset": 2299992 |
42 | }, | 46 | }, |
43 | "duration": 10, | 47 | "duration": 10, |
48 | "timeline": 0, | ||
44 | "uri": "hls_450k_video.ts" | 49 | "uri": "hls_450k_video.ts" |
45 | }, | 50 | }, |
46 | { | 51 | { |
... | @@ -49,6 +54,7 @@ | ... | @@ -49,6 +54,7 @@ |
49 | "offset": 2835604 | 54 | "offset": 2835604 |
50 | }, | 55 | }, |
51 | "duration": 10, | 56 | "duration": 10, |
57 | "timeline": 0, | ||
52 | "uri": "hls_450k_video.ts" | 58 | "uri": "hls_450k_video.ts" |
53 | }, | 59 | }, |
54 | { | 60 | { |
... | @@ -57,6 +63,7 @@ | ... | @@ -57,6 +63,7 @@ |
57 | "offset": 3042780 | 63 | "offset": 3042780 |
58 | }, | 64 | }, |
59 | "duration": 10, | 65 | "duration": 10, |
66 | "timeline": 0, | ||
60 | "uri": "hls_450k_video.ts" | 67 | "uri": "hls_450k_video.ts" |
61 | }, | 68 | }, |
62 | { | 69 | { |
... | @@ -65,6 +72,7 @@ | ... | @@ -65,6 +72,7 @@ |
65 | "offset": 3498680 | 72 | "offset": 3498680 |
66 | }, | 73 | }, |
67 | "duration": 10, | 74 | "duration": 10, |
75 | "timeline": 0, | ||
68 | "uri": "hls_450k_video.ts" | 76 | "uri": "hls_450k_video.ts" |
69 | }, | 77 | }, |
70 | { | 78 | { |
... | @@ -73,6 +81,7 @@ | ... | @@ -73,6 +81,7 @@ |
73 | "offset": 4155928 | 81 | "offset": 4155928 |
74 | }, | 82 | }, |
75 | "duration": 10, | 83 | "duration": 10, |
84 | "timeline": 0, | ||
76 | "uri": "hls_450k_video.ts" | 85 | "uri": "hls_450k_video.ts" |
77 | }, | 86 | }, |
78 | { | 87 | { |
... | @@ -81,6 +90,7 @@ | ... | @@ -81,6 +90,7 @@ |
81 | "offset": 4727636 | 90 | "offset": 4727636 |
82 | }, | 91 | }, |
83 | "duration": 10, | 92 | "duration": 10, |
93 | "timeline": 0, | ||
84 | "uri": "hls_450k_video.ts" | 94 | "uri": "hls_450k_video.ts" |
85 | }, | 95 | }, |
86 | { | 96 | { |
... | @@ -89,6 +99,7 @@ | ... | @@ -89,6 +99,7 @@ |
89 | "offset": 5212676 | 99 | "offset": 5212676 |
90 | }, | 100 | }, |
91 | "duration": 10, | 101 | "duration": 10, |
102 | "timeline": 0, | ||
92 | "uri": "hls_450k_video.ts" | 103 | "uri": "hls_450k_video.ts" |
93 | }, | 104 | }, |
94 | { | 105 | { |
... | @@ -97,6 +108,7 @@ | ... | @@ -97,6 +108,7 @@ |
97 | "offset": 5921812 | 108 | "offset": 5921812 |
98 | }, | 109 | }, |
99 | "duration": 10, | 110 | "duration": 10, |
111 | "timeline": 0, | ||
100 | "uri": "hls_450k_video.ts" | 112 | "uri": "hls_450k_video.ts" |
101 | }, | 113 | }, |
102 | { | 114 | { |
... | @@ -105,6 +117,7 @@ | ... | @@ -105,6 +117,7 @@ |
105 | "offset": 6651816 | 117 | "offset": 6651816 |
106 | }, | 118 | }, |
107 | "duration": 10, | 119 | "duration": 10, |
120 | "timeline": 0, | ||
108 | "uri": "hls_450k_video.ts" | 121 | "uri": "hls_450k_video.ts" |
109 | }, | 122 | }, |
110 | { | 123 | { |
... | @@ -113,6 +126,7 @@ | ... | @@ -113,6 +126,7 @@ |
113 | "offset": 7108092 | 126 | "offset": 7108092 |
114 | }, | 127 | }, |
115 | "duration": 10, | 128 | "duration": 10, |
129 | "timeline": 0, | ||
116 | "uri": "hls_450k_video.ts" | 130 | "uri": "hls_450k_video.ts" |
117 | }, | 131 | }, |
118 | { | 132 | { |
... | @@ -121,6 +135,7 @@ | ... | @@ -121,6 +135,7 @@ |
121 | "offset": 7576776 | 135 | "offset": 7576776 |
122 | }, | 136 | }, |
123 | "duration": 10, | 137 | "duration": 10, |
138 | "timeline": 0, | ||
124 | "uri": "hls_450k_video.ts" | 139 | "uri": "hls_450k_video.ts" |
125 | }, | 140 | }, |
126 | { | 141 | { |
... | @@ -129,6 +144,7 @@ | ... | @@ -129,6 +144,7 @@ |
129 | "offset": 8021772 | 144 | "offset": 8021772 |
130 | }, | 145 | }, |
131 | "duration": 10, | 146 | "duration": 10, |
147 | "timeline": 0, | ||
132 | "uri": "hls_450k_video.ts" | 148 | "uri": "hls_450k_video.ts" |
133 | }, | 149 | }, |
134 | { | 150 | { |
... | @@ -137,6 +153,7 @@ | ... | @@ -137,6 +153,7 @@ |
137 | "offset": 8353216 | 153 | "offset": 8353216 |
138 | }, | 154 | }, |
139 | "duration": 1.4167, | 155 | "duration": 1.4167, |
156 | "timeline": 0, | ||
140 | "uri": "hls_450k_video.ts" | 157 | "uri": "hls_450k_video.ts" |
141 | } | 158 | } |
142 | ], | 159 | ], | ... | ... |
... | @@ -5,14 +5,17 @@ | ... | @@ -5,14 +5,17 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 8, | 12 | "duration": 8, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 8, | 17 | "duration": 8, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | } | 20 | } |
18 | ], | 21 | ], | ... | ... |
... | @@ -4,38 +4,47 @@ | ... | @@ -4,38 +4,47 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 10, | 6 | "duration": 10, |
7 | "timeline": 0, | ||
7 | "uri": "001.ts" | 8 | "uri": "001.ts" |
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "duration": 19, | 11 | "duration": 19, |
12 | "timeline": 0, | ||
11 | "uri": "002.ts" | 13 | "uri": "002.ts" |
12 | }, | 14 | }, |
13 | { | 15 | { |
14 | "duration": 10, | 16 | "duration": 10, |
17 | "timeline": 0, | ||
15 | "uri": "003.ts" | 18 | "uri": "003.ts" |
16 | }, | 19 | }, |
17 | { | 20 | { |
18 | "duration": 11, | 21 | "duration": 11, |
22 | "timeline": 0, | ||
19 | "uri": "004.ts" | 23 | "uri": "004.ts" |
20 | }, | 24 | }, |
21 | { | 25 | { |
22 | "duration": 10, | 26 | "duration": 10, |
27 | "timeline": 0, | ||
23 | "uri": "005.ts" | 28 | "uri": "005.ts" |
24 | }, | 29 | }, |
25 | { | 30 | { |
26 | "duration": 10, | 31 | "duration": 10, |
32 | "timeline": 0, | ||
27 | "uri": "006.ts" | 33 | "uri": "006.ts" |
28 | }, | 34 | }, |
29 | { | 35 | { |
30 | "duration": 10, | 36 | "duration": 10, |
37 | "timeline": 0, | ||
31 | "uri": "007.ts" | 38 | "uri": "007.ts" |
32 | }, | 39 | }, |
33 | { | 40 | { |
34 | "duration": 10, | 41 | "duration": 10, |
42 | "timeline": 0, | ||
35 | "uri": "008.ts" | 43 | "uri": "008.ts" |
36 | }, | 44 | }, |
37 | { | 45 | { |
38 | "duration": 16, | 46 | "duration": 16, |
47 | "timeline": 0, | ||
39 | "uri": "009.ts" | 48 | "uri": "009.ts" |
40 | } | 49 | } |
41 | ], | 50 | ], | ... | ... |
... | @@ -4,22 +4,27 @@ | ... | @@ -4,22 +4,27 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 10, | 6 | "duration": 10, |
7 | "timeline": 0, | ||
7 | "uri": "/test/ts-files/zencoder/gogo/00001.ts" | 8 | "uri": "/test/ts-files/zencoder/gogo/00001.ts" |
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
11 | "uri": "/test/ts-files/zencoder/gogo/00002.ts" | 13 | "uri": "/test/ts-files/zencoder/gogo/00002.ts" |
12 | }, | 14 | }, |
13 | { | 15 | { |
14 | "duration": 10, | 16 | "duration": 10, |
17 | "timeline": 0, | ||
15 | "uri": "/test/ts-files/zencoder/gogo/00003.ts" | 18 | "uri": "/test/ts-files/zencoder/gogo/00003.ts" |
16 | }, | 19 | }, |
17 | { | 20 | { |
18 | "duration": 10, | 21 | "duration": 10, |
22 | "timeline": 0, | ||
19 | "uri": "/test/ts-files/zencoder/gogo/00004.ts" | 23 | "uri": "/test/ts-files/zencoder/gogo/00004.ts" |
20 | }, | 24 | }, |
21 | { | 25 | { |
22 | "duration": 10, | 26 | "duration": 10, |
27 | "timeline": 0, | ||
23 | "uri": "/test/ts-files/zencoder/gogo/00005.ts" | 28 | "uri": "/test/ts-files/zencoder/gogo/00005.ts" |
24 | } | 29 | } |
25 | ], | 30 | ], | ... | ... |
... | @@ -10,6 +10,7 @@ | ... | @@ -10,6 +10,7 @@ |
10 | "height": 224 | 10 | "height": 224 |
11 | } | 11 | } |
12 | }, | 12 | }, |
13 | "timeline": 0, | ||
13 | "uri": "media.m3u8" | 14 | "uri": "media.m3u8" |
14 | }, | 15 | }, |
15 | { | 16 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "PROGRAM-ID": 1, | 18 | "PROGRAM-ID": 1, |
18 | "BANDWIDTH": 40000 | 19 | "BANDWIDTH": 40000 |
19 | }, | 20 | }, |
21 | "timeline": 0, | ||
20 | "uri": "media1.m3u8" | 22 | "uri": "media1.m3u8" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -28,6 +30,7 @@ | ... | @@ -28,6 +30,7 @@ |
28 | "height": 224 | 30 | "height": 224 |
29 | } | 31 | } |
30 | }, | 32 | }, |
33 | "timeline": 0, | ||
31 | "uri": "media2.m3u8" | 34 | "uri": "media2.m3u8" |
32 | }, | 35 | }, |
33 | { | 36 | { |
... | @@ -39,8 +42,15 @@ | ... | @@ -39,8 +42,15 @@ |
39 | "height": 540 | 42 | "height": 540 |
40 | } | 43 | } |
41 | }, | 44 | }, |
45 | "timeline": 0, | ||
42 | "uri": "media3.m3u8" | 46 | "uri": "media3.m3u8" |
43 | } | 47 | } |
44 | ], | 48 | ], |
45 | "discontinuityStarts": [] | 49 | "discontinuityStarts": [], |
50 | "mediaGroups": { | ||
51 | "VIDEO": {}, | ||
52 | "AUDIO": {}, | ||
53 | "CLOSED-CAPTIONS": {}, | ||
54 | "SUBTITLES": {} | ||
55 | } | ||
46 | } | 56 | } | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "media-00001.ts" | 9 | "uri": "media-00001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "media-00002.ts" | 14 | "uri": "media-00002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "media-00003.ts" | 19 | "uri": "media-00003.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 10, | 22 | "duration": 10, |
23 | "timeline": 0, | ||
20 | "uri": "media-00004.ts" | 24 | "uri": "media-00004.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -4,10 +4,12 @@ | ... | @@ -4,10 +4,12 @@ |
4 | "segments": [ | 4 | "segments": [ |
5 | { | 5 | { |
6 | "duration": 10, | 6 | "duration": 10, |
7 | "timeline": 0, | ||
7 | "uri": "00001.ts" | 8 | "uri": "00001.ts" |
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
11 | "uri": "00002.ts" | 13 | "uri": "00002.ts" |
12 | } | 14 | } |
13 | ], | 15 | ], | ... | ... |
... | @@ -5,14 +5,17 @@ | ... | @@ -5,14 +5,17 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "hls_450k_video.ts" | 9 | "uri": "hls_450k_video.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "hls_450k_video.ts" | 14 | "uri": "hls_450k_video.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "hls_450k_video.ts" | 19 | "uri": "hls_450k_video.ts" |
17 | } | 20 | } |
18 | ], | 21 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 8, | 12 | "duration": 8, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 8, | 17 | "duration": 8, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 8, | 22 | "duration": 8, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
utils/manifest/multipleAudioGroups.js
0 → 100644
1 | { | ||
2 | allowCache: true, | ||
3 | discontinuityStarts: [], | ||
4 | mediaGroups: { | ||
5 | AUDIO: { | ||
6 | "audio-lo": { | ||
7 | "English": { | ||
8 | autoselect: true, | ||
9 | default: true, | ||
10 | language: "eng", | ||
11 | uri: "englo/prog_index.m3u8" | ||
12 | }, | ||
13 | "Français": { | ||
14 | autoselect: true, | ||
15 | default: false, | ||
16 | language: "fre", | ||
17 | uri: "frelo/prog_index.m3u8" | ||
18 | }, | ||
19 | "Espanol": { | ||
20 | autoselect: true, | ||
21 | default: false, | ||
22 | language: "sp", | ||
23 | uri: "splo/prog_index.m3u8" | ||
24 | } | ||
25 | }, | ||
26 | "audio-hi": { | ||
27 | "English": { | ||
28 | autoselect: true, | ||
29 | default: true, | ||
30 | language: "eng", | ||
31 | uri: "eng/prog_index.m3u8" | ||
32 | }, | ||
33 | "Français": { | ||
34 | autoselect: true, | ||
35 | default: false, | ||
36 | language: "fre", | ||
37 | uri: "fre/prog_index.m3u8" | ||
38 | }, | ||
39 | "Espanol": { | ||
40 | autoselect: true, | ||
41 | default: false, | ||
42 | language: "sp", | ||
43 | uri: "sp/prog_index.m3u8" | ||
44 | } | ||
45 | } | ||
46 | }, | ||
47 | VIDEO: {}, | ||
48 | "CLOSED-CAPTIONS": {}, | ||
49 | SUBTITLES: {} | ||
50 | }, | ||
51 | playlists: [{ | ||
52 | attributes: { | ||
53 | "PROGRAM-ID": 1, | ||
54 | BANDWIDTH: 195023, | ||
55 | CODECS: "mp4a.40.5", | ||
56 | AUDIO: "audio-lo", | ||
57 | }, | ||
58 | timeline: 0, | ||
59 | uri: "lo/prog_index.m3u8" | ||
60 | }, { | ||
61 | attributes: { | ||
62 | "PROGRAM-ID": 1, | ||
63 | BANDWIDTH: 260000, | ||
64 | CODECS: "avc1.42e01e,mp4a.40.2", | ||
65 | AUDIO: "audio-lo" | ||
66 | }, | ||
67 | timeline: 0, | ||
68 | uri: "lo2/prog_index.m3u8" | ||
69 | }, { | ||
70 | attributes: { | ||
71 | "PROGRAM-ID": 1, | ||
72 | BANDWIDTH: 591680, | ||
73 | CODECS: "mp4a.40.2, avc1.64001e", | ||
74 | AUDIO: "audio-hi" | ||
75 | }, | ||
76 | timeline: 0, | ||
77 | uri: "hi/prog_index.m3u8" | ||
78 | }, { | ||
79 | attributes: { | ||
80 | "PROGRAM-ID": 1, | ||
81 | BANDWIDTH: 650000, | ||
82 | CODECS: "avc1.42e01e,mp4a.40.2", | ||
83 | AUDIO: "audio-hi" | ||
84 | }, | ||
85 | timeline: 0, | ||
86 | uri: "hi2/prog_index.m3u8" | ||
87 | }] | ||
88 | } |
utils/manifest/multipleAudioGroups.m3u8
0 → 100644
1 | #EXTM3U | ||
2 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="eng",NAME="English",AUTOSELECT=YES, DEFAULT=YES,URI="englo/prog_index.m3u8" | ||
3 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="frelo/prog_index.m3u8" | ||
4 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="splo/prog_index.m3u8" | ||
5 | |||
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="eng",NAME="English",AUTOSELECT=YES, DEFAULT=YES,URI="eng/prog_index.m3u8" | ||
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="fre/prog_index.m3u8" | ||
8 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="sp/prog_index.m3u8" | ||
9 | |||
10 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="mp4a.40.5", AUDIO="audio-lo" | ||
11 | lo/prog_index.m3u8 | ||
12 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=260000,CODECS="avc1.42e01e,mp4a.40.2", AUDIO="audio-lo" | ||
13 | lo2/prog_index.m3u8 | ||
14 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=591680,CODECS="mp4a.40.2, avc1.64001e", AUDIO="audio-hi" | ||
15 | hi/prog_index.m3u8 | ||
16 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=650000,CODECS="avc1.42e01e,mp4a.40.2", AUDIO="audio-hi" | ||
17 | hi2/prog_index.m3u8 |
1 | { | ||
2 | allowCache: true, | ||
3 | discontinuityStarts: [], | ||
4 | mediaGroups: { | ||
5 | AUDIO: { | ||
6 | "audio-lo": { | ||
7 | "English": { | ||
8 | autoselect: true, | ||
9 | default: true, | ||
10 | language: "eng", | ||
11 | }, | ||
12 | "Français": { | ||
13 | autoselect: true, | ||
14 | default: false, | ||
15 | language: "fre", | ||
16 | uri: "frelo/prog_index.m3u8" | ||
17 | }, | ||
18 | "Espanol": { | ||
19 | autoselect: true, | ||
20 | default: false, | ||
21 | language: "sp", | ||
22 | uri: "splo/prog_index.m3u8" | ||
23 | } | ||
24 | }, | ||
25 | "audio-hi": { | ||
26 | "English": { | ||
27 | autoselect: true, | ||
28 | default: true, | ||
29 | language: "eng", | ||
30 | uri: "eng/prog_index.m3u8" | ||
31 | }, | ||
32 | "Français": { | ||
33 | autoselect: true, | ||
34 | default: false, | ||
35 | language: "fre", | ||
36 | uri: "fre/prog_index.m3u8" | ||
37 | }, | ||
38 | "Espanol": { | ||
39 | autoselect: true, | ||
40 | default: false, | ||
41 | language: "sp", | ||
42 | uri: "sp/prog_index.m3u8" | ||
43 | } | ||
44 | } | ||
45 | }, | ||
46 | VIDEO: {}, | ||
47 | "CLOSED-CAPTIONS": {}, | ||
48 | SUBTITLES: {} | ||
49 | }, | ||
50 | playlists: [{ | ||
51 | attributes: { | ||
52 | "PROGRAM-ID": 1, | ||
53 | BANDWIDTH: 195023, | ||
54 | CODECS: "mp4a.40.5", | ||
55 | AUDIO: "audio-lo", | ||
56 | }, | ||
57 | timeline: 0, | ||
58 | uri: "lo/prog_index.m3u8" | ||
59 | }, { | ||
60 | attributes: { | ||
61 | "PROGRAM-ID": 1, | ||
62 | BANDWIDTH: 260000, | ||
63 | CODECS: "avc1.42e01e,mp4a.40.2", | ||
64 | AUDIO: "audio-lo" | ||
65 | }, | ||
66 | timeline: 0, | ||
67 | uri: "lo2/prog_index.m3u8" | ||
68 | }, { | ||
69 | attributes: { | ||
70 | "PROGRAM-ID": 1, | ||
71 | BANDWIDTH: 591680, | ||
72 | CODECS: "mp4a.40.2, avc1.64001e", | ||
73 | AUDIO: "audio-hi" | ||
74 | }, | ||
75 | timeline: 0, | ||
76 | uri: "hi/prog_index.m3u8" | ||
77 | }, { | ||
78 | attributes: { | ||
79 | "PROGRAM-ID": 1, | ||
80 | BANDWIDTH: 650000, | ||
81 | CODECS: "avc1.42e01e,mp4a.40.2", | ||
82 | AUDIO: "audio-hi" | ||
83 | }, | ||
84 | timeline: 0, | ||
85 | uri: "hi2/prog_index.m3u8" | ||
86 | }] | ||
87 | } |
1 | #EXTM3U | ||
2 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="eng",NAME="English",AUTOSELECT=YES, DEFAULT=YES | ||
3 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="frelo/prog_index.m3u8" | ||
4 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="splo/prog_index.m3u8" | ||
5 | |||
6 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="eng",NAME="English",AUTOSELECT=YES, DEFAULT=YES,URI="eng/prog_index.m3u8" | ||
7 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="fre/prog_index.m3u8" | ||
8 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="sp/prog_index.m3u8" | ||
9 | |||
10 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="mp4a.40.5", AUDIO="audio-lo" | ||
11 | lo/prog_index.m3u8 | ||
12 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=260000,CODECS="avc1.42e01e,mp4a.40.2", AUDIO="audio-lo" | ||
13 | lo2/prog_index.m3u8 | ||
14 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=591680,CODECS="mp4a.40.2, avc1.64001e", AUDIO="audio-hi" | ||
15 | hi/prog_index.m3u8 | ||
16 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=650000,CODECS="avc1.42e01e,mp4a.40.2", AUDIO="audio-hi" | ||
17 | hi2/prog_index.m3u8 |
... | @@ -4,19 +4,23 @@ | ... | @@ -4,19 +4,23 @@ |
4 | "targetDuration": 10, | 4 | "targetDuration": 10, |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "uri": "001.ts" | 7 | "uri": "001.ts", |
8 | "timeline": 0 | ||
8 | }, | 9 | }, |
9 | { | 10 | { |
10 | "uri": "002.ts", | 11 | "uri": "002.ts", |
11 | "duration": 9 | 12 | "duration": 9, |
13 | "timeline": 0 | ||
12 | }, | 14 | }, |
13 | { | 15 | { |
14 | "uri": "003.ts", | 16 | "uri": "003.ts", |
15 | "duration": 7 | 17 | "duration": 7, |
18 | "timeline": 0 | ||
16 | }, | 19 | }, |
17 | { | 20 | { |
18 | "uri": "004.ts", | 21 | "uri": "004.ts", |
19 | "duration": 10 | 22 | "duration": 10, |
23 | "timeline": 0 | ||
20 | } | 24 | } |
21 | ], | 25 | ], |
22 | "discontinuitySequence": 0, | 26 | "discontinuitySequence": 0, | ... | ... |
utils/manifest/multipleVideo.js
0 → 100644
1 | { | ||
2 | allowCache: true, | ||
3 | discontinuityStarts: [], | ||
4 | mediaGroups: { | ||
5 | AUDIO: { | ||
6 | aac: { | ||
7 | English: { | ||
8 | autoselect: true, | ||
9 | default: true, | ||
10 | language: "eng", | ||
11 | uri: "eng/prog_index.m3u8" | ||
12 | } | ||
13 | } | ||
14 | }, | ||
15 | VIDEO: { | ||
16 | "200kbs": { | ||
17 | Angle1: { | ||
18 | autoselect: true, | ||
19 | default: true | ||
20 | }, | ||
21 | Angle2: { | ||
22 | autoselect: true, | ||
23 | default: false, | ||
24 | uri: "Angle2/200kbs/prog_index.m3u8" | ||
25 | }, | ||
26 | Angle3: { | ||
27 | autoselect: true, | ||
28 | default: false, | ||
29 | uri: "Angle3/200kbs/prog_index.m3u8" | ||
30 | } | ||
31 | }, | ||
32 | "500kbs": { | ||
33 | Angle1: { | ||
34 | autoselect: true, | ||
35 | default: true | ||
36 | }, | ||
37 | Angle2: { | ||
38 | autoselect: true, | ||
39 | default: false, | ||
40 | uri: "Angle2/500kbs/prog_index.m3u8" | ||
41 | }, | ||
42 | Angle3: { | ||
43 | autoselect: true, | ||
44 | default: false, | ||
45 | uri: "Angle3/500kbs/prog_index.m3u8" | ||
46 | } | ||
47 | } | ||
48 | }, | ||
49 | "CLOSED-CAPTIONS": {}, | ||
50 | SUBTITLES: {} | ||
51 | }, | ||
52 | playlists: [{ | ||
53 | attributes: { | ||
54 | "PROGRAM-ID": 1, | ||
55 | BANDWIDTH: 300000, | ||
56 | CODECS: "mp4a.40.2,avc1.4d401e", | ||
57 | AUDIO: "aac", | ||
58 | VIDEO: "200kbs" | ||
59 | }, | ||
60 | timeline: 0, | ||
61 | uri: "Angle1/200kbs/prog_index.m3u" | ||
62 | }, { | ||
63 | attributes: { | ||
64 | "PROGRAM-ID": 1, | ||
65 | BANDWIDTH: 754857, | ||
66 | CODECS: "mp4a.40.2,avc1.4d401e", | ||
67 | AUDIO: "aac", | ||
68 | VIDEO: "500kbs" | ||
69 | }, | ||
70 | timeline: 0, | ||
71 | uri: "Angle1/500kbs/prog_index.m3u8" | ||
72 | }] | ||
73 | } |
utils/manifest/multipleVideo.m3u8
0 → 100644
1 | #EXTM3U | ||
2 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle1",AUTOSELECT=YES,DEFAULT=YES | ||
3 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle2",AUTOSELECT=YES,DEFAULT=NO,URI="Angle2/200kbs/prog_index.m3u8" | ||
4 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle3",AUTOSELECT=YES,DEFAULT=NO,URI="Angle3/200kbs/prog_index.m3u8" | ||
5 | |||
6 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle1",AUTOSELECT=YES,DEFAULT=YES | ||
7 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle2",AUTOSELECT=YES,DEFAULT=NO,URI="Angle2/500kbs/prog_index.m3u8" | ||
8 | #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle3",AUTOSELECT=YES,DEFAULT=NO,URI="Angle3/500kbs/prog_index.m3u8" | ||
9 | |||
10 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="eng",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8" | ||
11 | |||
12 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=300000,CODECS="mp4a.40.2,avc1.4d401e",VIDEO="200kbs",AUDIO="aac" | ||
13 | Angle1/200kbs/prog_index.m3u | ||
14 | |||
15 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=754857,CODECS="mp4a.40.2,avc1.4d401e",VIDEO="500kbs",AUDIO="aac" | ||
16 | Angle1/500kbs/prog_index.m3u8 | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -9,6 +9,7 @@ | ... | @@ -9,6 +9,7 @@ |
9 | "offset": 0 | 9 | "offset": 0 |
10 | }, | 10 | }, |
11 | "duration": 10, | 11 | "duration": 10, |
12 | "timeline": 0, | ||
12 | "uri": "hls_450k_video.ts" | 13 | "uri": "hls_450k_video.ts" |
13 | }, | 14 | }, |
14 | { | 15 | { |
... | @@ -17,6 +18,7 @@ | ... | @@ -17,6 +18,7 @@ |
17 | "offset": 522828 | 18 | "offset": 522828 |
18 | }, | 19 | }, |
19 | "duration": 10, | 20 | "duration": 10, |
21 | "timeline": 0, | ||
20 | "uri": "hls_450k_video.ts" | 22 | "uri": "hls_450k_video.ts" |
21 | }, | 23 | }, |
22 | { | 24 | { |
... | @@ -25,6 +27,7 @@ | ... | @@ -25,6 +27,7 @@ |
25 | "offset": 1110328 | 27 | "offset": 1110328 |
26 | }, | 28 | }, |
27 | "duration": 10, | 29 | "duration": 10, |
30 | "timeline": 0, | ||
28 | "uri": "hls_450k_video.ts" | 31 | "uri": "hls_450k_video.ts" |
29 | }, | 32 | }, |
30 | { | 33 | { |
... | @@ -33,6 +36,7 @@ | ... | @@ -33,6 +36,7 @@ |
33 | "offset": 1823412 | 36 | "offset": 1823412 |
34 | }, | 37 | }, |
35 | "duration": 10, | 38 | "duration": 10, |
39 | "timeline": 0, | ||
36 | "uri": "hls_450k_video.ts" | 40 | "uri": "hls_450k_video.ts" |
37 | }, | 41 | }, |
38 | { | 42 | { |
... | @@ -41,6 +45,7 @@ | ... | @@ -41,6 +45,7 @@ |
41 | "offset": 2299992 | 45 | "offset": 2299992 |
42 | }, | 46 | }, |
43 | "duration": 10, | 47 | "duration": 10, |
48 | "timeline": 0, | ||
44 | "uri": "hls_450k_video.ts" | 49 | "uri": "hls_450k_video.ts" |
45 | }, | 50 | }, |
46 | { | 51 | { |
... | @@ -49,6 +54,7 @@ | ... | @@ -49,6 +54,7 @@ |
49 | "offset": 2835604 | 54 | "offset": 2835604 |
50 | }, | 55 | }, |
51 | "duration": 10, | 56 | "duration": 10, |
57 | "timeline": 0, | ||
52 | "uri": "hls_450k_video.ts" | 58 | "uri": "hls_450k_video.ts" |
53 | }, | 59 | }, |
54 | { | 60 | { |
... | @@ -57,6 +63,7 @@ | ... | @@ -57,6 +63,7 @@ |
57 | "offset": 3042780 | 63 | "offset": 3042780 |
58 | }, | 64 | }, |
59 | "duration": 10, | 65 | "duration": 10, |
66 | "timeline": 0, | ||
60 | "uri": "hls_450k_video.ts" | 67 | "uri": "hls_450k_video.ts" |
61 | }, | 68 | }, |
62 | { | 69 | { |
... | @@ -65,6 +72,7 @@ | ... | @@ -65,6 +72,7 @@ |
65 | "offset": 3498680 | 72 | "offset": 3498680 |
66 | }, | 73 | }, |
67 | "duration": 10, | 74 | "duration": 10, |
75 | "timeline": 0, | ||
68 | "uri": "hls_450k_video.ts" | 76 | "uri": "hls_450k_video.ts" |
69 | }, | 77 | }, |
70 | { | 78 | { |
... | @@ -73,6 +81,7 @@ | ... | @@ -73,6 +81,7 @@ |
73 | "offset": 4155928 | 81 | "offset": 4155928 |
74 | }, | 82 | }, |
75 | "duration": 10, | 83 | "duration": 10, |
84 | "timeline": 0, | ||
76 | "uri": "hls_450k_video.ts" | 85 | "uri": "hls_450k_video.ts" |
77 | }, | 86 | }, |
78 | { | 87 | { |
... | @@ -81,6 +90,7 @@ | ... | @@ -81,6 +90,7 @@ |
81 | "offset": 4727636 | 90 | "offset": 4727636 |
82 | }, | 91 | }, |
83 | "duration": 10, | 92 | "duration": 10, |
93 | "timeline": 0, | ||
84 | "uri": "hls_450k_video.ts" | 94 | "uri": "hls_450k_video.ts" |
85 | }, | 95 | }, |
86 | { | 96 | { |
... | @@ -89,6 +99,7 @@ | ... | @@ -89,6 +99,7 @@ |
89 | "offset": 5212676 | 99 | "offset": 5212676 |
90 | }, | 100 | }, |
91 | "duration": 10, | 101 | "duration": 10, |
102 | "timeline": 0, | ||
92 | "uri": "hls_450k_video.ts" | 103 | "uri": "hls_450k_video.ts" |
93 | }, | 104 | }, |
94 | { | 105 | { |
... | @@ -97,6 +108,7 @@ | ... | @@ -97,6 +108,7 @@ |
97 | "offset": 5921812 | 108 | "offset": 5921812 |
98 | }, | 109 | }, |
99 | "duration": 10, | 110 | "duration": 10, |
111 | "timeline": 0, | ||
100 | "uri": "hls_450k_video.ts" | 112 | "uri": "hls_450k_video.ts" |
101 | }, | 113 | }, |
102 | { | 114 | { |
... | @@ -105,6 +117,7 @@ | ... | @@ -105,6 +117,7 @@ |
105 | "offset": 6651816 | 117 | "offset": 6651816 |
106 | }, | 118 | }, |
107 | "duration": 10, | 119 | "duration": 10, |
120 | "timeline": 0, | ||
108 | "uri": "hls_450k_video.ts" | 121 | "uri": "hls_450k_video.ts" |
109 | }, | 122 | }, |
110 | { | 123 | { |
... | @@ -113,6 +126,7 @@ | ... | @@ -113,6 +126,7 @@ |
113 | "offset": 7108092 | 126 | "offset": 7108092 |
114 | }, | 127 | }, |
115 | "duration": 10, | 128 | "duration": 10, |
129 | "timeline": 0, | ||
116 | "uri": "hls_450k_video.ts" | 130 | "uri": "hls_450k_video.ts" |
117 | }, | 131 | }, |
118 | { | 132 | { |
... | @@ -121,6 +135,7 @@ | ... | @@ -121,6 +135,7 @@ |
121 | "offset": 7576776 | 135 | "offset": 7576776 |
122 | }, | 136 | }, |
123 | "duration": 10, | 137 | "duration": 10, |
138 | "timeline": 0, | ||
124 | "uri": "hls_450k_video.ts" | 139 | "uri": "hls_450k_video.ts" |
125 | }, | 140 | }, |
126 | { | 141 | { |
... | @@ -129,6 +144,7 @@ | ... | @@ -129,6 +144,7 @@ |
129 | "offset": 8021772 | 144 | "offset": 8021772 |
130 | }, | 145 | }, |
131 | "duration": 10, | 146 | "duration": 10, |
147 | "timeline": 0, | ||
132 | "uri": "hls_450k_video.ts" | 148 | "uri": "hls_450k_video.ts" |
133 | }, | 149 | }, |
134 | { | 150 | { |
... | @@ -137,6 +153,7 @@ | ... | @@ -137,6 +153,7 @@ |
137 | "offset": 8353216 | 153 | "offset": 8353216 |
138 | }, | 154 | }, |
139 | "duration": 1.4167, | 155 | "duration": 1.4167, |
156 | "timeline": 0, | ||
140 | "uri": "hls_450k_video.ts" | 157 | "uri": "hls_450k_video.ts" |
141 | } | 158 | } |
142 | ], | 159 | ], | ... | ... |
... | @@ -5,6 +5,7 @@ | ... | @@ -5,6 +5,7 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | } | 10 | } |
10 | ], | 11 | ], | ... | ... |
... | @@ -5,11 +5,19 @@ | ... | @@ -5,11 +5,19 @@ |
5 | "attributes": { | 5 | "attributes": { |
6 | "PROGRAM-ID": 1 | 6 | "PROGRAM-ID": 1 |
7 | }, | 7 | }, |
8 | "timeline": 0, | ||
8 | "uri": "media.m3u8" | 9 | "uri": "media.m3u8" |
9 | }, | 10 | }, |
10 | { | 11 | { |
12 | "timeline": 0, | ||
11 | "uri": "media1.m3u8" | 13 | "uri": "media1.m3u8" |
12 | } | 14 | } |
13 | ], | 15 | ], |
14 | "discontinuityStarts": [] | 16 | "discontinuityStarts": [], |
17 | "mediaGroups": { | ||
18 | "VIDEO": {}, | ||
19 | "AUDIO": {}, | ||
20 | "CLOSED-CAPTIONS": {}, | ||
21 | "SUBTITLES": {} | ||
22 | } | ||
15 | } | 23 | } | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 6.64, | 7 | "duration": 6.64, |
8 | "timeline": 0, | ||
8 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" | 9 | "uri": "/test/ts-files/tvy7/8a5e2822668b5370f4eb1438b2564fb7ab12ffe1-hi720.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 6.08, | 12 | "duration": 6.08, |
13 | "timeline": 0, | ||
12 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" | 14 | "uri": "/test/ts-files/tvy7/56be1cef869a1c0cc8e38864ad1add17d187f051-hi720.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 6.6, | 17 | "duration": 6.6, |
18 | "timeline": 0, | ||
16 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" | 19 | "uri": "/test/ts-files/tvy7/549c8c77f55f049741a06596e5c1e01dacaa46d0-hi720.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 5, | 22 | "duration": 5, |
23 | "timeline": 0, | ||
20 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" | 24 | "uri": "/test/ts-files/tvy7/6cfa378684ffeb1c455a64dae6c103290a1f53d4-hi720.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
... | @@ -5,18 +5,22 @@ | ... | @@ -5,18 +5,22 @@ |
5 | "segments": [ | 5 | "segments": [ |
6 | { | 6 | { |
7 | "duration": 10, | 7 | "duration": 10, |
8 | "timeline": 0, | ||
8 | "uri": "http://example.com/00001.ts" | 9 | "uri": "http://example.com/00001.ts" |
9 | }, | 10 | }, |
10 | { | 11 | { |
11 | "duration": 10, | 12 | "duration": 10, |
13 | "timeline": 0, | ||
12 | "uri": "https://example.com/00002.ts" | 14 | "uri": "https://example.com/00002.ts" |
13 | }, | 15 | }, |
14 | { | 16 | { |
15 | "duration": 10, | 17 | "duration": 10, |
18 | "timeline": 0, | ||
16 | "uri": "//example.com/00003.ts" | 19 | "uri": "//example.com/00003.ts" |
17 | }, | 20 | }, |
18 | { | 21 | { |
19 | "duration": 10, | 22 | "duration": 10, |
23 | "timeline": 0, | ||
20 | "uri": "http://example.com/00004.ts" | 24 | "uri": "http://example.com/00004.ts" |
21 | } | 25 | } |
22 | ], | 26 | ], | ... | ... |
utils/stats/audio-track-selector.js
0 → 100644
1 | (function(videojs) { | ||
2 | var Component = videojs.getComponent('Component'); | ||
3 | |||
4 | // ----------------- | ||
5 | // AudioTrackMenuItem | ||
6 | // ----------------- | ||
7 | // | ||
8 | var MenuItem = videojs.getComponent('MenuItem'); | ||
9 | |||
10 | var AudioTrackMenuItem = videojs.extend(MenuItem, { | ||
11 | constructor: function(player, options) { | ||
12 | var track = options.track; | ||
13 | var tracks = player.audioTracks(); | ||
14 | |||
15 | options.label = track.label || track.language || 'Unknown'; | ||
16 | options.selected = track.enabled; | ||
17 | |||
18 | MenuItem.call(this, player, options); | ||
19 | |||
20 | this.track = track; | ||
21 | |||
22 | if (tracks) { | ||
23 | var changeHandler = videojs.bind(this, this.handleTracksChange); | ||
24 | |||
25 | tracks.addEventListener('change', changeHandler); | ||
26 | this.on('dispose', function() { | ||
27 | tracks.removeEventListener('change', changeHandler); | ||
28 | }); | ||
29 | } | ||
30 | }, | ||
31 | |||
32 | handleClick: function(event) { | ||
33 | var kind = this.track.kind; | ||
34 | var tracks = this.player_.audioTracks(); | ||
35 | |||
36 | MenuItem.prototype.handleClick.call(this, event); | ||
37 | |||
38 | if (!tracks) return; | ||
39 | |||
40 | for (var i = 0; i < tracks.length; i++) { | ||
41 | var track = tracks[i]; | ||
42 | |||
43 | if (track === this.track) { | ||
44 | track.enabled = true; | ||
45 | } | ||
46 | } | ||
47 | }, | ||
48 | |||
49 | handleTracksChange: function(event) { | ||
50 | this.selected(this.track.enabled); | ||
51 | } | ||
52 | }); | ||
53 | |||
54 | Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); | ||
55 | |||
56 | // ----------------- | ||
57 | // AudioTrackButton | ||
58 | // ----------------- | ||
59 | // | ||
60 | var MenuButton = videojs.getComponent('MenuButton'); | ||
61 | |||
62 | var AudioTrackButton = videojs.extend(MenuButton, { | ||
63 | constructor: function(player, options) { | ||
64 | MenuButton.call(this, player, options); | ||
65 | this.el_.setAttribute('aria-label','Audio Menu'); | ||
66 | |||
67 | var tracks = this.player_.audioTracks(); | ||
68 | |||
69 | if (this.items.length <= 1) { | ||
70 | this.hide(); | ||
71 | } | ||
72 | |||
73 | if (!tracks) { | ||
74 | return; | ||
75 | } | ||
76 | |||
77 | var updateHandler = videojs.bind(this, this.update); | ||
78 | tracks.addEventListener('removetrack', updateHandler); | ||
79 | tracks.addEventListener('addtrack', updateHandler); | ||
80 | |||
81 | this.player_.on('dispose', function() { | ||
82 | tracks.removeEventListener('removetrack', updateHandler); | ||
83 | tracks.removeEventListener('addtrack', updateHandler); | ||
84 | }); | ||
85 | }, | ||
86 | |||
87 | buildCSSClass() { | ||
88 | return 'vjs-subtitles-button ' + MenuButton.prototype.buildCSSClass.call(this); | ||
89 | }, | ||
90 | |||
91 | createItems: function(items) { | ||
92 | items = items || []; | ||
93 | |||
94 | var tracks = this.player_.audioTracks(); | ||
95 | |||
96 | if (!tracks) { | ||
97 | return items; | ||
98 | } | ||
99 | |||
100 | for (var i = 0; i < tracks.length; i++) { | ||
101 | var track = tracks[i]; | ||
102 | |||
103 | items.push(new AudioTrackMenuItem(this.player_, { | ||
104 | 'selectable': true, | ||
105 | 'track': track | ||
106 | })); | ||
107 | } | ||
108 | |||
109 | return items; | ||
110 | } | ||
111 | }); | ||
112 | |||
113 | Component.registerComponent('AudioTrackButton', AudioTrackButton); | ||
114 | })(window.videojs); |
... | @@ -9,28 +9,16 @@ | ... | @@ -9,28 +9,16 @@ |
9 | <!-- video.js --> | 9 | <!-- video.js --> |
10 | <script src="../../node_modules/video.js/dist/video.js"></script> | 10 | <script src="../../node_modules/video.js/dist/video.js"></script> |
11 | 11 | ||
12 | <!-- Media Sources plugin --> | ||
13 | <script src="../../node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script> | ||
14 | |||
15 | <!-- HLS plugin --> | 12 | <!-- HLS plugin --> |
16 | <script src="../../src/videojs-hls.js"></script> | 13 | <script src="../../dist/videojs-contrib-hls.js"></script> |
17 | |||
18 | <!-- m3u8 handling --> | ||
19 | <script src="../../src/xhr.js"></script> | ||
20 | <script src="../../src/stream.js"></script> | ||
21 | <script src="../../src/m3u8/m3u8-parser.js"></script> | ||
22 | <script src="../../src/playlist.js"></script> | ||
23 | <script src="../../src/playlist-loader.js"></script> | ||
24 | 14 | ||
25 | <script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | 15 | <!-- Track Selector plugin --> |
26 | <script src="../../src/decrypter.js"></script> | 16 | <script src="audio-track-selector.js"></script> |
27 | 17 | ||
28 | <!-- player stats visualization --> | 18 | <!-- player stats visualization --> |
29 | <link href="stats.css" rel="stylesheet"> | 19 | <link href="stats.css" rel="stylesheet"> |
30 | <script src="../switcher/js/vendor/d3.min.js"></script> | 20 | <script src="../switcher/js/vendor/d3.min.js"></script> |
31 | 21 | ||
32 | <!-- debugging --> | ||
33 | <script src="../../src/bin-utils.js"></script> | ||
34 | <style> | 22 | <style> |
35 | body { | 23 | body { |
36 | font-family: Arial, sans-serif; | 24 | font-family: Arial, sans-serif; |
... | @@ -43,13 +31,12 @@ | ... | @@ -43,13 +31,12 @@ |
43 | padding: 0 5px; | 31 | padding: 0 5px; |
44 | margin: 20px 0; | 32 | margin: 20px 0; |
45 | } | 33 | } |
46 | </style> | 34 | input { |
47 | 35 | margin-top: 15px; | |
48 | <script> | 36 | min-width: 450px; |
49 | if (window.location.search === '?flash') { | 37 | padding: 5px; |
50 | videojs.options.techOrder = ['flash']; | ||
51 | } | 38 | } |
52 | </script> | 39 | </style> |
53 | 40 | ||
54 | </head> | 41 | </head> |
55 | <body> | 42 | <body> |
... | @@ -57,18 +44,50 @@ | ... | @@ -57,18 +44,50 @@ |
57 | <p>The video below is an <a href="https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1">HTTP Live Stream</a>. On desktop browsers other than Safari, the HLS plugin will polyfill support for the format on top of the video.js Flash tech.</p> | 44 | <p>The video below is an <a href="https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1">HTTP Live Stream</a>. On desktop browsers other than Safari, the HLS plugin will polyfill support for the format on top of the video.js Flash tech.</p> |
58 | <p>Due to security restrictions in Flash, you will have to load this page over HTTP(S) to see the example in action.</p> | 45 | <p>Due to security restrictions in Flash, you will have to load this page over HTTP(S) to see the example in action.</p> |
59 | </div> | 46 | </div> |
60 | <video id="video" | 47 | |
61 | class="video-js vjs-default-skin" | 48 | <div id="fixture"> |
62 | height="300" | 49 | </div> |
63 | width="600" | 50 | |
64 | controls> | 51 | <form id="load-url"> |
65 | <source | 52 | <label> |
66 | src="http://solutions.brightcove.com/jwhisenant/hls/apple/bipbop/bipbopall.m3u8" | 53 | URL to Load: |
67 | type="application/x-mpegURL"> | 54 | <input id="url-to-load" type="url" value="https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8"> |
68 | <source | 55 | </label> |
69 | src="http://s3.amazonaws.com/_bc_dml/example-content/bipbop-id3/index.m3u8" | 56 | <br> |
70 | type="application/x-mpegURL"> | 57 | <label> |
71 | </video> | 58 | Player Options: |
59 | <input id="player-options" type="text" value='{}'> | ||
60 | </label> | ||
61 | <br> | ||
62 | <label> | ||
63 | URL Content Type: | ||
64 | <input id="url-content-type" type="text" value="application/x-mpegURL"> | ||
65 | </label> | ||
66 | <br> | ||
67 | <label> | ||
68 | Default Caption Track Label (blank for none): | ||
69 | <input id="caption-track" type="text" value=""> | ||
70 | </label> | ||
71 | <br> | ||
72 | <label> | ||
73 | Technology Mode: | ||
74 | <input type="radio" name="technology-mode" value="auto" checked> Auto | ||
75 | <input type="radio" name="technology-mode" value="html"> HTML | ||
76 | <input type="radio" name="technology-mode" value="flash"> Flash | ||
77 | </label> | ||
78 | <br> | ||
79 | <label> | ||
80 | Autoplay: | ||
81 | <input type="radio" name="autoplay" value="on"> On | ||
82 | <input type="radio" name="autoplay" value="off" checked> Off | ||
83 | </label> | ||
84 | |||
85 | <br> | ||
86 | <br> | ||
87 | <button type="submit">Load</button> | ||
88 | </form> | ||
89 | <br> | ||
90 | |||
72 | <section class="stats"> | 91 | <section class="stats"> |
73 | <div class="player-stats"> | 92 | <div class="player-stats"> |
74 | <h2>Player Stats</h2> | 93 | <h2>Player Stats</h2> |
... | @@ -109,9 +128,97 @@ | ... | @@ -109,9 +128,97 @@ |
109 | 128 | ||
110 | <script src="stats.js"></script> | 129 | <script src="stats.js"></script> |
111 | <script> | 130 | <script> |
112 | videojs.options.flash.swf = '../../node_modules/videojs-swf/dist/video-js.swf'; | 131 | function getCheckedValue(name) { |
113 | // initialize the player | 132 | var radios = document.getElementsByName(name); |
114 | var player = videojs('video').ready(function() { | 133 | var value; |
134 | for (var i = 0, length = radios.length; i < length; i++) { | ||
135 | if (radios[i].checked) { | ||
136 | value = radios[i].value; | ||
137 | break; | ||
138 | } | ||
139 | } | ||
140 | return value; | ||
141 | } | ||
142 | |||
143 | function createPlayer(cb) { | ||
144 | if (window.stats_timer) { | ||
145 | clearInterval(window.stats_timer); | ||
146 | } | ||
147 | // dispose of existing player | ||
148 | if(window.player) { | ||
149 | window.player.dispose(); | ||
150 | } | ||
151 | |||
152 | // create video element in the dom | ||
153 | var video = document.createElement('video'); | ||
154 | video.id = 'videojs-contrib-hls-player'; | ||
155 | video.className = 'video-js vjs-default-skin'; | ||
156 | video.setAttribute('controls', true); | ||
157 | video.setAttribute('height', 300); | ||
158 | video.setAttribute('width', 600); | ||
159 | document.querySelector('#fixture').appendChild(video); | ||
160 | var techRadios = document.getElementsByName('technology-mode'); | ||
161 | var techMode = getCheckedValue('technology-mode') || 'auto'; | ||
162 | var autoplay = getCheckedValue('autoplay') || 'off'; | ||
163 | var captionTrack = document.getElementById('caption-track').value || ""; | ||
164 | var url = document.getElementById('url-to-load').value || ""; | ||
165 | var options = {}; | ||
166 | // try to parse options from the form | ||
167 | try { | ||
168 | options = JSON.parse(document.getElementById('player-options').value); | ||
169 | } catch(err) { | ||
170 | console.log("Reseting options to {}, JSON Parse Error:", err); | ||
171 | } | ||
172 | if(typeof options.techOrder === 'undefined') { | ||
173 | if (techMode === 'html') { | ||
174 | options.techOrder = ['html5']; | ||
175 | } else if (techMode === 'flash') { | ||
176 | options.techOrder = ['flash']; | ||
177 | } | ||
178 | } | ||
179 | if(typeof options.autoplay === 'undefined') { | ||
180 | if (autoplay === 'on') { | ||
181 | options.autoplay = true; | ||
182 | } else if (techMode === 'off') { | ||
183 | options.autoplay = false; | ||
184 | } | ||
185 | } | ||
186 | var type = document.getElementById('url-content-type').value; | ||
187 | // use the form data to add a src to the player | ||
188 | try { | ||
189 | window.player = videojs(video.id, options); | ||
190 | if(captionTrack) { | ||
191 | // hackey way to show captions | ||
192 | window.player.on('loadeddata', function(event) { | ||
193 | textTracks = this.textTracks().tracks_; | ||
194 | for(var i = 0; i < textTracks.length; i++) { | ||
195 | if(textTracks[i].label === captionTrack) { | ||
196 | console.log("Found matching track, going to show " + captionTrack); | ||
197 | textTracks[i].mode = "showing"; | ||
198 | break; | ||
199 | } | ||
200 | } | ||
201 | }); | ||
202 | } | ||
203 | window.player.src({ | ||
204 | src: url, | ||
205 | type: type | ||
206 | }); | ||
207 | |||
208 | cb(player); | ||
209 | } catch(err) { | ||
210 | console.log("caught an error trying to create and add src to player:", err); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | function setup(player) { | ||
215 | player.ready(function() { | ||
216 | |||
217 | // ------------ | ||
218 | // Audio Track Switcher | ||
219 | // ------------ | ||
220 | |||
221 | player.controlBar.addChild('AudioTrackButton', {}, 13); | ||
115 | 222 | ||
116 | // ------------ | 223 | // ------------ |
117 | // Player Stats | 224 | // Player Stats |
... | @@ -128,7 +235,7 @@ | ... | @@ -128,7 +235,7 @@ |
128 | currentTimeStat.textContent = player.currentTime().toFixed(1); | 235 | currentTimeStat.textContent = player.currentTime().toFixed(1); |
129 | }); | 236 | }); |
130 | 237 | ||
131 | window.setInterval(function() { | 238 | window.stats_timer = window.setInterval(function() { |
132 | var bufferedText = '', oldStart, oldEnd, i; | 239 | var bufferedText = '', oldStart, oldEnd, i; |
133 | 240 | ||
134 | // buffered | 241 | // buffered |
... | @@ -184,23 +291,18 @@ | ... | @@ -184,23 +291,18 @@ |
184 | videojs.Hls.displayStats(document.querySelector('.switching-stats'), player); | 291 | videojs.Hls.displayStats(document.querySelector('.switching-stats'), player); |
185 | videojs.Hls.displayCues(document.querySelector('.segment-timeline'), player); | 292 | videojs.Hls.displayCues(document.querySelector('.segment-timeline'), player); |
186 | }); | 293 | }); |
187 | |||
188 | // ----------- | ||
189 | // Tech Switch | ||
190 | // ----------- | ||
191 | |||
192 | var techSwitch = document.createElement('a'); | ||
193 | techSwitch.className = 'tech-switch'; | ||
194 | if (player.el().querySelector('video')) { | ||
195 | techSwitch.href = window.location.origin + window.location.pathname + '?flash'; | ||
196 | techSwitch.appendChild(document.createTextNode('Switch to the Flash tech')); | ||
197 | } else { | ||
198 | techSwitch.href = window.location.origin + window.location.pathname; | ||
199 | techSwitch.appendChild(document.createTextNode('Stop forcing Flash')); | ||
200 | } | 294 | } |
201 | 295 | ||
202 | document.body.insertBefore(techSwitch, document.querySelector('.stats')); | 296 | (function(window, videojs) { |
203 | 297 | videojs.options.flash.swf = '../../node_modules/videojs-swf/dist/video-js.swf'; | |
298 | createPlayer(setup); | ||
299 | // hook up the video switcher | ||
300 | document.getElementById('load-url').addEventListener('submit', function(event) { | ||
301 | event.preventDefault(); | ||
302 | createPlayer(setup); | ||
303 | return false; | ||
304 | }); | ||
305 | }(window, window.videojs)); | ||
204 | </script> | 306 | </script> |
205 | </body> | 307 | </body> |
206 | </html> | 308 | </html> | ... | ... |
-
Please register or sign in to post a comment