6fe9fa2f by jrivera

Merge branch 'development'

2 parents c0ab5e76 3e6d9c88
Showing 182 changed files with 1729 additions and 1257 deletions
1 # http://editorconfig.org
2 root = true
3
4 [*]
5 charset = utf-8
6 end_of_line = lf
7 indent_style = space
8 indent_size = 2
9 insert_final_newline = true
10 trim_trailing_whitespace = true
11
12 [*.md]
13 trim_trailing_whitespace = false
1 # OS
2 Thumbs.db
3 ehthumbs.db
4 Desktop.ini
1 .DS_Store 5 .DS_Store
2 dist/* 6 ._*
3 /node_modules/ 7
8 # Editors
4 *~ 9 *~
5 *.iml
6 *.ipr
7 *.iws
8 *.swp 10 *.swp
9 tmp/**.*.swo 11 *.tmproj
12 *.tmproject
13 *.sublime-*
14 .idea/
15 .project/
16 .settings/
17 .vscode/
18
19 # Logs
20 logs
21 *.log
22 npm-debug.log*
23
24 # Dependency directories
25 bower_components/
26 node_modules/
27
28 # Yeoman meta-data
29 .yo-rc.json
30
31 # Build-related directories
32 dist/
33 dist-test/
34 docs/api/
35 es5/
36 tmp
37 test/test-manifests.js
38 test/test-expected.js
......
1 {
2 "curly": true,
3 "eqeqeq": true,
4 "immed": true,
5 "latedef": true,
6 "newcap": true,
7 "noarg": true,
8 "sub": true,
9 "undef": true,
10 "unused": true,
11 "boss": true,
12 "eqnull": true,
13 "node": true,
14
15 "camelcase": true,
16 "nonew": true,
17 "quotmark": "single",
18 "trailing": true,
19 "maxlen": 80
20 }
1 *~ 1 # Intentionally left blank, so that npm does not ignore anything by default,
2 *.iml 2 # but relies on the package.json "files" array to explicitly define what ends
3 *.swp 3 # up in the package.
4 tmp/**
5 test/**
......
1 language: node_js
2 sudo: false 1 sudo: false
2 language: node_js
3 addons:
4 firefox: "latest"
3 node_js: 5 node_js:
4 - "stable" 6 - "stable"
5 install:
6 - npm install -g grunt-cli && npm install
7 notifications: 7 notifications:
8 hipchat: 8 hipchat:
9 rooms: 9 rooms:
...@@ -12,6 +12,7 @@ notifications: ...@@ -12,6 +12,7 @@ notifications:
12 channels: 12 channels:
13 - "chat.freenode.net#videojs" 13 - "chat.freenode.net#videojs"
14 use_notice: true 14 use_notice: true
15 # Set up a virtual screen for Firefox.
15 before_script: 16 before_script:
16 - export DISPLAY=:99.0 17 - export DISPLAY=:99.0
17 - sh -e /etc/init.d/xvfb start 18 - sh -e /etc/init.d/xvfb start
......
1 CHANGELOG 1 CHANGELOG
2 ========= 2 =========
3 3
4 ## HEAD (Unreleased) 4 --------------------
5 * buffer at the current time range end instead of incrementing a variable. ([view](https://github.com/videojs/videojs-contrib-hls/pull/423)) 5
6 ## 2.0.1 (2016-03-11)
7 * First release of the ES6 version of the SourceHandler
8 * All new lint/build/test setup via the [generator-videojs-plugin](https://github.com/videojs/generator-videojs-plugin) project
9
10 --------------------
11
12 ## 1.13.1 (2016-03-04)
13 * Converted from a Tech to a SourceHandler for Video.js 5.x compatibility
14 * Implemented a Media Source Extensions-based playback engine with a Flash-based fallback
15 * Rewrote the Transmuxer and moved it into it's own project [mux.js](https://github.com/videojs/mux.js)
16 * Added support for 608/708 captions
6 17
7 -------------------- 18 --------------------
8 19
......
1 'use strict';
2
3 var
4 basename = require('path').basename,
5 mediaSourcesPath = 'node_modules/videojs-contrib-media-sources/dist/',
6 mediaSourcesDebug = mediaSourcesPath + 'videojs-media-sources.js';
7
8 module.exports = function(grunt) {
9 var pkg = grunt.file.readJSON('package.json');
10
11 // Project configuration.
12 grunt.initConfig({
13 // Metadata.
14 pkg: pkg,
15 banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
16 '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
17 '* Copyright (c) <%= grunt.template.today("yyyy") %> Brightcove;' +
18 ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
19 // Task configuration.
20 clean: {
21 files: ['build', 'dist', 'tmp']
22 },
23 concat: {
24 options: {
25 banner: '<%= banner %>',
26 stripBanners: true
27 },
28 dist: {
29 nonull: true,
30 src: [
31 mediaSourcesDebug,
32 'src/videojs-hls.js',
33 'src/xhr.js',
34 'src/stream.js',
35 'src/m3u8/m3u8-parser.js',
36 'src/playlist.js',
37 'src/playlist-loader.js',
38 'node_modules/pkcs7/dist/pkcs7.unpad.js',
39 'src/decrypter.js'
40 ],
41 dest: 'dist/videojs.hls.js'
42 }
43 },
44 uglify: {
45 options: {
46 banner: '<%= banner %>'
47 },
48 dist: {
49 src: '<%= concat.dist.dest %>',
50 dest: 'dist/videojs.hls.min.js'
51 }
52 },
53 jshint: {
54 gruntfile: {
55 options: {
56 jshintrc: '.jshintrc'
57 },
58 src: 'Gruntfile.js'
59 },
60 src: {
61 options: {
62 jshintrc: 'src/.jshintrc'
63 },
64 src: ['src/**/*.js']
65 },
66 test: {
67 options: {
68 jshintrc: 'test/.jshintrc'
69 },
70 src: ['test/**/*.js',
71 '!test/tsSegment.js',
72 '!test/fixtures/*.js',
73 '!test/manifest/**',
74 '!test/muxer/**',
75 '!test/switcher/**']
76 }
77 },
78 connect: {
79 dev: {
80 options: {
81 hostname: '*',
82 port: 9999,
83 keepalive: true
84 }
85 },
86 test: {
87 options: {
88 hostname: '*',
89 port: 9999
90 }
91 }
92 },
93 open : {
94 dev : {
95 path: 'http://127.0.0.1:<%= connect.dev.options.port %>/example.html',
96 app: 'Google Chrome'
97 }
98 },
99 watch: {
100 build: {
101 files: '<%= concat.dist.src %>',
102 tasks: ['clean', 'concat', 'uglify']
103 },
104 gruntfile: {
105 files: '<%= jshint.gruntfile.src %>',
106 tasks: ['jshint:gruntfile']
107 },
108 src: {
109 files: '<%= jshint.src.src %>',
110 tasks: ['jshint:src', 'test']
111 },
112 test: {
113 files: '<%= jshint.test.src %>',
114 tasks: ['jshint:test', 'test']
115 }
116 },
117 concurrent: {
118 dev: {
119 tasks: ['connect', 'open', 'watch'],
120 options: {
121 logConcurrentOutput: true
122 }
123 }
124 },
125 version: {
126 project: {
127 src: ['package.json']
128 }
129 },
130 'github-release': {
131 options: {
132 repository: 'videojs/videojs-contrib-hls',
133 auth: {
134 user: process.env.VJS_GITHUB_USER,
135 password: process.env.VJS_GITHUB_TOKEN
136 },
137 release: {
138 'tag_name': 'v' + pkg.version,
139 name: pkg.version,
140 body: require('chg').find(pkg.version).changesRaw
141 }
142 },
143 files: {
144 'dist': ['videojs.hls.min.js']
145 }
146 },
147 karma: {
148 options: {
149 frameworks: ['qunit']
150 },
151
152 saucelabs: {
153 configFile: 'test/karma.conf.js',
154 autoWatch: true
155 },
156
157 dev: {
158 browsers: ['Chrome', 'Safari', 'Firefox',
159 'Opera', 'IE', 'PhantomJS', 'ChromeCanary'],
160 configFile: 'test/localkarma.conf.js',
161 autoWatch: true
162 },
163
164 chromecanary: {
165 options: {
166 browsers: ['ChromeCanary'],
167 configFile: 'test/localkarma.conf.js',
168 autoWatch: true
169 }
170 },
171
172 phantomjs: {
173 options: {
174 browsers: ['PhantomJS'],
175 configFile: 'test/localkarma.conf.js',
176 autoWatch: true
177 }
178 },
179
180 opera: {
181 options: {
182 browsers: ['Opera'],
183 configFile: 'test/localkarma.conf.js',
184 autoWatch: true
185 }
186 },
187
188 chrome: {
189 options: {
190 browsers: ['Chrome'],
191 configFile: 'test/localkarma.conf.js',
192 autoWatch: true
193 }
194 },
195
196 safari: {
197 options: {
198 browsers: ['Safari'],
199 configFile: 'test/localkarma.conf.js',
200 autoWatch: true
201 }
202 },
203
204 firefox: {
205 options: {
206 browsers: ['Firefox'],
207 configFile: 'test/localkarma.conf.js',
208 autoWatch: true
209 }
210 },
211
212 ie: {
213 options: {
214 browsers: ['IE'],
215 configFile: 'test/localkarma.conf.js',
216 autoWatch: true
217 }
218 },
219
220 ci: {
221 configFile: 'test/karma.conf.js',
222 autoWatch: false
223 }
224 },
225 protractor: {
226 options: {
227 configFile: 'test/functional/protractor.config.js',
228 webdriverManagerUpdate: process.env.TRAVIS ? false : true
229 },
230
231 chrome: {
232 options: {
233 args: {
234 capabilities: {
235 browserName: 'chrome'
236 }
237 }
238 }
239 },
240
241 firefox: {
242 options: {
243 args: {
244 capabilities: {
245 browserName: 'firefox'
246 }
247 }
248 }
249 },
250
251 safari: {
252 options: {
253 args: {
254 capabilities: {
255 browserName: 'safari'
256 }
257 }
258 }
259 },
260
261 ie: {
262 options: {
263 args: {
264 capabilities: {
265 browserName: 'internet explorer'
266 }
267 }
268 }
269 },
270
271 saucelabs:{}
272 }
273 });
274
275 // These plugins provide necessary tasks.
276 grunt.loadNpmTasks('grunt-karma');
277 grunt.loadNpmTasks('grunt-contrib-clean');
278 grunt.loadNpmTasks('grunt-contrib-concat');
279 grunt.loadNpmTasks('grunt-contrib-uglify');
280 grunt.loadNpmTasks('grunt-contrib-jshint');
281 grunt.loadNpmTasks('grunt-contrib-watch');
282 grunt.loadNpmTasks('grunt-contrib-connect');
283 grunt.loadNpmTasks('grunt-open');
284 grunt.loadNpmTasks('grunt-concurrent');
285 grunt.loadNpmTasks('grunt-contrib-watch');
286 grunt.loadNpmTasks('grunt-github-releaser');
287 grunt.loadNpmTasks('grunt-version');
288 grunt.loadNpmTasks('grunt-protractor-runner');
289 grunt.loadNpmTasks('chg');
290
291
292 grunt.registerTask('manifests-to-js', 'Wrap the test fixtures and output' +
293 ' so they can be loaded in a browser',
294 function() {
295 var
296 jsManifests = 'window.manifests = {\n',
297 jsExpected = 'window.expected = {\n';
298 grunt.file.recurse('test/manifest/',
299 function(abspath, root, sub, filename) {
300 if ((/\.m3u8$/).test(abspath)) {
301
302 // translate this manifest
303 jsManifests += ' \'' + basename(filename, '.m3u8') + '\': ' +
304 grunt.file.read(abspath)
305 .split(/\r\n|\n/)
306
307 // quote and concatenate
308 .map(function(line) {
309 return ' \'' + line + '\\n\' +\n';
310 }).join('')
311
312 // strip leading spaces and the trailing '+'
313 .slice(4, -3);
314 jsManifests += ',\n';
315 }
316
317 if ((/\.js$/).test(abspath)) {
318
319 // append the expected parse
320 jsExpected += ' "' + basename(filename, '.js') + '": ' +
321 grunt.file.read(abspath) + ',\n';
322 }
323 });
324
325 // clean up and close the objects
326 jsManifests = jsManifests.slice(0, -2);
327 jsManifests += '\n};\n';
328 jsExpected = jsExpected.slice(0, -2);
329 jsExpected += '\n};\n';
330
331 // write out the manifests
332 grunt.file.write('tmp/manifests.js', jsManifests);
333 grunt.file.write('tmp/expected.js', jsExpected);
334 });
335
336 // Launch a Development Environment
337 grunt.registerTask('dev', 'Launching Dev Environment', 'concurrent:dev');
338
339 grunt.registerTask('build',
340 ['clean',
341 'concat',
342 'uglify']);
343
344 // Default task.
345 grunt.registerTask('default',
346 ['test',
347 'build']);
348
349 // The test task will run `karma:saucelabs` when running in travis,
350 // otherwise, it'll default to running karma in chrome.
351 // You can specify which browsers to build with by using grunt-style arguments
352 // or separating them with a comma:
353 // grunt test:chrome:firefox # grunt-style
354 // grunt test:chrome,firefox # comma-separated
355 grunt.registerTask('test', function() {
356 var tasks = this.args;
357
358 grunt.task.run(['jshint', 'manifests-to-js']);
359
360 if (process.env.TRAVIS) {
361 if (process.env.TRAVIS_PULL_REQUEST === 'false') {
362 grunt.task.run(['karma:saucelabs']);
363 grunt.task.run(['connect:test', 'protractor:saucelabs']);
364 } else {
365 grunt.task.run(['karma:firefox']);
366 }
367 } else {
368 if (tasks.length === 0) {
369 tasks.push('chrome');
370 }
371 if (tasks.length === 1) {
372 tasks = tasks[0].split(',');
373 }
374 tasks = tasks.reduce(function(acc, el) {
375 acc.push('karma:' + el);
376 if (/chrome|firefox|safari|ie/.test(el)) {
377 acc.push('protractor:' + el);
378 }
379 return acc;
380 }, ['connect:test']);
381
382 grunt.task.run(tasks);
383 }
384 });
385 };
...@@ -4,6 +4,33 @@ Play back HLS with video.js, even where it's not natively supported. ...@@ -4,6 +4,33 @@ Play back HLS with video.js, even where it's not natively supported.
4 4
5 [![Build Status](https://travis-ci.org/videojs/videojs-contrib-hls.svg?branch=master)](https://travis-ci.org/videojs/videojs-contrib-hls) 5 [![Build Status](https://travis-ci.org/videojs/videojs-contrib-hls.svg?branch=master)](https://travis-ci.org/videojs/videojs-contrib-hls)
6 6
7 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
8 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9 **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
10
11 - [Getting Started](#getting-started)
12 - [Documentation](#documentation)
13 - [Options](#options)
14 - [withCredentials](#withcredentials)
15 - [Runtime Properties](#runtime-properties)
16 - [hls.playlists.master](#hlsplaylistsmaster)
17 - [hls.playlists.media](#hlsplaylistsmedia)
18 - [hls.segmentXhrTime](#hlssegmentxhrtime)
19 - [hls.bandwidth](#hlsbandwidth)
20 - [hls.bytesReceived](#hlsbytesreceived)
21 - [hls.selectPlaylist](#hlsselectplaylist)
22 - [Events](#events)
23 - [loadedmetadata](#loadedmetadata)
24 - [loadedplaylist](#loadedplaylist)
25 - [mediachange](#mediachange)
26 - [In-Band Metadata](#in-band-metadata)
27 - [Hosting Considerations](#hosting-considerations)
28 - [Testing](#testing)
29 - [Release History](#release-history)
30
31 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
32
33
7 ## Getting Started 34 ## Getting Started
8 Download 35 Download
9 [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls/releases) 36 [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls/releases)
......
...@@ -2,31 +2,8 @@ ...@@ -2,31 +2,8 @@
2 <html> 2 <html>
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title>video.js HLS Plugin Example</title> 5 <title>videojs-contrib-hls Demo</title>
6 6 <link href="/node_modules/video.js/dist/video-js.css" rel="stylesheet">
7 <link href="node_modules/video.js/dist/video-js.css" rel="stylesheet">
8
9 <!-- video.js -->
10 <script src="node_modules/video.js/dist/video.js"></script>
11
12 <!-- Media Sources plugin -->
13 <script src="node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
14
15 <!-- HLS plugin -->
16 <script src="src/videojs-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
25 <script src="node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
26 <script src="src/decrypter.js"></script>
27
28 <script src="src/bin-utils.js"></script>
29
30 <style> 7 <style>
31 body { 8 body {
32 font-family: Arial, sans-serif; 9 font-family: Arial, sans-serif;
...@@ -52,14 +29,8 @@ ...@@ -52,14 +29,8 @@
52 <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> 29 <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>
53 <p>Due to security restrictions in Flash, you will have to load this page over HTTP(S) to see the example in action.</p> 30 <p>Due to security restrictions in Flash, you will have to load this page over HTTP(S) to see the example in action.</p>
54 </div> 31 </div>
55 <video id="video" 32 <video id="videojs-contrib-hls-player" class="video-js vjs-default-skin" controls>
56 class="video-js vjs-default-skin" 33 <source src="http://solutions.brightcove.com/jwhisenant/hls/apple/bipbop/bipbopall.m3u8" type="application/x-mpegURL">
57 height="300"
58 width="600"
59 controls>
60 <source
61 src="http://solutions.brightcove.com/jwhisenant/hls/apple/bipbop/bipbopall.m3u8"
62 type="application/x-mpegURL">
63 </video> 34 </video>
64 35
65 <form id=load-url> 36 <form id=load-url>
...@@ -69,23 +40,28 @@ ...@@ -69,23 +40,28 @@
69 </label> 40 </label>
70 <button type=submit>Load</button> 41 <button type=submit>Load</button>
71 </form> 42 </form>
43 <ul>
44 <li><a href="/test/">Run unit tests in browser.</a></li>
45 <li><a href="/docs/api/">Read generated docs.</a></li>
46 </ul>
72 47
48 <script src="/node_modules/video.js/dist/video.js"></script>
49 <script src="/dist/videojs-contrib-hls.js"></script>
73 <script> 50 <script>
74 videojs.options.flash.swf = 'node_modules/videojs-swf/dist/video-js.swf'; 51 (function(window, videojs) {
75 // initialize the player 52 var player = window.player = videojs('videojs-contrib-hls-player');
76 var player = videojs('video'); 53 // hook up the video switcher
77 54 var loadUrl = document.getElementById('load-url');
78 // hook up the video switcher 55 var url = document.getElementById('url');
79 var loadUrl = document.getElementById('load-url'); 56 loadUrl.addEventListener('submit', function(event) {
80 var url = document.getElementById('url'); 57 event.preventDefault();
81 loadUrl.addEventListener('submit', function(event) { 58 player.src({
82 event.preventDefault(); 59 src: url.value,
83 player.src({ 60 type: 'application/x-mpegURL'
84 src: url.value, 61 });
85 type: 'application/x-mpegURL' 62 return false;
86 }); 63 });
87 return false; 64 }(window, window.videojs));
88 });
89 </script> 65 </script>
90 </body> 66 </body>
91 </html> 67 </html>
......
1 { 1 {
2 "name": "videojs-contrib-hls", 2 "name": "videojs-contrib-hls",
3 "version": "1.3.11", 3 "version": "2.0.1",
4 "description": "Play back HLS with video.js, even where it's not natively supported",
5 "main": "es5/videojs-contrib-hls.js",
4 "engines": { 6 "engines": {
5 "node": ">= 0.10.12" 7 "node": ">= 0.10.12"
6 }, 8 },
...@@ -8,47 +10,120 @@ ...@@ -8,47 +10,120 @@
8 "type": "git", 10 "type": "git",
9 "url": "git@github.com:videojs/videojs-contrib-hls.git" 11 "url": "git@github.com:videojs/videojs-contrib-hls.git"
10 }, 12 },
11 "license": "Apache-2.0",
12 "scripts": { 13 "scripts": {
13 "test": "grunt test", 14 "prebuild": "npm run clean",
14 "prepublish": "if [ -z \"$TRAVIS\" ]; then grunt; fi" 15 "build": "npm-run-all -p build:*",
16 "build:js": "npm-run-all build:js:babel build:js:browserify build:js:bannerize build:js:uglify",
17 "build:js:babel": "babel src -d es5",
18 "build:js:bannerize": "bannerize dist/videojs-contrib-hls.js --banner=scripts/banner.ejs",
19 "build:js:browserify": "browserify . -s videojs-contrib-hls -o dist/videojs-contrib-hls.js",
20 "build:js:uglify": "uglifyjs dist/videojs-contrib-hls.js --comments --mangle --compress -o dist/videojs-contrib-hls.min.js",
21 "build:test": "npm-run-all build:test:manifest build:test:js",
22 "build:test:js": "node scripts/build-test.js",
23 "build:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.build();\"",
24 "clean": "npm-run-all -p clean:*",
25 "clean:build": "node -e \"var s=require('shelljs'),d=['dist','dist-test','es5'];s.rm('-rf',d);s.mkdir('-p',d);\"",
26 "clean:test": "node -e \"var b=require('./scripts/manifest-data.js'); b.clean();\"",
27 "docs": "npm-run-all docs:*",
28 "docs:api": "jsdoc src -r -d docs/api",
29 "docs:toc": "doctoc README.md",
30 "lint": "vjsstandard",
31 "prestart": "npm-run-all docs build",
32 "start": "npm-run-all -p start:* watch:*",
33 "start:serve": "babel-node scripts/server.js",
34 "pretest": "npm-run-all lint build",
35 "test": "karma start test/karma/detected.js",
36 "test:chrome": "npm run pretest && karma start test/karma/chrome.js",
37 "test:firefox": "npm run pretest && karma start test/karma/firefox.js",
38 "test:ie": "npm run pretest && karma start test/karma/ie.js",
39 "test:safari": "npm run pretest && karma start test/karma/safari.js",
40 "preversion": "npm test",
41 "version": "npm run build",
42 "watch": "npm-run-all -p watch:*",
43 "watch:js": "watchify src/videojs-contrib-hls.js -t babelify -v -o dist/videojs-contrib-hls.js",
44 "watch:test": "npm-run-all -p watch:test:*",
45 "watch:test:js": "node scripts/watch-test.js",
46 "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"",
47 "prepublish": "npm run build"
15 }, 48 },
16 "keywords": [ 49 "keywords": [
17 "videojs", 50 "videojs",
18 "videojs-plugin" 51 "videojs-plugin"
19 ], 52 ],
20 "devDependencies": { 53 "author": "Brightcove, Inc",
21 "chg": "^0.2.0", 54 "license": "Apache-2.0",
22 "grunt": "^0.4.5", 55 "browserify": {
23 "grunt-concurrent": "0.4.3", 56 "transform": [
24 "grunt-contrib-clean": "~0.4.0", 57 "browserify-shim"
25 "grunt-contrib-concat": "~0.3.0", 58 ]
26 "grunt-contrib-connect": "~0.6.0", 59 },
27 "grunt-contrib-jshint": "~0.6.0", 60 "browserify-shim": {
28 "grunt-contrib-uglify": "~0.2.0", 61 "qunit": "global:QUnit",
29 "grunt-contrib-watch": "~0.4.0", 62 "sinon": "global:sinon",
30 "grunt-github-releaser": "^0.1.17", 63 "video.js": "global:videojs"
31 "grunt-karma": "~0.6.2",
32 "grunt-open": "0.2.3",
33 "grunt-protractor-runner": "forbesjo/grunt-protractor-runner.git#webdriverManagerUpdate",
34 "grunt-shell": "0.6.1",
35 "grunt-version": "^1.0.0",
36 "karma": "~0.10.0",
37 "karma-chrome-launcher": "~0.1.2",
38 "karma-firefox-launcher": "~0.1.3",
39 "karma-ie-launcher": "~0.1.1",
40 "karma-opera-launcher": "~0.1.0",
41 "karma-phantomjs-launcher": "^0.1.4",
42 "karma-qunit": "~0.1.1",
43 "karma-safari-launcher": "~0.1.1",
44 "karma-sauce-launcher": "~0.1.8",
45 "qunitjs": "^1.18.0",
46 "sinon": "1.10.2",
47 "video.js": "^5.2.1"
48 }, 64 },
65 "vjsstandard": {
66 "ignore": [
67 "dist",
68 "dist-test",
69 "docs",
70 "es5",
71 "test/karma",
72 "scripts",
73 "utils",
74 "test/test-manifests.js",
75 "test/test-expected.js",
76 "src/playlist-loader.js"
77 ]
78 },
79 "files": [
80 "CONTRIBUTING.md",
81 "dist-test/",
82 "dist/",
83 "docs/",
84 "es5/",
85 "index.html",
86 "scripts/",
87 "src/",
88 "test/",
89 "utils/"
90 ],
49 "dependencies": { 91 "dependencies": {
50 "pkcs7": "^0.2.2", 92 "pkcs7": "^0.2.2",
51 "videojs-contrib-media-sources": "^2.5.0", 93 "video.js": "^5.2.1",
94 "videojs-contrib-media-sources": "^3.0.0",
52 "videojs-swf": "^5.0.0" 95 "videojs-swf": "^5.0.0"
96 },
97 "devDependencies": {
98 "babel": "^5.8.0",
99 "babelify": "^6.0.0",
100 "bannerize": "^1.0.0",
101 "browserify": "^11.0.0",
102 "browserify-shim": "^3.0.0",
103 "connect": "^3.4.0",
104 "cowsay": "^1.1.0",
105 "doctoc": "^0.15.0",
106 "glob": "^6.0.3",
107 "global": "^4.3.0",
108 "jsdoc": "^3.4.0",
109 "karma": "^0.13.0",
110 "karma-browserify": "^4.4.0",
111 "karma-chrome-launcher": "^0.2.0",
112 "karma-detect-browsers": "^2.0.0",
113 "karma-firefox-launcher": "^0.1.0",
114 "karma-ie-launcher": "^0.2.0",
115 "karma-qunit": "^0.1.9",
116 "karma-safari-launcher": "^0.1.0",
117 "lodash-compat": "^3.10.0",
118 "minimist": "^1.2.0",
119 "npm-run-all": "^1.2.0",
120 "portscanner": "^1.0.0",
121 "qunitjs": "^1.18.0",
122 "serve-static": "^1.10.0",
123 "shelljs": "^0.5.3",
124 "sinon": "1.10.2",
125 "uglify-js": "^2.5.0",
126 "videojs-standard": "^4.0.0",
127 "watchify": "^3.6.0"
53 } 128 }
54 } 129 }
......
1 /**
2 * <%- pkg.name %>
3 * @version <%- pkg.version %>
4 * @copyright <%- date.getFullYear() %> <%- pkg.author %>
5 * @license <%- pkg.license %>
6 */
1 var browserify = require('browserify');
2 var fs = require('fs');
3 var glob = require('glob');
4
5 glob('test/**/*.test.js', function(err, files) {
6 browserify(files)
7 .transform('babelify')
8 .bundle()
9 .pipe(fs.createWriteStream('dist-test/videojs-contrib-hls.js'));
10 });
1 var fs = require('fs');
2 var path = require('path');
3
4 var basePath = path.resolve(__dirname, '..');
5 var testDataDir = path.join(basePath,'test');
6 var manifestDir = path.join(basePath, 'utils', 'manifest');
7 var manifestFilepath = path.join(testDataDir, 'test-manifests.js');
8 var expectedFilepath = path.join(testDataDir, 'test-expected.js');
9
10 var build = function() {
11 var manifests = 'export default {\n';
12 var expected = 'export default {\n';
13
14 var files = fs.readdirSync(manifestDir);
15 while (files.length > 0) {
16 var file = path.resolve(manifestDir, files.shift());
17 var extname = path.extname(file);
18
19 if (extname === '.m3u8') {
20 // translate this manifest
21 manifests += ' \'' + path.basename(file, '.m3u8') + '\': ';
22 manifests += fs.readFileSync(file, 'utf8')
23 .split(/\r\n|\n/)
24 // quote and concatenate
25 .map(function(line) {
26 return ' \'' + line + '\\n\' +\n';
27 }).join('')
28 // strip leading spaces and the trailing '+'
29 .slice(4, -3);
30 manifests += ',\n';
31 } else if (extname === '.js') {
32 // append the expected parse
33 expected += ' "' + path.basename(file, '.js') + '": ';
34 expected += fs.readFileSync(file, 'utf8');
35 expected += ',\n';
36 } else {
37 console.log('Unknown file ' + file + ' found in manifest dir ' + manifestDir);
38 }
39
40 }
41
42 // clean up and close the objects
43 manifests = manifests.slice(0, -2);
44 manifests += '\n};\n';
45 expected = expected.slice(0, -2);
46 expected += '\n};\n';
47
48 fs.writeFileSync(manifestFilepath, manifests);
49 fs.writeFileSync(expectedFilepath, expected);
50 console.log('Wrote test data file ' + manifestFilepath);
51 console.log('Wrote test data file ' + expectedFilepath);
52 };
53
54 var watch = function() {
55 build();
56 fs.watch(manifestDir, function(event, filename) {
57 console.log('files in manifest dir were changed rebuilding manifest data');
58 build();
59 });
60 };
61
62 var clean = function() {
63 try {
64 fs.unlinkSync(manifestFilepath);
65 } catch(e) {
66 console.log(e);
67 }
68 try {
69 fs.unlinkSync(expectedFilepath);
70 } catch(e) {
71 console.log(e);
72 }
73 }
74
75 module.exports = {
76 build: build,
77 watch: watch,
78 clean: clean
79 };
1 import connect from 'connect';
2 import cowsay from 'cowsay';
3 import path from 'path';
4 import portscanner from 'portscanner';
5 import serveStatic from 'serve-static';
6
7 // Configuration for the server.
8 const PORT = 9999;
9 const MAX_PORT = PORT + 100;
10 const HOST = '127.0.0.1';
11
12 const app = connect();
13
14 const verbs = [
15 'Chewing the cud',
16 'Grazing',
17 'Mooing',
18 'Lowing',
19 'Churning the cream'
20 ];
21
22 app.use(serveStatic(path.join(__dirname, '..')));
23
24 portscanner.findAPortNotInUse(PORT, MAX_PORT, HOST, (error, port) => {
25 if (error) {
26 throw error;
27 }
28
29 process.stdout.write(cowsay.say({
30 text: `${verbs[Math.floor(Math.random() * 5)]} on ${HOST}:${port}`
31 }) + '\n\n');
32
33 app.listen(port);
34 });
1 var browserify = require('browserify');
2 var fs = require('fs');
3 var glob = require('glob');
4 var watchify = require('watchify');
5
6 glob('test/**/*.test.js', function(err, files) {
7 var b = browserify(files, {
8 cache: {},
9 packageCache: {},
10 plugin: [watchify]
11 }).transform('babelify');
12
13 var bundle = function() {
14 b.bundle().pipe(fs.createWriteStream('dist-test/videojs-contrib-hls.js'));
15 };
16
17 b.on('log', function(msg) {
18 process.stdout.write(msg + '\n');
19 });
20
21 b.on('update', bundle);
22 bundle();
23 });
1 {
2 "curly": true,
3 "eqeqeq": true,
4 "globals": {
5 "console": true
6 },
7 "immed": true,
8 "latedef": true,
9 "newcap": true,
10 "noarg": true,
11 "sub": true,
12 "undef": true,
13 "unused": true,
14 "boss": true,
15 "eqnull": true,
16 "browser": true
17 }
1 (function(window) { 1 const textRange = function(range, i) {
2 var textRange = function(range, i) { 2 return range.start(i) + '-' + range.end(i);
3 return range.start(i) + '-' + range.end(i); 3 };
4 }; 4
5 var module = { 5 const formatHexString = function(e, i) {
6 hexDump: function(data) { 6 let value = e.toString(16);
7 var 7
8 bytes = Array.prototype.slice.call(data), 8 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
9 step = 16, 9 };
10 formatHexString = function(e, i) { 10 const formatAsciiString = function(e) {
11 var value = e.toString(16); 11 if (e >= 0x20 && e < 0x7e) {
12 return "00".substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 12 return String.fromCharCode(e);
13 }, 13 }
14 formatAsciiString = function(e) { 14 return '.';
15 if (e >= 0x20 && e < 0x7e) { 15 };
16 return String.fromCharCode(e); 16
17 } 17 const utils = {
18 return '.'; 18 hexDump(data) {
19 }, 19 let bytes = Array.prototype.slice.call(data);
20 result = '', 20 let step = 16;
21 hex, 21 let result = '';
22 ascii; 22 let hex;
23 for (var j = 0; j < bytes.length / step; j++) { 23 let ascii;
24 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 24
25 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 25 for (let j = 0; j < bytes.length / step; j++) {
26 result += hex + ' ' + ascii + '\n'; 26 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
27 } 27 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
28 return result; 28 result += hex + ' ' + ascii + '\n';
29 }, 29 }
30 tagDump: function(tag) { 30 return result;
31 return module.hexDump(tag.bytes); 31 },
32 }, 32 tagDump(tag) {
33 textRanges: function(ranges) { 33 return utils.hexDump(tag.bytes);
34 var result = '', i; 34 },
35 for (i = 0; i < ranges.length; i++) { 35 textRanges(ranges) {
36 result += textRange(ranges, i) + ' '; 36 let result = '';
37 } 37 let i;
38 return result; 38
39 for (i = 0; i < ranges.length; i++) {
40 result += textRange(ranges, i) + ' ';
39 } 41 }
40 }; 42 return result;
43 }
44 };
41 45
42 window.videojs.Hls.utils = module; 46 export default utils;
43 })(this);
......
1 /*
2 * aes.js
3 *
4 * This file contains an adaptation of the AES decryption algorithm
5 * from the Standford Javascript Cryptography Library. That work is
6 * covered by the following copyright and permissions notice:
7 *
8 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions are
13 * met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above
19 * copyright notice, this list of conditions and the following
20 * disclaimer in the documentation and/or other materials provided
21 * with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
33 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of the authors.
38 */
39
40 /**
41 * Expand the S-box tables.
42 *
43 * @private
44 */
45 const precompute = function() {
46 let tables = [[[], [], [], [], []], [[], [], [], [], []]];
47 let encTable = tables[0];
48 let decTable = tables[1];
49 let sbox = encTable[4];
50 let sboxInv = decTable[4];
51 let i;
52 let x;
53 let xInv;
54 let d = [];
55 let th = [];
56 let x2;
57 let x4;
58 let x8;
59 let s;
60 let tEnc;
61 let tDec;
62
63 // Compute double and third tables
64 for (i = 0; i < 256; i++) {
65 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
66 }
67
68 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
69 // Compute sbox
70 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
71 s = s >> 8 ^ s & 255 ^ 99;
72 sbox[x] = s;
73 sboxInv[s] = x;
74
75 // Compute MixColumns
76 x8 = d[x4 = d[x2 = d[x]]];
77 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
78 tEnc = d[s] * 0x101 ^ s * 0x1010100;
79
80 for (i = 0; i < 4; i++) {
81 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
82 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
83 }
84 }
85
86 // Compactify. Considerable speedup on Firefox.
87 for (i = 0; i < 5; i++) {
88 encTable[i] = encTable[i].slice(0);
89 decTable[i] = decTable[i].slice(0);
90 }
91 return tables;
92 };
93 let aesTables = null;
94
95 /**
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.
98 *
99 * @constructor
100 * @param key {Array} The key as an array of 4, 6 or 8 words.
101 */
102 export default class AES {
103 constructor(key) {
104 /**
105 * The expanded S-box and inverse S-box tables. These will be computed
106 * on the client so that we don't have to send them down the wire.
107 *
108 * There are two tables, _tables[0] is for encryption and
109 * _tables[1] is for decryption.
110 *
111 * The first 4 sub-tables are the expanded S-box with MixColumns. The
112 * last (_tables[01][4]) is the S-box itself.
113 *
114 * @private
115 */
116 // if we have yet to precompute the S-box tables
117 // do so now
118 if (!aesTables) {
119 aesTables = precompute();
120 }
121 // then make a copy of that object for use
122 this._tables = [[aesTables[0][0].slice(),
123 aesTables[0][1].slice(),
124 aesTables[0][2].slice(),
125 aesTables[0][3].slice(),
126 aesTables[0][4].slice()],
127 [aesTables[1][0].slice(),
128 aesTables[1][1].slice(),
129 aesTables[1][2].slice(),
130 aesTables[1][3].slice(),
131 aesTables[1][4].slice()]];
132 let i;
133 let j;
134 let tmp;
135 let encKey;
136 let decKey;
137 let sbox = this._tables[0][4];
138 let decTable = this._tables[1];
139 let keyLen = key.length;
140 let rcon = 1;
141
142 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
143 throw new Error('Invalid aes key size');
144 }
145
146 encKey = key.slice(0);
147 decKey = [];
148 this._key = [encKey, decKey];
149
150 // schedule encryption keys
151 for (i = keyLen; i < 4 * keyLen + 28; i++) {
152 tmp = encKey[i - 1];
153
154 // apply sbox
155 if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
156 tmp = sbox[tmp >>> 24] << 24 ^
157 sbox[tmp >> 16 & 255] << 16 ^
158 sbox[tmp >> 8 & 255] << 8 ^
159 sbox[tmp & 255];
160
161 // shift rows and add rcon
162 if (i % keyLen === 0) {
163 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
164 rcon = rcon << 1 ^ (rcon >> 7) * 283;
165 }
166 }
167
168 encKey[i] = encKey[i - keyLen] ^ tmp;
169 }
170
171 // schedule decryption keys
172 for (j = 0; i; j++, i--) {
173 tmp = encKey[j & 3 ? i : i - 4];
174 if (i <= 4 || j < 4) {
175 decKey[j] = tmp;
176 } else {
177 decKey[j] = decTable[0][sbox[tmp >>> 24 ]] ^
178 decTable[1][sbox[tmp >> 16 & 255]] ^
179 decTable[2][sbox[tmp >> 8 & 255]] ^
180 decTable[3][sbox[tmp & 255]];
181 }
182 }
183 }
184
185 /**
186 * Decrypt 16 bytes, specified as four 32-bit words.
187 * @param encrypted0 {number} the first word to decrypt
188 * @param encrypted1 {number} the second word to decrypt
189 * @param encrypted2 {number} the third word to decrypt
190 * @param encrypted3 {number} the fourth word to decrypt
191 * @param out {Int32Array} the array to write the decrypted words
192 * into
193 * @param offset {number} the offset into the output array to start
194 * writing results
195 * @return {Array} The plaintext.
196 */
197 decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
198 let key = this._key[1];
199 // state variables a,b,c,d are loaded with pre-whitened data
200 let a = encrypted0 ^ key[0];
201 let b = encrypted3 ^ key[1];
202 let c = encrypted2 ^ key[2];
203 let d = encrypted1 ^ key[3];
204 let a2;
205 let b2;
206 let c2;
207
208 // key.length === 2 ?
209 let nInnerRounds = key.length / 4 - 2;
210 let i;
211 let kIndex = 4;
212 let table = this._tables[1];
213
214 // load up the tables
215 let table0 = table[0];
216 let table1 = table[1];
217 let table2 = table[2];
218 let table3 = table[3];
219 let sbox = table[4];
220
221 // Inner rounds. Cribbed from OpenSSL.
222 for (i = 0; i < nInnerRounds; i++) {
223 a2 = table0[a >>> 24] ^
224 table1[b >> 16 & 255] ^
225 table2[c >> 8 & 255] ^
226 table3[d & 255] ^
227 key[kIndex];
228 b2 = table0[b >>> 24] ^
229 table1[c >> 16 & 255] ^
230 table2[d >> 8 & 255] ^
231 table3[a & 255] ^
232 key[kIndex + 1];
233 c2 = table0[c >>> 24] ^
234 table1[d >> 16 & 255] ^
235 table2[a >> 8 & 255] ^
236 table3[b & 255] ^
237 key[kIndex + 2];
238 d = table0[d >>> 24] ^
239 table1[a >> 16 & 255] ^
240 table2[b >> 8 & 255] ^
241 table3[c & 255] ^
242 key[kIndex + 3];
243 kIndex += 4;
244 a = a2; b = b2; c = c2;
245 }
246
247 // Last round.
248 for (i = 0; i < 4; i++) {
249 out[(3 & -i) + offset] =
250 sbox[a >>> 24] << 24 ^
251 sbox[b >> 16 & 255] << 16 ^
252 sbox[c >> 8 & 255] << 8 ^
253 sbox[d & 255] ^
254 key[kIndex++];
255 a2 = a; a = b; b = c; c = d; d = a2;
256 }
257 }
258 }
1 import Stream from '../stream';
2
3 /**
4 * A wrapper around the Stream class to use setTiemout
5 * and run stream "jobs" Asynchronously
6 */
7 export default class AsyncStream extends Stream {
8 constructor() {
9 super(Stream);
10 this.jobs = [];
11 this.delay = 1;
12 this.timeout_ = null;
13 }
14 processJob_() {
15 this.jobs.shift()();
16 if (this.jobs.length) {
17 this.timeout_ = setTimeout(this.processJob_.bind(this),
18 this.delay);
19 } else {
20 this.timeout_ = null;
21 }
22 }
23 push(job) {
24 this.jobs.push(job);
25 if (!this.timeout_) {
26 this.timeout_ = setTimeout(this.processJob_.bind(this),
27 this.delay);
28 }
29 }
30 }
31
1 /*
2 * decrypter.js
3 *
4 * An asynchronous implementation of AES-128 CBC decryption with
5 * PKCS#7 padding.
6 */
7
8 import AES from './aes';
9 import AsyncStream from './async-stream';
10 import {unpad} from 'pkcs7';
11
12 /**
13 * Convert network-order (big-endian) bytes into their little-endian
14 * representation.
15 */
16 const ntoh = function(word) {
17 return (word << 24) |
18 ((word & 0xff00) << 8) |
19 ((word & 0xff0000) >> 8) |
20 (word >>> 24);
21 };
22
23 /* eslint-disable max-len */
24 /**
25 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
26 * @param encrypted {Uint8Array} the encrypted bytes
27 * @param key {Uint32Array} the bytes of the decryption key
28 * @param initVector {Uint32Array} the initialization vector (IV) to
29 * use for the first round of CBC.
30 * @return {Uint8Array} the decrypted bytes
31 *
32 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
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
35 */
36 /* eslint-enable max-len */
37 export const decrypt = function(encrypted, key, initVector) {
38 // word-level access to the encrypted bytes
39 let encrypted32 = new Int32Array(encrypted.buffer,
40 encrypted.byteOffset,
41 encrypted.byteLength >> 2);
42
43 let decipher = new AES(Array.prototype.slice.call(key));
44
45 // byte and word-level access for the decrypted output
46 let decrypted = new Uint8Array(encrypted.byteLength);
47 let decrypted32 = new Int32Array(decrypted.buffer);
48
49 // temporary variables for working with the IV, encrypted, and
50 // decrypted data
51 let init0;
52 let init1;
53 let init2;
54 let init3;
55 let encrypted0;
56 let encrypted1;
57 let encrypted2;
58 let encrypted3;
59
60 // iteration variable
61 let wordIx;
62
63 // pull out the words of the IV to ensure we don't modify the
64 // passed-in reference and easier access
65 init0 = initVector[0];
66 init1 = initVector[1];
67 init2 = initVector[2];
68 init3 = initVector[3];
69
70 // decrypt four word sequences, applying cipher-block chaining (CBC)
71 // to each decrypted block
72 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
73 // convert big-endian (network order) words into little-endian
74 // (javascript order)
75 encrypted0 = ntoh(encrypted32[wordIx]);
76 encrypted1 = ntoh(encrypted32[wordIx + 1]);
77 encrypted2 = ntoh(encrypted32[wordIx + 2]);
78 encrypted3 = ntoh(encrypted32[wordIx + 3]);
79
80 // decrypt the block
81 decipher.decrypt(encrypted0,
82 encrypted1,
83 encrypted2,
84 encrypted3,
85 decrypted32,
86 wordIx);
87
88 // XOR with the IV, and restore network byte-order to obtain the
89 // plaintext
90 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
91 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
92 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
93 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
94
95 // setup the IV for the next round
96 init0 = encrypted0;
97 init1 = encrypted1;
98 init2 = encrypted2;
99 init3 = encrypted3;
100 }
101
102 return decrypted;
103 };
104
105 /**
106 * The `Decrypter` class that manages decryption of AES
107 * data through `AsyncStream` objects and the `decrypt`
108 * function
109 */
110 export class Decrypter {
111 constructor(encrypted, key, initVector, done) {
112 let step = Decrypter.STEP;
113 let encrypted32 = new Int32Array(encrypted.buffer);
114 let decrypted = new Uint8Array(encrypted.byteLength);
115 let i = 0;
116
117 this.asyncStream_ = new AsyncStream();
118
119 // split up the encryption job and do the individual chunks asynchronously
120 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
121 key,
122 initVector,
123 decrypted));
124 for (i = step; i < encrypted32.length; i += step) {
125 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
126 ntoh(encrypted32[i - 3]),
127 ntoh(encrypted32[i - 2]),
128 ntoh(encrypted32[i - 1])]);
129 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
130 key,
131 initVector,
132 decrypted));
133 }
134 // invoke the done() callback when everything is finished
135 this.asyncStream_.push(function() {
136 // remove pkcs#7 padding from the decrypted bytes
137 done(null, unpad(decrypted));
138 });
139 }
140 decryptChunk_(encrypted, key, initVector, decrypted) {
141 return function() {
142 let bytes = decrypt(encrypted, key, initVector);
143
144 decrypted.set(bytes, encrypted.byteOffset);
145 };
146 }
147 }
148
149 // the maximum number of bytes to process at one time
150 // 4 * 8000;
151 Decrypter.STEP = 32000;
152
153 export default {
154 Decrypter,
155 decrypt
156 };
1 /*
2 * index.js
3 *
4 * Index module to easily import the primary components of AES-128
5 * decryption. Like this:
6 *
7 * ```js
8 * import {Decrypter, decrypt, AsyncStream} from './src/decrypter';
9 * ```
10 */
11 import {decrypt, Decrypter} from './decrypter';
12 import AsyncStream from './async-stream';
13
14 export default {
15 decrypt,
16 Decrypter,
17 AsyncStream
18 };
1 /**
2 * Utilities for parsing M3U8 files. If the entire manifest is available,
3 * `Parser` will create an object representation with enough detail for managing
4 * playback. `ParseStream` and `LineStream` are lower-level parsing primitives
5 * that do not assume the entirety of the manifest is ready and expose a
6 * ReadableStream-like interface.
7 */
8
9 import LineStream from './line-stream';
10 import ParseStream from './parse-stream';
11 import Parser from './parser';
12
13 export default {
14 LineStream,
15 ParseStream,
16 Parser
17 };
1 import Stream from '../stream';
2 /**
3 * A stream that buffers string input and generates a `data` event for each
4 * line.
5 */
6 export default class LineStream extends Stream {
7 constructor() {
8 super();
9 this.buffer = '';
10 }
11
12 /**
13 * Add new data to be parsed.
14 * @param data {string} the text to process
15 */
16 push(data) {
17 let nextNewline;
18
19 this.buffer += data;
20 nextNewline = this.buffer.indexOf('\n');
21
22 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
23 this.trigger('data', this.buffer.substring(0, nextNewline));
24 this.buffer = this.buffer.substring(nextNewline + 1);
25 }
26 }
27 }
1 import Stream from '../stream' ;
2 import LineStream from './line-stream';
3 import ParseStream from './parse-stream';
4 import {mergeOptions} from 'video.js';
5
6 /**
7 * A parser for M3U8 files. The current interpretation of the input is
8 * exposed as a property `manifest` on parser objects. It's just two lines to
9 * create and parse a manifest once you have the contents available as a string:
10 *
11 * ```js
12 * var parser = new videojs.m3u8.Parser();
13 * parser.push(xhr.responseText);
14 * ```
15 *
16 * New input can later be applied to update the manifest object by calling
17 * `push` again.
18 *
19 * The parser attempts to create a usable manifest object even if the
20 * underlying input is somewhat nonsensical. It emits `info` and `warning`
21 * events during the parse if it encounters input that seems invalid or
22 * requires some property of the manifest object to be defaulted.
23 */
24 export default class Parser extends Stream {
25 constructor() {
26 super();
27 this.lineStream = new LineStream();
28 this.parseStream = new ParseStream();
29 this.lineStream.pipe(this.parseStream);
30 /* eslint-disable consistent-this */
31 let self = this;
32 /* eslint-enable consistent-this */
33 let uris = [];
34 let currentUri = {};
35 let key;
36 let noop = function() {};
37
38 // the manifest is empty until the parse stream begins delivering data
39 this.manifest = {
40 allowCache: true,
41 discontinuityStarts: []
42 };
43
44 // update the manifest with the m3u8 entry from the parse stream
45 this.parseStream.on('data', function(entry) {
46 ({
47 tag() {
48 // switch based on the tag type
49 (({
50 'allow-cache'() {
51 this.manifest.allowCache = entry.allowed;
52 if (!('allowed' in entry)) {
53 this.trigger('info', {
54 message: 'defaulting allowCache to YES'
55 });
56 this.manifest.allowCache = true;
57 }
58 },
59 byterange() {
60 let byterange = {};
61
62 if ('length' in entry) {
63 currentUri.byterange = byterange;
64 byterange.length = entry.length;
65
66 if (!('offset' in entry)) {
67 this.trigger('info', {
68 message: 'defaulting offset to zero'
69 });
70 entry.offset = 0;
71 }
72 }
73 if ('offset' in entry) {
74 currentUri.byterange = byterange;
75 byterange.offset = entry.offset;
76 }
77 },
78 endlist() {
79 this.manifest.endList = true;
80 },
81 inf() {
82 if (!('mediaSequence' in this.manifest)) {
83 this.manifest.mediaSequence = 0;
84 this.trigger('info', {
85 message: 'defaulting media sequence to zero'
86 });
87 }
88 if (!('discontinuitySequence' in this.manifest)) {
89 this.manifest.discontinuitySequence = 0;
90 this.trigger('info', {
91 message: 'defaulting discontinuity sequence to zero'
92 });
93 }
94 if (entry.duration >= 0) {
95 currentUri.duration = entry.duration;
96 }
97
98 this.manifest.segments = uris;
99
100 },
101 key() {
102 if (!entry.attributes) {
103 this.trigger('warn', {
104 message: 'ignoring key declaration without attribute list'
105 });
106 return;
107 }
108 // clear the active encryption key
109 if (entry.attributes.METHOD === 'NONE') {
110 key = null;
111 return;
112 }
113 if (!entry.attributes.URI) {
114 this.trigger('warn', {
115 message: 'ignoring key declaration without URI'
116 });
117 return;
118 }
119 if (!entry.attributes.METHOD) {
120 this.trigger('warn', {
121 message: 'defaulting key method to AES-128'
122 });
123 }
124
125 // setup an encryption key for upcoming segments
126 key = {
127 method: entry.attributes.METHOD || 'AES-128',
128 uri: entry.attributes.URI
129 };
130
131 if (typeof entry.attributes.IV !== 'undefined') {
132 key.iv = entry.attributes.IV;
133 }
134 },
135 'media-sequence'() {
136 if (!isFinite(entry.number)) {
137 this.trigger('warn', {
138 message: 'ignoring invalid media sequence: ' + entry.number
139 });
140 return;
141 }
142 this.manifest.mediaSequence = entry.number;
143 },
144 'discontinuity-sequence'() {
145 if (!isFinite(entry.number)) {
146 this.trigger('warn', {
147 message: 'ignoring invalid discontinuity sequence: ' + entry.number
148 });
149 return;
150 }
151 this.manifest.discontinuitySequence = entry.number;
152 },
153 'playlist-type'() {
154 if (!(/VOD|EVENT/).test(entry.playlistType)) {
155 this.trigger('warn', {
156 message: 'ignoring unknown playlist type: ' + entry.playlist
157 });
158 return;
159 }
160 this.manifest.playlistType = entry.playlistType;
161 },
162 'stream-inf'() {
163 this.manifest.playlists = uris;
164
165 if (!entry.attributes) {
166 this.trigger('warn', {
167 message: 'ignoring empty stream-inf attributes'
168 });
169 return;
170 }
171
172 if (!currentUri.attributes) {
173 currentUri.attributes = {};
174 }
175 currentUri.attributes = mergeOptions(currentUri.attributes,
176 entry.attributes);
177 },
178 discontinuity() {
179 currentUri.discontinuity = true;
180 this.manifest.discontinuityStarts.push(uris.length);
181 },
182 targetduration() {
183 if (!isFinite(entry.duration) || entry.duration < 0) {
184 this.trigger('warn', {
185 message: 'ignoring invalid target duration: ' + entry.duration
186 });
187 return;
188 }
189 this.manifest.targetDuration = entry.duration;
190 },
191 totalduration() {
192 if (!isFinite(entry.duration) || entry.duration < 0) {
193 this.trigger('warn', {
194 message: 'ignoring invalid total duration: ' + entry.duration
195 });
196 return;
197 }
198 this.manifest.totalDuration = entry.duration;
199 }
200 })[entry.tagType] || noop).call(self);
201 },
202 uri() {
203 currentUri.uri = entry.uri;
204 uris.push(currentUri);
205
206 // if no explicit duration was declared, use the target duration
207 if (this.manifest.targetDuration &&
208 !('duration' in currentUri)) {
209 this.trigger('warn', {
210 message: 'defaulting segment duration to the target duration'
211 });
212 currentUri.duration = this.manifest.targetDuration;
213 }
214 // annotate with encryption information, if necessary
215 if (key) {
216 currentUri.key = key;
217 }
218
219 // prepare for the next URI
220 currentUri = {};
221 },
222 comment() {
223 // comments are not important for playback
224 }
225 })[entry.type].call(self);
226 });
227
228 }
229
230 /**
231 * Parse the input string and update the manifest object.
232 * @param chunk {string} a potentially incomplete portion of the manifest
233 */
234 push(chunk) {
235 this.lineStream.push(chunk);
236 }
237
238 /**
239 * Flush any remaining input. This can be handy if the last line of an M3U8
240 * manifest did not contain a trailing newline but the file has been
241 * completely received.
242 */
243 end() {
244 // flush any buffered input
245 this.lineStream.push('\n');
246 }
247
248 }
249
1 import document from 'global/document';
2 /* eslint-disable max-len */
3 /**
4 * Constructs a new URI by interpreting a path relative to another
5 * URI.
6 * @param basePath {string} a relative or absolute URI
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
11 */
12 /* eslint-enable max-len */
13 const resolveUrl = function(basePath, path) {
14 // use the base element to get the browser to handle URI resolution
15 let oldBase = document.querySelector('base');
16 let docHead = document.querySelector('head');
17 let a = document.createElement('a');
18 let base = oldBase;
19 let oldHref;
20 let result;
21
22 // prep the document
23 if (oldBase) {
24 oldHref = oldBase.href;
25 } else {
26 base = docHead.appendChild(document.createElement('base'));
27 }
28
29 base.href = basePath;
30 a.href = path;
31 result = a.href;
32
33 // clean up
34 if (oldBase) {
35 oldBase.href = oldHref;
36 } else {
37 docHead.removeChild(base);
38 }
39 return result;
40 };
41
42 export default resolveUrl;
1 /** 1 /**
2 * A lightweight readable stream implemention that handles event dispatching. 2 * A lightweight readable stream implemention that handles event dispatching.
3 * Objects that inherit from streams should call init in their constructors.
4 */ 3 */
5 (function(videojs, undefined) { 4 export default class Stream {
6 var Stream = function() { 5 constructor() {
7 this.init = function() { 6 this.listeners = {};
8 var listeners = {}; 7 }
9 /** 8
10 * Add a listener for a specified event type. 9 /**
11 * @param type {string} the event name 10 * Add a listener for a specified event type.
12 * @param listener {function} the callback to be invoked when an event of 11 * @param type {string} the event name
13 * the specified type occurs 12 * @param listener {function} the callback to be invoked when an event of
14 */ 13 * the specified type occurs
15 this.on = function(type, listener) { 14 */
16 if (!listeners[type]) { 15 on(type, listener) {
17 listeners[type] = []; 16 if (!this.listeners[type]) {
18 } 17 this.listeners[type] = [];
19 listeners[type].push(listener); 18 }
20 }; 19 this.listeners[type].push(listener);
21 /** 20 }
22 * Remove a listener for a specified event type. 21
23 * @param type {string} the event name 22 /**
24 * @param listener {function} a function previously registered for this 23 * Remove a listener for a specified event type.
25 * type of event through `on` 24 * @param type {string} the event name
26 */ 25 * @param listener {function} a function previously registered for this
27 this.off = function(type, listener) { 26 * type of event through `on`
28 var index; 27 */
29 if (!listeners[type]) { 28 off(type, listener) {
30 return false; 29 let index;
31 } 30
32 index = listeners[type].indexOf(listener); 31 if (!this.listeners[type]) {
33 listeners[type].splice(index, 1); 32 return false;
34 return index > -1; 33 }
35 }; 34 index = this.listeners[type].indexOf(listener);
36 /** 35 this.listeners[type].splice(index, 1);
37 * Trigger an event of the specified type on this stream. Any additional 36 return index > -1;
38 * arguments to this function are passed as parameters to event listeners. 37 }
39 * @param type {string} the event name 38
40 */ 39 /**
41 this.trigger = function(type) { 40 * Trigger an event of the specified type on this stream. Any additional
42 var callbacks, i, length, args; 41 * arguments to this function are passed as parameters to event listeners.
43 callbacks = listeners[type]; 42 * @param type {string} the event name
44 if (!callbacks) { 43 */
45 return; 44 trigger(type) {
46 } 45 let callbacks;
47 // Slicing the arguments on every invocation of this method 46 let i;
48 // can add a significant amount of overhead. Avoid the 47 let length;
49 // intermediate object creation for the common case of a 48 let args;
50 // single callback argument 49
51 if (arguments.length === 2) { 50 callbacks = this.listeners[type];
52 length = callbacks.length; 51 if (!callbacks) {
53 for (i = 0; i < length; ++i) { 52 return;
54 callbacks[i].call(this, arguments[1]); 53 }
55 } 54 // Slicing the arguments on every invocation of this method
56 } else { 55 // can add a significant amount of overhead. Avoid the
57 args = Array.prototype.slice.call(arguments, 1); 56 // intermediate object creation for the common case of a
58 length = callbacks.length; 57 // single callback argument
59 for (i = 0; i < length; ++i) { 58 if (arguments.length === 2) {
60 callbacks[i].apply(this, args); 59 length = callbacks.length;
61 } 60 for (i = 0; i < length; ++i) {
62 } 61 callbacks[i].call(this, arguments[1]);
63 }; 62 }
64 /** 63 } else {
65 * Destroys the stream and cleans up. 64 args = Array.prototype.slice.call(arguments, 1);
66 */ 65 length = callbacks.length;
67 this.dispose = function() { 66 for (i = 0; i < length; ++i) {
68 listeners = {}; 67 callbacks[i].apply(this, args);
69 }; 68 }
70 }; 69 }
71 }; 70 }
71
72 /**
73 * Destroys the stream and cleans up.
74 */
75 dispose() {
76 this.listeners = {};
77 }
72 /** 78 /**
73 * Forwards all `data` events on this stream to the destination stream. The 79 * Forwards all `data` events on this stream to the destination stream. The
74 * destination stream should provide a method `push` to receive the data 80 * destination stream should provide a method `push` to receive the data
...@@ -76,11 +82,9 @@ ...@@ -76,11 +82,9 @@
76 * @param destination {stream} the stream that will receive all `data` events 82 * @param destination {stream} the stream that will receive all `data` events
77 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 83 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
78 */ 84 */
79 Stream.prototype.pipe = function(destination) { 85 pipe(destination) {
80 this.on('data', function(data) { 86 this.on('data', function(data) {
81 destination.push(data); 87 destination.push(data);
82 }); 88 });
83 }; 89 }
84 90 }
85 videojs.Hls.Stream = Stream;
86 })(window.videojs);
......
1 (function(videojs) { 1 /**
2 'use strict'; 2 * A wrapper for videojs.xhr that tracks bandwidth.
3 */
4 import {xhr as videojsXHR, mergeOptions} from 'video.js';
5 const xhr = function(options, callback) {
6 // Add a default timeout for all hls requests
7 options = mergeOptions({
8 timeout: 45e3
9 }, options);
3 10
4 /** 11 let request = videojsXHR(options, function(error, response) {
5 * A wrapper for videojs.xhr that tracks bandwidth. 12 if (!error && request.response) {
6 */ 13 request.responseTime = (new Date()).getTime();
7 videojs.Hls.xhr = function(options, callback) { 14 request.roundTripTime = request.responseTime - request.requestTime;
8 // Add a default timeout for all hls requests 15 request.bytesReceived = request.response.byteLength || request.response.length;
9 options = videojs.mergeOptions({ 16 if (!request.bandwidth) {
10 timeout: 45e3 17 request.bandwidth =
11 }, options); 18 Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
12
13 var request = videojs.xhr(options, function(error, response) {
14 if (!error && request.response) {
15 request.responseTime = (new Date()).getTime();
16 request.roundTripTime = request.responseTime - request.requestTime;
17 request.bytesReceived = request.response.byteLength || request.response.length;
18 if (!request.bandwidth) {
19 request.bandwidth = Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
20 }
21 } 19 }
20 }
22 21
23 // videojs.xhr now uses a specific code on the error object to signal that a request has 22 // videojs.xhr now uses a specific code
24 // timed out errors of setting a boolean on the request object 23 // on the error object to signal that a request has
25 if (error || request.timedout) { 24 // timed out errors of setting a boolean on the request object
26 request.timedout = request.timedout || (error.code === 'ETIMEDOUT'); 25 if (error || request.timedout) {
27 } else { 26 request.timedout = request.timedout || (error.code === 'ETIMEDOUT');
28 request.timedout = false; 27 } else {
29 } 28 request.timedout = false;
29 }
30 30
31 // videojs.xhr no longer considers status codes outside of 200 and 0 31 // videojs.xhr no longer considers status codes outside of 200 and 0
32 // (for file uris) to be errors, but the old XHR did, so emulate that 32 // (for file uris) to be errors, but the old XHR did, so emulate that
33 // behavior. Status 206 may be used in response to byterange requests. 33 // behavior. Status 206 may be used in response to byterange requests.
34 if (!error && 34 if (!error &&
35 response.statusCode !== 200 && 35 response.statusCode !== 200 &&
36 response.statusCode !== 206 && 36 response.statusCode !== 206 &&
37 response.statusCode !== 0) { 37 response.statusCode !== 0) {
38 error = new Error('XHR Failed with a response of: ' + 38 error = new Error('XHR Failed with a response of: ' +
39 (request && (request.response || request.responseText))); 39 (request && (request.response || request.responseText)));
40 } 40 }
41
42 callback(error, request);
43 });
41 44
42 callback(error, request); 45 request.requestTime = (new Date()).getTime();
43 }); 46 return request;
47 };
44 48
45 request.requestTime = (new Date()).getTime(); 49 export default xhr;
46 return request;
47 };
48 })(window.videojs);
......
1 {
2 "curly": true,
3 "eqeqeq": true,
4 "immed": true,
5 "latedef": true,
6 "newcap": true,
7 "noarg": true,
8 "sub": true,
9 "undef": true,
10 "unused": true,
11 "boss": true,
12 "eqnull": true,
13 "browser": true,
14 "node": true,
15 "predef": [
16 "QUnit",
17 "module",
18 "test",
19 "asyncTest",
20 "expect",
21 "start",
22 "stop",
23 "ok",
24 "equal",
25 "notEqual",
26 "deepEqual",
27 "notDeepEqual",
28 "strictEqual",
29 "notStrictEqual",
30 "throws",
31 "sinon",
32 "process"
33 ]
34 }
1 // see docs/hlse.md for instructions on how test data was generated
2 import QUnit from 'qunit';
3 import {unpad} from 'pkcs7';
4 import sinon from 'sinon';
5 import {decrypt, Decrypter, AsyncStream} from '../src/decrypter';
6
7 // see docs/hlse.md for instructions on how test data was generated
8 const stringFromBytes = function(bytes) {
9 let result = '';
10
11 for (let i = 0; i < bytes.length; i++) {
12 result += String.fromCharCode(bytes[i]);
13 }
14 return result;
15 };
16
17 QUnit.module('Decryption');
18 QUnit.test('decrypts a single AES-128 with PKCS7 block', function() {
19 let key = new Uint32Array([0, 0, 0, 0]);
20 let initVector = key;
21 // the string "howdy folks" encrypted
22 let encrypted = new Uint8Array([
23 0xce, 0x90, 0x97, 0xd0,
24 0x08, 0x46, 0x4d, 0x18,
25 0x4f, 0xae, 0x01, 0x1c,
26 0x82, 0xa8, 0xf0, 0x67
27 ]);
28
29 QUnit.deepEqual('howdy folks',
30 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
31 'decrypted with a byte array key'
32 );
33 });
34
35 QUnit.test('decrypts multiple AES-128 blocks with CBC', function() {
36 let key = new Uint32Array([0, 0, 0, 0]);
37 let initVector = key;
38 // the string "0123456789abcdef01234" encrypted
39 let encrypted = new Uint8Array([
40 0x14, 0xf5, 0xfe, 0x74,
41 0x69, 0x66, 0xf2, 0x92,
42 0x65, 0x1c, 0x22, 0x88,
43 0xbb, 0xff, 0x46, 0x09,
44
45 0x0b, 0xde, 0x5e, 0x71,
46 0x77, 0x87, 0xeb, 0x84,
47 0xa9, 0x54, 0xc2, 0x45,
48 0xe9, 0x4e, 0x29, 0xb3
49 ]);
50
51 QUnit.deepEqual('0123456789abcdef01234',
52 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
53 'decrypted multiple blocks');
54 });
55
56 QUnit.test(
57 'verify that the deepcopy works by doing two decrypts in the same test',
58 function() {
59 let key = new Uint32Array([0, 0, 0, 0]);
60 let initVector = key;
61 // the string "howdy folks" encrypted
62 let pkcs7Block = new Uint8Array([
63 0xce, 0x90, 0x97, 0xd0,
64 0x08, 0x46, 0x4d, 0x18,
65 0x4f, 0xae, 0x01, 0x1c,
66 0x82, 0xa8, 0xf0, 0x67
67 ]);
68
69 QUnit.deepEqual('howdy folks',
70 stringFromBytes(unpad(decrypt(pkcs7Block, key, initVector))),
71 'decrypted with a byte array key'
72 );
73
74 // the string "0123456789abcdef01234" encrypted
75 let cbcBlocks = new Uint8Array([
76 0x14, 0xf5, 0xfe, 0x74,
77 0x69, 0x66, 0xf2, 0x92,
78 0x65, 0x1c, 0x22, 0x88,
79 0xbb, 0xff, 0x46, 0x09,
80
81 0x0b, 0xde, 0x5e, 0x71,
82 0x77, 0x87, 0xeb, 0x84,
83 0xa9, 0x54, 0xc2, 0x45,
84 0xe9, 0x4e, 0x29, 0xb3
85 ]);
86
87 QUnit.deepEqual('0123456789abcdef01234',
88 stringFromBytes(unpad(decrypt(cbcBlocks, key, initVector))),
89 'decrypted multiple blocks');
90
91 });
92
93 QUnit.module('Incremental Processing', {
94 beforeEach() {
95 this.clock = sinon.useFakeTimers();
96 },
97 afterEach() {
98 this.clock.restore();
99 }
100 });
101
102 QUnit.test('executes a callback after a timeout', function() {
103 let asyncStream = new AsyncStream();
104 let calls = '';
105
106 asyncStream.push(function() {
107 calls += 'a';
108 });
109
110 this.clock.tick(asyncStream.delay);
111 QUnit.equal(calls, 'a', 'invoked the callback once');
112 this.clock.tick(asyncStream.delay);
113 QUnit.equal(calls, 'a', 'only invoked the callback once');
114 });
115
116 QUnit.test('executes callback in series', function() {
117 let asyncStream = new AsyncStream();
118 let calls = '';
119
120 asyncStream.push(function() {
121 calls += 'a';
122 });
123 asyncStream.push(function() {
124 calls += 'b';
125 });
126
127 this.clock.tick(asyncStream.delay);
128 QUnit.equal(calls, 'a', 'invoked the first callback');
129 this.clock.tick(asyncStream.delay);
130 QUnit.equal(calls, 'ab', 'invoked the second');
131 });
132
133 QUnit.module('Incremental Decryption', {
134 beforeEach() {
135 this.clock = sinon.useFakeTimers();
136 },
137 afterEach() {
138 this.clock.restore();
139 }
140 });
141
142 QUnit.test('asynchronously decrypts a 4-word block', function() {
143 let key = new Uint32Array([0, 0, 0, 0]);
144 let initVector = key;
145 // the string "howdy folks" encrypted
146 let encrypted = new Uint8Array([0xce, 0x90, 0x97, 0xd0,
147 0x08, 0x46, 0x4d, 0x18,
148 0x4f, 0xae, 0x01, 0x1c,
149 0x82, 0xa8, 0xf0, 0x67]);
150 let decrypted;
151 let decrypter = new Decrypter(encrypted,
152 key,
153 initVector,
154 function(error, result) {
155 if (error) {
156 throw new Error(error);
157 }
158 decrypted = result;
159 });
160
161 QUnit.ok(!decrypted, 'asynchronously decrypts');
162 this.clock.tick(decrypter.asyncStream_.delay * 2);
163
164 QUnit.ok(decrypted, 'completed decryption');
165 QUnit.deepEqual('howdy folks',
166 stringFromBytes(decrypted),
167 'decrypts and unpads the result');
168 });
169
170 QUnit.test('breaks up input greater than the step value', function() {
171 let encrypted = new Int32Array(Decrypter.STEP + 4);
172 let done = false;
173 let decrypter = new Decrypter(encrypted,
174 new Uint32Array(4),
175 new Uint32Array(4),
176 function() {
177 done = true;
178 });
179
180 this.clock.tick(decrypter.asyncStream_.delay * 2);
181 QUnit.ok(!done, 'not finished after two ticks');
182
183 this.clock.tick(decrypter.asyncStream_.delay);
184 QUnit.ok(done, 'finished after the last chunk is decrypted');
185 });
1 (function(window, videojs, unpad, undefined) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23
24 // see docs/hlse.md for instructions on how test data was generated
25
26 var stringFromBytes = function(bytes) {
27 var result = '', i;
28
29 for (i = 0; i < bytes.length; i++) {
30 result += String.fromCharCode(bytes[i]);
31 }
32 return result;
33 };
34
35 module('Decryption');
36
37 test('decrypts a single AES-128 with PKCS7 block', function() {
38 var
39 key = new Uint32Array([0, 0, 0, 0]),
40 initVector = key,
41 // the string "howdy folks" encrypted
42 encrypted = new Uint8Array([
43 0xce, 0x90, 0x97, 0xd0,
44 0x08, 0x46, 0x4d, 0x18,
45 0x4f, 0xae, 0x01, 0x1c,
46 0x82, 0xa8, 0xf0, 0x67]);
47
48 deepEqual('howdy folks',
49 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))),
50 'decrypted with a byte array key');
51 });
52
53 test('decrypts multiple AES-128 blocks with CBC', function() {
54 var
55 key = new Uint32Array([0, 0, 0, 0]),
56 initVector = key,
57 // the string "0123456789abcdef01234" encrypted
58 encrypted = new Uint8Array([
59 0x14, 0xf5, 0xfe, 0x74,
60 0x69, 0x66, 0xf2, 0x92,
61 0x65, 0x1c, 0x22, 0x88,
62 0xbb, 0xff, 0x46, 0x09,
63
64 0x0b, 0xde, 0x5e, 0x71,
65 0x77, 0x87, 0xeb, 0x84,
66 0xa9, 0x54, 0xc2, 0x45,
67 0xe9, 0x4e, 0x29, 0xb3
68 ]);
69
70 deepEqual('0123456789abcdef01234',
71 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))),
72 'decrypted multiple blocks');
73 });
74
75 var clock;
76
77 module('Incremental Processing', {
78 setup: function() {
79 clock = sinon.useFakeTimers();
80 },
81 teardown: function() {
82 clock.restore();
83 }
84 });
85
86 test('executes a callback after a timeout', function() {
87 var asyncStream = new videojs.Hls.AsyncStream(),
88 calls = '';
89 asyncStream.push(function() {
90 calls += 'a';
91 });
92
93 clock.tick(asyncStream.delay);
94 equal(calls, 'a', 'invoked the callback once');
95 clock.tick(asyncStream.delay);
96 equal(calls, 'a', 'only invoked the callback once');
97 });
98
99 test('executes callback in series', function() {
100 var asyncStream = new videojs.Hls.AsyncStream(),
101 calls = '';
102 asyncStream.push(function() {
103 calls += 'a';
104 });
105 asyncStream.push(function() {
106 calls += 'b';
107 });
108
109 clock.tick(asyncStream.delay);
110 equal(calls, 'a', 'invoked the first callback');
111 clock.tick(asyncStream.delay);
112 equal(calls, 'ab', 'invoked the second');
113 });
114
115 var decrypter;
116
117 module('Incremental Decryption', {
118 setup: function() {
119 clock = sinon.useFakeTimers();
120 },
121 teardown: function() {
122 clock.restore();
123 }
124 });
125
126 test('asynchronously decrypts a 4-word block', function() {
127 var
128 key = new Uint32Array([0, 0, 0, 0]),
129 initVector = key,
130 // the string "howdy folks" encrypted
131 encrypted = new Uint8Array([
132 0xce, 0x90, 0x97, 0xd0,
133 0x08, 0x46, 0x4d, 0x18,
134 0x4f, 0xae, 0x01, 0x1c,
135 0x82, 0xa8, 0xf0, 0x67]),
136 decrypted;
137
138 decrypter = new videojs.Hls.Decrypter(encrypted, key, initVector, function(error, result) {
139 decrypted = result;
140 });
141 ok(!decrypted, 'asynchronously decrypts');
142
143 clock.tick(decrypter.asyncStream_.delay * 2);
144
145 ok(decrypted, 'completed decryption');
146 deepEqual('howdy folks',
147 stringFromBytes(decrypted),
148 'decrypts and unpads the result');
149 });
150
151 test('breaks up input greater than the step value', function() {
152 var encrypted = new Int32Array(videojs.Hls.Decrypter.STEP + 4),
153 done = false,
154 decrypter = new videojs.Hls.Decrypter(encrypted,
155 new Uint32Array(4),
156 new Uint32Array(4),
157 function() {
158 done = true;
159 });
160 clock.tick(decrypter.asyncStream_.delay * 2);
161 ok(!done, 'not finished after two ticks');
162
163 clock.tick(decrypter.asyncStream_.delay);
164 ok(done, 'finished after the last chunk is decrypted');
165 });
166
167 })(window, window.videojs, window.pkcs7.unpad);
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>video.js HLS Plugin Test Suite</title>
6 <link rel="stylesheet" href="/node_modules/qunitjs/qunit/qunit.css" media="screen">
7 <link rel="stylesheet" href="/node_modules/video.js/dist/video-js.css" media="screen">
8 </head>
9 <body>
10 <div id="qunit"></div>
11 <div id="qunit-fixture"></div>
12 <!-- NOTE in order for test to pass we require sinon 1.10.2 exactly -->
13 <script src="/node_modules/sinon/pkg/sinon.js"></script>
14 <script src="/node_modules/qunitjs/qunit/qunit.js"></script>
15 <script src="/node_modules/video.js/dist/video.js"></script>
16 <script src="/dist-test/videojs-contrib-hls.js"></script>
17
18 </body>
19 </html>
1
2
3 var fixture = document.createElement('div');
4 fixture.id = 'qunit-fixture';
5 document.body.appendChild(fixture);
1 // Karma example configuration file
2 // NOTE: To configure Karma tests, do the following:
3 // 1. Copy this file and rename the copy with a .conf.js extension, for example: karma.conf.js
4 // 2. Configure the properties below in your conf.js copy
5 // 3. Run your tests
6
7 module.exports = function(config) {
8 var customLaunchers = {
9 chrome_sl: {
10 singleRun: true,
11 base: 'SauceLabs',
12 browserName: 'chrome',
13 platform: 'Windows 7'
14 },
15
16 firefox_sl: {
17 singleRun: true,
18 base: 'SauceLabs',
19 browserName: 'firefox',
20 platform: 'Windows 8'
21 },
22
23 safari_sl: {
24 singleRun: true,
25 base: 'SauceLabs',
26 browserName: 'safari',
27 platform: 'OS X 10.8'
28 },
29
30 ipad_sl: {
31 singleRun: true,
32 base: 'SauceLabs',
33 browserName: 'ipad',
34 platform:'OS X 10.9',
35 version: '7.1'
36 },
37
38 android_sl: {
39 singleRun: true,
40 base: 'SauceLabs',
41 browserName: 'android',
42 platform:'Linux'
43 }
44 };
45
46 config.set({
47 // base path, that will be used to resolve files and exclude
48 basePath: '',
49
50 frameworks: ['qunit'],
51
52 // Set autoWatch to true if you plan to run `grunt karma` continuously, to automatically test changes as you make them.
53 autoWatch: false,
54
55 // Setting singleRun to true here will start up your specified browsers, run tests, and then shut down the browsers. Helpful to have in a CI environment, where you don't want to leave browsers running continuously.
56 singleRun: true,
57
58 // custom launchers for sauce labs
59 //define SL browsers
60 customLaunchers: customLaunchers,
61
62 // Start these browsers
63 browsers: ['chrome_sl'], //Object.keys(customLaunchers),
64
65 // List of files / patterns to load in the browser
66 // Add any new src files to this list.
67 // If you add new unit tests, they will be picked up automatically by Karma,
68 // unless you've added them to a nested directory, in which case you should
69 // add their paths to this list.
70
71 files: [
72 '../node_modules/sinon/pkg/sinon.js',
73 '../node_modules/video.js/dist/video-js.css',
74 '../node_modules/video.js/dist/video.js',
75 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
76 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
77 '../test/karma-qunit-shim.js',
78 '../src/videojs-hls.js',
79 '../src/stream.js',
80 '../src/m3u8/m3u8-parser.js',
81 '../src/xhr.js',
82 '../src/playlist.js',
83 '../src/playlist-loader.js',
84 '../src/decrypter.js',
85 '../tmp/manifests.js',
86 '../tmp/expected.js',
87 'tsSegment-bc.js',
88 '../src/bin-utils.js',
89 '../test/*.js',
90 ],
91
92 plugins: [
93 'karma-qunit',
94 'karma-chrome-launcher',
95 'karma-firefox-launcher',
96 'karma-ie-launcher',
97 'karma-opera-launcher',
98 'karma-phantomjs-launcher',
99 'karma-safari-launcher',
100 'karma-sauce-launcher'
101 ],
102
103 // test results reporter to use
104 // possible values: 'dots', 'progress', 'junit'
105 reporters: ['dots', 'progress'],
106
107 // web server port
108 port: 9876,
109
110 // cli runner port
111 runnerPort: 9100,
112
113 // enable / disable colors in the output (reporters and logs)
114 colors: true,
115
116 // level of logging
117 // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
118 //logLevel: config.LOG_INFO,
119
120 // If browser does not capture in given timeout [ms], kill it
121 captureTimeout: 60000,
122
123 // global config for SauceLabs
124 sauceLabs: {
125 startConnect: false,
126 tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
127 build: process.env.TRAVIS_BUILD_NUMBER,
128 testName: process.env.TRAVIS_BUILD_NUMBER + process.env.TRAVIS_BRANCH,
129 recordScreenshots: false
130 }
131 });
132 };
1 var common = require('./common');
2
3 module.exports = function(config) {
4 config.set(common({
5 plugins: ['karma-chrome-launcher'],
6 browsers: ['Chrome']
7 }));
8 };
1 var merge = require('lodash-compat/object/merge');
2
3 var DEFAULTS = {
4 basePath: '../..',
5 frameworks: ['browserify', 'qunit'],
6
7
8 files: [
9 'node_modules/sinon/pkg/sinon.js',
10 'node_modules/sinon/pkg/sinon-ie.js',
11 'node_modules/video.js/dist/video.js',
12 'node_modules/video.js/dist/video-js.css',
13
14 'test/**/*.test.js'
15 ],
16
17 exclude: [
18 'test/data/**'
19 ],
20
21 plugins: [
22 'karma-browserify',
23 'karma-qunit'
24 ],
25
26 preprocessors: {
27 'test/**/*.test.js': ['browserify']
28 },
29
30 reporters: ['dots'],
31 port: 9876,
32 colors: true,
33 autoWatch: false,
34 singleRun: true,
35 concurrency: Infinity,
36
37 browserify: {
38 debug: true,
39 transform: [
40 'babelify',
41 'browserify-shim'
42 ],
43 noParse: [
44 'test/data/**',
45 ]
46 }
47 };
48
49 /**
50 * Customizes target/source merging with lodash merge.
51 *
52 * @param {Mixed} target
53 * @param {Mixed} source
54 * @return {Mixed}
55 */
56 var customizer = function(target, source) {
57 if (Array.isArray(target)) {
58 return target.concat(source);
59 }
60 };
61
62 /**
63 * Generates a new Karma config with a common set of base configuration.
64 *
65 * @param {Object} custom
66 * Configuration that will be deep-merged. Arrays will be
67 * concatenated.
68 * @return {Object}
69 */
70 module.exports = function(custom) {
71 return merge({}, custom, DEFAULTS, customizer);
72 };
1 var common = require('./common');
2
3 // Runs default testing configuration in multiple environments.
4
5 module.exports = function(config) {
6
7 // Travis CI should run in its available Firefox headless browser.
8 if (process.env.TRAVIS) {
9
10 config.set(common({
11 browsers: ['Firefox'],
12 plugins: ['karma-firefox-launcher']
13 }))
14 } else {
15 config.set(common({
16
17 frameworks: ['detectBrowsers'],
18
19 plugins: [
20 'karma-chrome-launcher',
21 'karma-detect-browsers',
22 'karma-firefox-launcher',
23 'karma-ie-launcher',
24 'karma-safari-launcher'
25 ],
26
27 detectBrowsers: {
28 // disable safari as it was not previously supported and causes test failures
29 postDetection: function(availableBrowsers) {
30 var safariIndex = availableBrowsers.indexOf('Safari');
31 if(safariIndex !== -1) {
32 console.log("Not running safari it is/was broken");
33 availableBrowsers.splice(safariIndex, 1);
34 }
35 return availableBrowsers;
36 },
37 usePhantomJS: false
38 }
39 }));
40 }
41 };
1 var common = require('./common');
2
3 module.exports = function(config) {
4 config.set(common({
5 plugins: ['karma-firefox-launcher'],
6 browsers: ['Firefox']
7 }));
8 };
1 var common = require('./common');
2
3 module.exports = function(config) {
4 config.set(common({
5 plugins: ['karma-ie-launcher'],
6 browsers: ['IE']
7 }));
8 };
1 var common = require('./common');
2
3 module.exports = function(config) {
4 config.set(common({
5 plugins: ['karma-safari-launcher'],
6 browsers: ['Safari']
7 }));
8 };
1 // Karma example configuration file
2 // NOTE: To configure Karma tests, do the following:
3 // 1. Copy this file and rename the copy with a .conf.js extension, for example: karma.conf.js
4 // 2. Configure the properties below in your conf.js copy
5 // 3. Run your tests
6
7 module.exports = function(config) {
8 config.set({
9 // base path, that will be used to resolve files and exclude
10 basePath: '',
11
12 frameworks: ['qunit'],
13
14 // Set autoWatch to true if you plan to run `grunt karma` continuously, to automatically test changes as you make them.
15 autoWatch: false,
16
17 // Setting singleRun to true here will start up your specified browsers, run tests, and then shut down the browsers. Helpful to have in a CI environment, where you don't want to leave browsers running continuously.
18 singleRun: true,
19
20 // Start these browsers, currently available:
21 // - Chrome
22 // - ChromeCanary
23 // - Firefox
24 // - Opera
25 // - Safari (only Mac)
26 // - PhantomJS
27 // - IE (only Windows)
28 // Example usage:
29 // browsers: [],
30 // List of files / patterns to load in the browser
31 // Add any new src files to this list.
32 // If you add new unit tests, they will be picked up automatically by Karma,
33 // unless you've added them to a nested directory, in which case you should
34 // add their paths to this list.
35
36 files: [
37 '../node_modules/sinon/pkg/sinon.js',
38 '../node_modules/video.js/dist/video-js.css',
39 '../node_modules/video.js/dist/video.js',
40 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
41 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
42 '../test/karma-qunit-shim.js',
43 '../src/videojs-hls.js',
44 '../src/stream.js',
45 '../src/m3u8/m3u8-parser.js',
46 '../src/xhr.js',
47 '../src/playlist.js',
48 '../src/playlist-loader.js',
49 '../src/decrypter.js',
50 '../tmp/manifests.js',
51 '../tmp/expected.js',
52 'tsSegment-bc.js',
53 '../src/bin-utils.js',
54 '../test/*.js',
55 ],
56
57 plugins: [
58 'karma-qunit',
59 'karma-chrome-launcher',
60 'karma-firefox-launcher',
61 'karma-ie-launcher',
62 'karma-opera-launcher',
63 'karma-phantomjs-launcher',
64 'karma-safari-launcher'
65 ],
66
67 // list of files to exclude
68 exclude: [
69
70 ],
71
72
73 // test results reporter to use
74 // possible values: 'dots', 'progress', 'junit'
75 reporters: ['progress'],
76
77
78 // web server port
79 port: 9876,
80
81
82 // cli runner port
83 runnerPort: 9100,
84
85
86 // enable / disable colors in the output (reporters and logs)
87 colors: true,
88
89
90 // level of logging
91 // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
92 logLevel: config.LOG_DISABLE,
93
94 // If browser does not capture in given timeout [ms], kill it
95 captureTimeout: 60000
96 });
97 };
1 <!doctype html>
2 <html>
3 <head>
4 <title>MPEG-TS Parser Performance Workbench</title>
5 <!-- video.js -->
6 <script src="../node_modules/video.js/video.dev.js"></script>
7
8 <!-- HLS plugin -->
9 <script src="../src/video-js-hls.js"></script>
10 <script src="../src/flv-tag.js"></script>
11 <script src="../src/exp-golomb.js"></script>
12 <script src="../src/h264-stream.js"></script>
13 <script src="../src/aac-stream.js"></script>
14 <script src="../src/segment-parser.js"></script>
15
16 <!-- MPEG-TS segment -->
17 <script src="tsSegment-bc.js"></script>
18 <style>
19 .desc {
20 background-color: #ddd;
21 border: thin solid #333;
22 padding: 8px;
23 }
24 </style>
25 </head>
26 <body>
27 <p class="desc">Select your number of iterations and then press "Run" to begin parsing MPEG-TS packets into FLV tags. This page can be handy for identifying segment parser performance bottlenecks.</p>
28 <form>
29 <input name="iterations" min="1" type="number" value="1">
30 <button type="sumbit">Run</button>
31 </form>
32 <table>
33 <thead>
34 <th>Iterations</th><th>Time</th><th>MB/second</th>
35 </thead>
36 <tbody class="results"></tbody>
37 </table>
38 <script>
39 var
40 button = document.querySelector('button'),
41 input = document.querySelector('input'),
42 results = document.querySelector('.results'),
43
44 reportResults = function(count, elapsed) {
45 var
46 row = document.createElement('tr'),
47 countCell = document.createElement('td'),
48 elapsedCell = document.createElement('td'),
49 throughputCell = document.createElement('td');
50
51 countCell.innerText = count;
52 elapsedCell.innerText = elapsed;
53 throughputCell.innerText = (((bcSegment.byteLength * count * 1000) / elapsed) / (Math.pow(2, 20))).toFixed(3);
54 row.appendChild(countCell);
55 row.appendChild(elapsedCell);
56 row.appendChild(throughputCell);
57
58 results.insertBefore(row, results.firstChild);
59 };
60
61 button.addEventListener('click', function(event) {
62 var
63 iterations = input.value,
64 parser = new window.videojs.hls.SegmentParser(),
65 start;
66
67 // setup
68 start = +new Date();
69
70 while (iterations--) {
71
72 // parse the segment
73 parser.parseSegmentBinaryData(window.bcSegment);
74
75 // finalize all the FLV tags
76 while (parser.tagsAvailable()) {
77 parser.getNextTag();
78 }
79 }
80
81 // report
82 reportResults(input.value, (+new Date()) - start);
83
84 // don't actually submit the form
85 event.preventDefault();
86 }, false);
87 </script>
88 </body>
89 </html>
1 import document from 'global/document';
2
3 import QUnit from 'qunit';
4 import sinon from 'sinon';
5 import videojs from 'video.js';
6
7 QUnit.module('videojs-contrib-hls - sanity', {
8 beforeEach() {
9 this.fixture = document.getElementById('qunit-fixture');
10 this.video = document.createElement('video');
11 this.fixture.appendChild(this.video);
12 this.player = videojs(this.video);
13
14 // Mock the environment's timers because certain things - particularly
15 // player readiness - are asynchronous in video.js 5.
16 this.clock = sinon.useFakeTimers();
17 },
18
19 afterEach() {
20
21 // The clock _must_ be restored before disposing the player; otherwise,
22 // certain timeout listeners that happen inside video.js may throw errors.
23 this.clock.restore();
24 this.player.dispose();
25 }
26 });
27
28 QUnit.test('the environment is sane', function(assert) {
29 assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
30 assert.strictEqual(typeof sinon, 'object', 'sinon exists');
31 assert.strictEqual(typeof videojs, 'function', 'videojs exists');
32 assert.strictEqual(typeof videojs.MediaSource, 'function', 'MediaSource is an object');
33 assert.strictEqual(typeof videojs.URL, 'object', 'URL is an object');
34 assert.strictEqual(typeof videojs.Hls, 'object', 'Hls is an object');
35 assert.strictEqual(typeof videojs.HlsSourceHandler,
36 'function',
37 'HlsSourceHandler is a function');
38 assert.strictEqual(typeof videojs.HlsHandler, 'function', 'HlsHandler is a function');
39 });
This diff could not be displayed because it is too large.
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>video.js HLS Plugin Test Suite</title>
6 <!-- Load sinon server for fakeXHR -->
7 <script src="../node_modules/sinon/pkg/sinon.js"></script>
8
9 <!-- Load local QUnit. -->
10 <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css" media="screen">
11 <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
12
13 <!-- video.js -->
14 <script src="../node_modules/video.js/dist/video.js"></script>
15 <link rel="stylesheet" href="../node_modules/video.js/dist/video-js.css" media="screen">
16 <script src="../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
17
18 <!-- HLS plugin -->
19 <script src="../src/videojs-hls.js"></script>
20 <script src="../src/xhr.js"></script>
21 <script src="../src/stream.js"></script>
22
23 <!-- M3U8 -->
24 <script src="../src/m3u8/m3u8-parser.js"></script>
25 <script src="../src/playlist.js"></script>
26 <script src="../src/playlist-loader.js"></script>
27 <script src="../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
28 <script src="../src/decrypter.js"></script>
29 <!-- M3U8 TEST DATA -->
30 <script src="../tmp/manifests.js"></script>
31 <script src="../tmp/expected.js"></script>
32
33 <!-- M3U8 -->
34 <!-- SEGMENT -->
35 <script src="tsSegment-bc.js"></script>
36 <script src="../src/bin-utils.js"></script>
37
38 <!-- Test cases -->
39 <script>
40 module('environment');
41 test('is sane', function() {
42 expect(1);
43 ok(true);
44 });
45 </script>
46 <script src="videojs-hls_test.js"></script>
47 <script src="m3u8_test.js"></script>
48 <script src="playlist_test.js"></script>
49 <script src="playlist-loader_test.js"></script>
50 <script src="decrypter_test.js"></script>
51 </head>
52 <body>
53 <div id="qunit"></div>
54 <div id="qunit-fixture">
55 <span>test markup</span>
56 </div>
57 </body>
58 </html>
This diff could not be displayed because it is too large.
...@@ -20,7 +20,7 @@ if (process.env.SAUCE_USERNAME) { ...@@ -20,7 +20,7 @@ if (process.env.SAUCE_USERNAME) {
20 config.maxDuration = 300; 20 config.maxDuration = 300;
21 } 21 }
22 22
23 config.baseUrl = 'http://127.0.0.1:9999/example.html'; 23 config.baseUrl = 'http://127.0.0.1:9999/';
24 config.specs = ['spec.js']; 24 config.specs = ['spec.js'];
25 25
26 config.framework = 'jasmine2'; 26 config.framework = 'jasmine2';
......