2db4b64c by Jon-Carlos Rivera

Merge pull request #568 from videojs/browserify-p5

Browserify p5
2 parents c694b4b7 10562674
1 sudo: false 1 sudo: false
2 language: node_js 2 language: node_js
3 addons:
4 firefox: "latest"
3 node_js: 5 node_js:
4 - 'node' 6 - "stable"
5 - '4.2'
6 - '0.12'
7 - '0.10'
8 notifications: 7 notifications:
9 hipchat: 8 hipchat:
10 rooms: 9 rooms:
......
...@@ -46,10 +46,7 @@ ...@@ -46,10 +46,7 @@
46 </ul> 46 </ul>
47 47
48 <script src="/node_modules/video.js/dist/video.js"></script> 48 <script src="/node_modules/video.js/dist/video.js"></script>
49 <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
50 <script src="/src/videojs-contrib-hls.js"></script>
51 <script src="/dist/videojs-contrib-hls.js"></script> 49 <script src="/dist/videojs-contrib-hls.js"></script>
52 <script src="/src/bin-utils.js"></script>
53 <script> 50 <script>
54 (function(window, videojs) { 51 (function(window, videojs) {
55 var player = window.player = videojs('videojs-contrib-hls-player'); 52 var player = window.player = videojs('videojs-contrib-hls-player');
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 "name": "videojs-contrib-hls", 2 "name": "videojs-contrib-hls",
3 "version": "1.3.5", 3 "version": "1.3.5",
4 "description": "Play back HLS with video.js, even where it's not natively supported", 4 "description": "Play back HLS with video.js, even where it's not natively supported",
5 "main": "es5/stub.js", 5 "main": "es5/videojs-contrib-hls.js",
6 "engines": { 6 "engines": {
7 "node": ">= 0.10.12" 7 "node": ">= 0.10.12"
8 }, 8 },
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
27 "docs": "npm-run-all docs:*", 27 "docs": "npm-run-all docs:*",
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 "prestart": "npm-run-all docs build",
32 "start": "npm-run-all -p start:* watch:*", 32 "start": "npm-run-all -p start:* watch:*",
33 "start:serve": "babel-node scripts/server.js", 33 "start:serve": "babel-node scripts/server.js",
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
40 "preversion": "npm test", 40 "preversion": "npm test",
41 "version": "npm run build", 41 "version": "npm run build",
42 "watch": "npm-run-all -p watch:*", 42 "watch": "npm-run-all -p watch:*",
43 "watch:js": "watchify src/stub.js -t babelify -v -o dist/videojs-contrib-hls.js", 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:*", 44 "watch:test": "npm-run-all -p watch:test:*",
45 "watch:test:js": "node scripts/watch-test.js", 45 "watch:test:js": "node scripts/watch-test.js",
46 "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", 46 "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"",
...@@ -72,7 +72,8 @@ ...@@ -72,7 +72,8 @@
72 "scripts", 72 "scripts",
73 "utils", 73 "utils",
74 "test/test-manifests.js", 74 "test/test-manifests.js",
75 "test/test-expected.js" 75 "test/test-expected.js",
76 "src/playlist-loader.js"
76 ] 77 ]
77 }, 78 },
78 "files": [ 79 "files": [
...@@ -88,10 +89,10 @@ ...@@ -88,10 +89,10 @@
88 "utils/" 89 "utils/"
89 ], 90 ],
90 "dependencies": { 91 "dependencies": {
91 "pkcs7": "^0.2.3", 92 "pkcs7": "^0.2.2",
92 "video.js": "^5.0.0", 93 "video.js": "^5.2.1",
93 "videojs-contrib-media-sources": "^2.4.4", 94 "videojs-contrib-media-sources": "^3.0.0",
94 "videojs-swf": "^5.0.1" 95 "videojs-swf": "^5.0.0"
95 }, 96 },
96 "devDependencies": { 97 "devDependencies": {
97 "babel": "^5.8.0", 98 "babel": "^5.8.0",
...@@ -117,12 +118,13 @@ ...@@ -117,12 +118,13 @@
117 "minimist": "^1.2.0", 118 "minimist": "^1.2.0",
118 "npm-run-all": "^1.2.0", 119 "npm-run-all": "^1.2.0",
119 "portscanner": "^1.0.0", 120 "portscanner": "^1.0.0",
120 "qunitjs": "^1.0.0", 121 "qunitjs": "^1.18.0",
121 "serve-static": "^1.10.0", 122 "serve-static": "^1.10.0",
122 "shelljs": "^0.5.3", 123 "shelljs": "^0.5.3",
123 "sinon": "1.10.2", 124 "sinon": "1.10.2",
124 "uglify-js": "^2.5.0", 125 "uglify-js": "^2.5.0",
125 "videojs-standard": "^4.0.0", 126 "videojs-standard": "^4.0.0",
126 "watchify": "^3.6.0" 127 "watchify": "^3.6.0",
128 "webworkify": "^1.1.0"
127 } 129 }
128 } 130 }
......
...@@ -2,7 +2,7 @@ var browserify = require('browserify'); ...@@ -2,7 +2,7 @@ var browserify = require('browserify');
2 var fs = require('fs'); 2 var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 4
5 glob('test/{playlist*,decryper,m3u8,stub}.test.js', function(err, files) { 5 glob('test/**/*.test.js', function(err, files) {
6 browserify(files) 6 browserify(files)
7 .transform('babelify') 7 .transform('babelify')
8 .bundle() 8 .bundle()
......
...@@ -3,7 +3,7 @@ var fs = require('fs'); ...@@ -3,7 +3,7 @@ var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 var watchify = require('watchify'); 4 var watchify = require('watchify');
5 5
6 glob('test/{playlist*,decrypter,m3u8,stub}.test.js', function(err, files) { 6 glob('test/**/*.test.js', function(err, files) {
7 var b = browserify(files, { 7 var b = browserify(files, {
8 cache: {}, 8 cache: {},
9 packageCache: {}, 9 packageCache: {},
......
1 (function(window) { 1 const textRange = function(range, i) {
2 var textRange = function(range, i) {
3 return range.start(i) + '-' + range.end(i); 2 return range.start(i) + '-' + range.end(i);
4 }; 3 };
5 var module = { 4
6 hexDump: function(data) { 5 const formatHexString = function(e, i) {
7 var 6 let value = e.toString(16);
8 bytes = Array.prototype.slice.call(data), 7
9 step = 16, 8 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
10 formatHexString = function(e, i) { 9 };
11 var value = e.toString(16); 10 const formatAsciiString = function(e) {
12 return "00".substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
13 },
14 formatAsciiString = function(e) {
15 if (e >= 0x20 && e < 0x7e) { 11 if (e >= 0x20 && e < 0x7e) {
16 return String.fromCharCode(e); 12 return String.fromCharCode(e);
17 } 13 }
18 return '.'; 14 return '.';
19 }, 15 };
20 result = '', 16
21 hex, 17 const utils = {
22 ascii; 18 hexDump(data) {
23 for (var j = 0; j < bytes.length / step; j++) { 19 let bytes = Array.prototype.slice.call(data);
20 let step = 16;
21 let result = '';
22 let hex;
23 let ascii;
24
25 for (let j = 0; j < bytes.length / step; j++) {
24 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 26 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
25 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 27 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
26 result += hex + ' ' + ascii + '\n'; 28 result += hex + ' ' + ascii + '\n';
27 } 29 }
28 return result; 30 return result;
29 }, 31 },
30 tagDump: function(tag) { 32 tagDump(tag) {
31 return module.hexDump(tag.bytes); 33 return utils.hexDump(tag.bytes);
32 }, 34 },
33 textRanges: function(ranges) { 35 textRanges(ranges) {
34 var result = '', i; 36 let result = '';
37 let i;
38
35 for (i = 0; i < ranges.length; i++) { 39 for (i = 0; i < ranges.length; i++) {
36 result += textRange(ranges, i) + ' '; 40 result += textRange(ranges, i) + ' ';
37 } 41 }
38 return result; 42 return result;
39 } 43 }
40 }; 44 };
41 45
42 window.videojs.Hls.utils = module; 46 export default utils;
43 })(this);
......
...@@ -89,10 +89,9 @@ const precompute = function() { ...@@ -89,10 +89,9 @@ const precompute = function() {
89 decTable[i] = decTable[i].slice(0); 89 decTable[i] = decTable[i].slice(0);
90 } 90 }
91 return tables; 91 return tables;
92 } 92 };
93
94
95 let aesTables = null; 93 let aesTables = null;
94
96 /** 95 /**
97 * Schedule out an AES key for both encryption and decryption. This 96 * Schedule out an AES key for both encryption and decryption. This
98 * 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.
...@@ -116,7 +115,7 @@ export default class AES { ...@@ -116,7 +115,7 @@ export default class AES {
116 */ 115 */
117 // if we have yet to precompute the S-box tables 116 // if we have yet to precompute the S-box tables
118 // do so now 117 // do so now
119 if(!aesTables) { 118 if (!aesTables) {
120 aesTables = precompute(); 119 aesTables = precompute();
121 } 120 }
122 // then make a copy of that object for use 121 // then make a copy of that object for use
......
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 export default class Stream { 4 export default class Stream {
6 constructor() { 5 constructor() {
7 this.init();
8 }
9
10 init() {
11 this.listeners = {}; 6 this.listeners = {};
12 } 7 }
13 8
......
1 import m3u8 from './m3u8';
2 import Stream from './stream';
3 import videojs from 'video.js';
4 import {Decrypter, decrypt, AsyncStream} from './decrypter';
5 import Playlist from './playlist';
6 import PlaylistLoader from './playlist-loader';
7 import xhr from './xhr';
8
9
10 if(typeof window.videojs.Hls === 'undefined') {
11 videojs.Hls = {};
12 }
13 videojs.Hls.Stream = Stream;
14 videojs.m3u8 = m3u8;
15 videojs.Hls.decrypt = decrypt;
16 videojs.Hls.Decrypter = Decrypter;
17 videojs.Hls.AsyncStream = AsyncStream;
18 videojs.Hls.xhr = xhr;
19 videojs.Hls.Playlist = Playlist;
20 videojs.Hls.PlaylistLoader = PlaylistLoader;
1 /* 1 /**
2 * videojs-hls 2 * videojs-hls
3 * The main file for the HLS project. 3 * The main file for the HLS project.
4 * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE 4 * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE
5 */ 5 */
6 (function(window, videojs, document, undefined) { 6 import PlaylistLoader from './playlist-loader';
7 'use strict'; 7 import Playlist from './playlist';
8 import xhr from './xhr';
9 import {Decrypter, AsyncStream, decrypt} from './decrypter';
10 import utils from './bin-utils';
11 import {MediaSource, URL} from 'videojs-contrib-media-sources';
12 import m3u8 from './m3u8';
13 import videojs from 'video.js';
14 import resolveUrl from './resolve-url';
15
16 const Hls = {
17 PlaylistLoader,
18 Playlist,
19 Decrypter,
20 AsyncStream,
21 decrypt,
22 utils,
23 xhr
24 };
25
26 // the desired length of video to maintain in the buffer, in seconds
27 Hls.GOAL_BUFFER_LENGTH = 30;
28
29 // HLS is a source handler, not a tech. Make sure attempts to use it
30 // as one do not cause exceptions.
31 Hls.canPlaySource = function() {
32 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
33 'your player\'s techOrder.');
34 };
35
36 // Search for a likely end time for the segment that was just appened
37 // based on the state of the `buffered` property before and after the
38 // append.
39 // If we found only one such uncommon end-point return it.
40 Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
41 let i;
42 let start;
43 let end;
44 let result = [];
45 let edges = [];
46
47 // In order to qualify as a possible candidate, the end point must:
48 // 1) Not have already existed in the `original` ranges
49 // 2) Not result from the shrinking of a range that already existed
50 // in the `original` ranges
51 // 3) Not be contained inside of a range that existed in `original`
52 let overlapsCurrentEnd = function(span) {
53 return (span[0] <= end && span[1] >= end);
54 };
55
56 if (original) {
57 // Save all the edges in the `original` TimeRanges object
58 for (i = 0; i < original.length; i++) {
59 start = original.start(i);
60 end = original.end(i);
61
62 edges.push([start, end]);
63 }
64 }
65
66 if (update) {
67 // Save any end-points in `update` that are not in the `original`
68 // TimeRanges object
69 for (i = 0; i < update.length; i++) {
70 start = update.start(i);
71 end = update.end(i);
72
73 if (edges.some(overlapsCurrentEnd)) {
74 continue;
75 }
76
77 // at this point it must be a unique non-shrinking end edge
78 result.push(end);
79 }
80 }
81
82 // we err on the side of caution and return null if didn't find
83 // exactly *one* differing end edge in the search above
84 if (result.length !== 1) {
85 return null;
86 }
8 87
9 var 88 return result[0];
10 // A fudge factor to apply to advertised playlist bitrates to account for 89 };
11 // temporary flucations in client bandwidth
12 bandwidthVariance = 1.2,
13 blacklistDuration = 5 * 60 * 1000, // 5 minute blacklist
14 TIME_FUDGE_FACTOR = 1 / 30, // Fudge factor to account for TimeRanges rounding
15 Component = videojs.getComponent('Component'),
16 90
17 // The amount of time to wait between checking the state of the buffer 91 /**
18 bufferCheckInterval = 500, 92 * Whether the browser has built-in HLS support.
93 */
94 Hls.supportsNativeHls = (function() {
95 let video = document.createElement('video');
96 let xMpegUrl;
97 let vndMpeg;
98
99 // native HLS is definitely not supported if HTML5 video isn't
100 if (!videojs.getComponent('Html5').isSupported()) {
101 return false;
102 }
19 103
20 keyFailed, 104 xMpegUrl = video.canPlayType('application/x-mpegURL');
21 resolveUrl; 105 vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
106 return (/probably|maybe/).test(xMpegUrl) ||
107 (/probably|maybe/).test(vndMpeg);
108 }());
109
110 // HLS is a source handler, not a tech. Make sure attempts to use it
111 // as one do not cause exceptions.
112 Hls.isSupported = function() {
113 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
114 'your player\'s techOrder.');
115 };
116
117 /**
118 * A comparator function to sort two playlist object by bandwidth.
119 * @param left {object} a media playlist object
120 * @param right {object} a media playlist object
121 * @return {number} Greater than zero if the bandwidth attribute of
122 * left is greater than the corresponding attribute of right. Less
123 * than zero if the bandwidth of right is greater than left and
124 * exactly zero if the two are equal.
125 */
126 Hls.comparePlaylistBandwidth = function(left, right) {
127 let leftBandwidth;
128 let rightBandwidth;
129
130 if (left.attributes && left.attributes.BANDWIDTH) {
131 leftBandwidth = left.attributes.BANDWIDTH;
132 }
133 leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
134 if (right.attributes && right.attributes.BANDWIDTH) {
135 rightBandwidth = right.attributes.BANDWIDTH;
136 }
137 rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
138
139 return leftBandwidth - rightBandwidth;
140 };
141
142 /**
143 * A comparator function to sort two playlist object by resolution (width).
144 * @param left {object} a media playlist object
145 * @param right {object} a media playlist object
146 * @return {number} Greater than zero if the resolution.width attribute of
147 * left is greater than the corresponding attribute of right. Less
148 * than zero if the resolution.width of right is greater than left and
149 * exactly zero if the two are equal.
150 */
151 Hls.comparePlaylistResolution = function(left, right) {
152 let leftWidth;
153 let rightWidth;
154
155 if (left.attributes &&
156 left.attributes.RESOLUTION &&
157 left.attributes.RESOLUTION.width) {
158 leftWidth = left.attributes.RESOLUTION.width;
159 }
160
161 leftWidth = leftWidth || window.Number.MAX_VALUE;
162
163 if (right.attributes &&
164 right.attributes.RESOLUTION &&
165 right.attributes.RESOLUTION.width) {
166 rightWidth = right.attributes.RESOLUTION.width;
167 }
168
169 rightWidth = rightWidth || window.Number.MAX_VALUE;
170
171 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
172 // have the same media dimensions/ resolution
173 if (leftWidth === rightWidth &&
174 left.attributes.BANDWIDTH &&
175 right.attributes.BANDWIDTH) {
176 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
177 }
178 return leftWidth - rightWidth;
179 };
180
181 // A fudge factor to apply to advertised playlist bitrates to account for
182 // temporary flucations in client bandwidth
183 const bandwidthVariance = 1.2;
184
185 // 5 minute blacklist
186 const blacklistDuration = 5 * 60 * 1000;
187
188 // Fudge factor to account for TimeRanges rounding
189 const TIME_FUDGE_FACTOR = 1 / 30;
190 const Component = videojs.getComponent('Component');
191
192 // The amount of time to wait between checking the state of the buffer
193 const bufferCheckInterval = 500;
22 194
23 // returns true if a key has failed to download within a certain amount of retries 195 // returns true if a key has failed to download within a certain amount of retries
24 keyFailed = function(key) { 196 const keyFailed = function(key) {
25 return key.retries && key.retries >= 2; 197 return key.retries && key.retries >= 2;
26 }; 198 };
27 199
28 videojs.Hls = {}; 200 const parseCodecs = function(codecs) {
29 videojs.HlsHandler = videojs.extend(Component, { 201 let result = {
30 constructor: function(tech, options) { 202 codecCount: 0,
31 var self = this, _player; 203 videoCodec: null,
204 audioProfile: null
205 };
206
207 result.codecCount = codecs.split(',').length;
208 result.codecCount = result.codecCount || 2;
32 209
33 Component.call(this, tech); 210 // parse the video codec but ignore the version
211 result.videoCodec = (/(^|\s|,)+(avc1)[^ ,]*/i).exec(codecs);
212 result.videoCodec = result.videoCodec && result.videoCodec[2];
213
214 // parse the last field of the audio codec
215 result.audioProfile = (/(^|\s|,)+mp4a.\d+\.(\d+)/i).exec(codecs);
216 result.audioProfile = result.audioProfile && result.audioProfile[2];
217
218 return result;
219 };
220
221 const filterBufferedRanges = function(predicate) {
222 return function(time) {
223 let i;
224 let ranges = [];
225 let tech = this.tech_;
226 // !!The order of the next two assignments is important!!
227 // `currentTime` must be equal-to or greater-than the start of the
228 // buffered range. Flash executes out-of-process so, every value can
229 // change behind the scenes from line-to-line. By reading `currentTime`
230 // after `buffered`, we ensure that it is always a current or later
231 // value during playback.
232 let buffered = tech.buffered();
233
234 if (typeof time === 'undefined') {
235 time = tech.currentTime();
236 }
237
238 if (buffered && buffered.length) {
239 // Search for a range containing the play-head
240 for (i = 0; i < buffered.length; i++) {
241 if (predicate(buffered.start(i), buffered.end(i), time)) {
242 ranges.push([buffered.start(i), buffered.end(i)]);
243 }
244 }
245 }
246
247 return videojs.createTimeRanges(ranges);
248 };
249 };
250
251 export default class HlsHandler extends Component {
252 constructor(tech, options) {
253 super(tech);
254 let _player;
34 255
35 // tech.player() is deprecated but setup a reference to HLS for 256 // tech.player() is deprecated but setup a reference to HLS for
36 // backwards-compatibility 257 // backwards-compatibility
...@@ -38,9 +259,9 @@ videojs.HlsHandler = videojs.extend(Component, { ...@@ -38,9 +259,9 @@ videojs.HlsHandler = videojs.extend(Component, {
38 _player = videojs(tech.options_.playerId); 259 _player = videojs(tech.options_.playerId);
39 if (!_player.hls) { 260 if (!_player.hls) {
40 Object.defineProperty(_player, 'hls', { 261 Object.defineProperty(_player, 'hls', {
41 get: function() { 262 get: () => {
42 videojs.log.warn('player.hls is deprecated. Use player.tech.hls instead.'); 263 videojs.log.warn('player.hls is deprecated. Use player.tech.hls instead.');
43 return self; 264 return this;
44 } 265 }
45 }); 266 });
46 } 267 }
...@@ -54,7 +275,8 @@ videojs.HlsHandler = videojs.extend(Component, { ...@@ -54,7 +275,8 @@ videojs.HlsHandler = videojs.extend(Component, {
54 275
55 // start playlist selection at a reasonable bandwidth for 276 // start playlist selection at a reasonable bandwidth for
56 // broadband internet 277 // broadband internet
57 this.bandwidth = options.bandwidth || 4194304; // 0.5 Mbps 278 // 0.5 Mbps
279 this.bandwidth = options.bandwidth || 4194304;
58 this.bytesReceived = 0; 280 this.bytesReceived = 0;
59 281
60 // loadingState_ tracks how far along the buffering process we 282 // loadingState_ tracks how far along the buffering process we
...@@ -81,71 +303,8 @@ videojs.HlsHandler = videojs.extend(Component, { ...@@ -81,71 +303,8 @@ videojs.HlsHandler = videojs.extend(Component, {
81 303
82 this.on(this.tech_, 'play', this.play); 304 this.on(this.tech_, 'play', this.play);
83 } 305 }
84 }); 306 src(src) {
85 307 let oldMediaPlaylist;
86 // HLS is a source handler, not a tech. Make sure attempts to use it
87 // as one do not cause exceptions.
88 videojs.Hls.canPlaySource = function() {
89 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
90 'your player\'s techOrder.');
91 };
92
93 /**
94 * The Source Handler object, which informs video.js what additional
95 * MIME types are supported and sets up playback. It is registered
96 * automatically to the appropriate tech based on the capabilities of
97 * the browser it is running in. It is not necessary to use or modify
98 * this object in normal usage.
99 */
100 videojs.HlsSourceHandler = function(mode) {
101 return {
102 canHandleSource: function(srcObj) {
103 return videojs.HlsSourceHandler.canPlayType(srcObj.type);
104 },
105 handleSource: function(source, tech) {
106 if (mode === 'flash') {
107 // We need to trigger this asynchronously to give others the chance
108 // to bind to the event when a source is set at player creation
109 tech.setTimeout(function() {
110 tech.trigger('loadstart');
111 }, 1);
112 }
113 tech.hls = new videojs.HlsHandler(tech, {
114 source: source,
115 mode: mode
116 });
117 tech.hls.src(source.src);
118 return tech.hls;
119 },
120 canPlayType: function(type) {
121 return videojs.HlsSourceHandler.canPlayType(type);
122 }
123 };
124 };
125
126 videojs.HlsSourceHandler.canPlayType = function(type) {
127 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
128
129 // favor native HLS support if it's available
130 if (videojs.Hls.supportsNativeHls) {
131 return false;
132 }
133 return mpegurlRE.test(type);
134 };
135
136 // register source handlers with the appropriate techs
137 if (videojs.MediaSource.supportsNativeMediaSources()) {
138 videojs.getComponent('Html5').registerSourceHandler(videojs.HlsSourceHandler('html5'));
139 }
140 if (window.Uint8Array) {
141 videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler('flash'));
142 }
143
144 // the desired length of video to maintain in the buffer, in seconds
145 videojs.Hls.GOAL_BUFFER_LENGTH = 30;
146
147 videojs.HlsHandler.prototype.src = function(src) {
148 var oldMediaPlaylist;
149 308
150 // do nothing if the src is falsey 309 // do nothing if the src is falsey
151 if (!src) { 310 if (!src) {
...@@ -158,16 +317,17 @@ videojs.HlsHandler.prototype.src = function(src) { ...@@ -158,16 +317,17 @@ videojs.HlsHandler.prototype.src = function(src) {
158 this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen.bind(this)); 317 this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen.bind(this));
159 318
160 this.options_ = {}; 319 this.options_ = {};
161 if (this.source_.withCredentials !== undefined) { 320 if (typeof this.source_.withCredentials !== 'undefined') {
162 this.options_.withCredentials = this.source_.withCredentials; 321 this.options_.withCredentials = this.source_.withCredentials;
163 } else if (videojs.options.hls) { 322 } else if (videojs.options.hls) {
164 this.options_.withCredentials = videojs.options.hls.withCredentials; 323 this.options_.withCredentials = videojs.options.hls.withCredentials;
165 } 324 }
166 this.playlists = new videojs.Hls.PlaylistLoader(this.source_.src, this.options_.withCredentials); 325 this.playlists = new Hls.PlaylistLoader(this.source_.src,
326 this.options_.withCredentials);
167 327
168 this.tech_.one('canplay', this.setupFirstPlay.bind(this)); 328 this.tech_.one('canplay', this.setupFirstPlay.bind(this));
169 329
170 this.playlists.on('loadedmetadata', function() { 330 this.playlists.on('loadedmetadata', () => {
171 oldMediaPlaylist = this.playlists.media(); 331 oldMediaPlaylist = this.playlists.media();
172 332
173 // if this isn't a live video and preload permits, start 333 // if this isn't a live video and preload permits, start
...@@ -182,14 +342,15 @@ videojs.HlsHandler.prototype.src = function(src) { ...@@ -182,14 +342,15 @@ videojs.HlsHandler.prototype.src = function(src) {
182 this.setupFirstPlay(); 342 this.setupFirstPlay();
183 this.fillBuffer(); 343 this.fillBuffer();
184 this.tech_.trigger('loadedmetadata'); 344 this.tech_.trigger('loadedmetadata');
185 }.bind(this)); 345 });
186 346
187 this.playlists.on('error', function() { 347 this.playlists.on('error', () => {
188 this.blacklistCurrentPlaylist_(this.playlists.error); 348 this.blacklistCurrentPlaylist_(this.playlists.error);
189 }.bind(this)); 349 });
190 350
191 this.playlists.on('loadedplaylist', function() { 351 this.playlists.on('loadedplaylist', () => {
192 var updatedPlaylist = this.playlists.media(), seekable; 352 let updatedPlaylist = this.playlists.media();
353 let seekable;
193 354
194 if (!updatedPlaylist) { 355 if (!updatedPlaylist) {
195 // select the initial variant 356 // select the initial variant
...@@ -207,14 +368,14 @@ videojs.HlsHandler.prototype.src = function(src) { ...@@ -207,14 +368,14 @@ videojs.HlsHandler.prototype.src = function(src) {
207 } 368 }
208 369
209 oldMediaPlaylist = updatedPlaylist; 370 oldMediaPlaylist = updatedPlaylist;
210 }.bind(this)); 371 });
211 372
212 this.playlists.on('mediachange', function() { 373 this.playlists.on('mediachange', () => {
213 this.tech_.trigger({ 374 this.tech_.trigger({
214 type: 'mediachange', 375 type: 'mediachange',
215 bubbles: true 376 bubbles: true
216 }); 377 });
217 }.bind(this)); 378 });
218 379
219 // do nothing if the tech has been disposed already 380 // do nothing if the tech has been disposed already
220 // this can occur if someone sets the src in player.ready(), for instance 381 // this can occur if someone sets the src in player.ready(), for instance
...@@ -223,9 +384,8 @@ videojs.HlsHandler.prototype.src = function(src) { ...@@ -223,9 +384,8 @@ videojs.HlsHandler.prototype.src = function(src) {
223 } 384 }
224 385
225 this.tech_.src(videojs.URL.createObjectURL(this.mediaSource)); 386 this.tech_.src(videojs.URL.createObjectURL(this.mediaSource));
226 }; 387 }
227 388 handleSourceOpen() {
228 videojs.HlsHandler.prototype.handleSourceOpen = function() {
229 // Only attempt to create the source buffer if none already exist. 389 // Only attempt to create the source buffer if none already exist.
230 // handleSourceOpen is also called when we are "re-opening" a source buffer 390 // handleSourceOpen is also called when we are "re-opening" a source buffer
231 // after `endOfStream` has been called (in response to a seek for instance) 391 // after `endOfStream` has been called (in response to a seek for instance)
...@@ -242,83 +402,9 @@ videojs.HlsHandler.prototype.handleSourceOpen = function() { ...@@ -242,83 +402,9 @@ videojs.HlsHandler.prototype.handleSourceOpen = function() {
242 if (this.tech_.autoplay()) { 402 if (this.tech_.autoplay()) {
243 this.play(); 403 this.play();
244 } 404 }
245 }; 405 }
246
247 // Search for a likely end time for the segment that was just appened
248 // based on the state of the `buffered` property before and after the
249 // append.
250 // If we found only one such uncommon end-point return it.
251 videojs.Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
252 var
253 i, start, end,
254 result = [],
255 edges = [],
256 // In order to qualify as a possible candidate, the end point must:
257 // 1) Not have already existed in the `original` ranges
258 // 2) Not result from the shrinking of a range that already existed
259 // in the `original` ranges
260 // 3) Not be contained inside of a range that existed in `original`
261 overlapsCurrentEnd = function(span) {
262 return (span[0] <= end && span[1] >= end);
263 };
264
265 if (original) {
266 // Save all the edges in the `original` TimeRanges object
267 for (i = 0; i < original.length; i++) {
268 start = original.start(i);
269 end = original.end(i);
270
271 edges.push([start, end]);
272 }
273 }
274
275 if (update) {
276 // Save any end-points in `update` that are not in the `original`
277 // TimeRanges object
278 for (i = 0; i < update.length; i++) {
279 start = update.start(i);
280 end = update.end(i);
281
282 if (edges.some(overlapsCurrentEnd)) {
283 continue;
284 }
285
286 // at this point it must be a unique non-shrinking end edge
287 result.push(end);
288 }
289 }
290
291 // we err on the side of caution and return null if didn't find
292 // exactly *one* differing end edge in the search above
293 if (result.length !== 1) {
294 return null;
295 }
296
297 return result[0];
298 };
299
300 var parseCodecs = function(codecs) {
301 var result = {
302 codecCount: 0,
303 videoCodec: null,
304 audioProfile: null
305 };
306
307 result.codecCount = codecs.split(',').length;
308 result.codecCount = result.codecCount || 2;
309
310 // parse the video codec but ignore the version
311 result.videoCodec = /(^|\s|,)+(avc1)[^ ,]*/i.exec(codecs);
312 result.videoCodec = result.videoCodec && result.videoCodec[2];
313
314 // parse the last field of the audio codec
315 result.audioProfile = /(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(codecs);
316 result.audioProfile = result.audioProfile && result.audioProfile[2];
317
318 return result;
319 };
320 406
321 /** 407 /**
322 * Blacklist playlists that are known to be codec or 408 * Blacklist playlists that are known to be codec or
323 * stream-incompatible with the SourceBuffer configuration. For 409 * stream-incompatible with the SourceBuffer configuration. For
324 * instance, Media Source Extensions would cause the video element to 410 * instance, Media Source Extensions would cause the video element to
...@@ -331,13 +417,12 @@ var parseCodecs = function(codecs) { ...@@ -331,13 +417,12 @@ var parseCodecs = function(codecs) {
331 * will be excluded from the default playlist selection algorithm 417 * will be excluded from the default playlist selection algorithm
332 * indefinitely. 418 * indefinitely.
333 */ 419 */
334 videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) { 420 excludeIncompatibleVariants_(media) {
335 var 421 let master = this.playlists.master;
336 master = this.playlists.master, 422 let codecCount = 2;
337 codecCount = 2, 423 let videoCodec = null;
338 videoCodec = null, 424 let audioProfile = null;
339 audioProfile = null, 425 let codecs;
340 codecs;
341 426
342 if (media.attributes && media.attributes.CODECS) { 427 if (media.attributes && media.attributes.CODECS) {
343 codecs = parseCodecs(media.attributes.CODECS); 428 codecs = parseCodecs(media.attributes.CODECS);
...@@ -346,7 +431,7 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) { ...@@ -346,7 +431,7 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) {
346 codecCount = codecs.codecCount; 431 codecCount = codecs.codecCount;
347 } 432 }
348 master.playlists.forEach(function(variant) { 433 master.playlists.forEach(function(variant) {
349 var variantCodecs = { 434 let variantCodecs = {
350 codecCount: 2, 435 codecCount: 2,
351 videoCodec: null, 436 videoCodec: null,
352 audioProfile: null 437 audioProfile: null
...@@ -374,10 +459,11 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) { ...@@ -374,10 +459,11 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) {
374 variant.excludeUntil = Infinity; 459 variant.excludeUntil = Infinity;
375 } 460 }
376 }); 461 });
377 }; 462 }
378 463
379 videojs.HlsHandler.prototype.setupSourceBuffer_ = function() { 464 setupSourceBuffer_() {
380 var media = this.playlists.media(), mimeType; 465 let media = this.playlists.media();
466 let mimeType;
381 467
382 // wait until a media playlist is available and the Media Source is 468 // wait until a media playlist is available and the Media Source is
383 // attached 469 // attached
...@@ -400,16 +486,15 @@ videojs.HlsHandler.prototype.setupSourceBuffer_ = function() { ...@@ -400,16 +486,15 @@ videojs.HlsHandler.prototype.setupSourceBuffer_ = function() {
400 // transition the sourcebuffer to the ended state if we've hit the end of 486 // transition the sourcebuffer to the ended state if we've hit the end of
401 // the playlist 487 // the playlist
402 this.sourceBuffer.addEventListener('updateend', this.updateEndHandler_.bind(this)); 488 this.sourceBuffer.addEventListener('updateend', this.updateEndHandler_.bind(this));
403 }; 489 }
404 490
405 /** 491 /**
406 * Seek to the latest media position if this is a live video and the 492 * Seek to the latest media position if this is a live video and the
407 * player and video are loaded and initialized. 493 * player and video are loaded and initialized.
408 */ 494 */
409 videojs.HlsHandler.prototype.setupFirstPlay = function() { 495 setupFirstPlay() {
410 var seekable, media; 496 let seekable;
411 media = this.playlists.media(); 497 let media = this.playlists.media();
412
413 498
414 // check that everything is ready to begin buffering 499 // check that everything is ready to begin buffering
415 500
...@@ -439,12 +524,12 @@ videojs.HlsHandler.prototype.setupFirstPlay = function() { ...@@ -439,12 +524,12 @@ videojs.HlsHandler.prototype.setupFirstPlay = function() {
439 this.tech_.setCurrentTime(seekable.end(0)); 524 this.tech_.setCurrentTime(seekable.end(0));
440 } 525 }
441 } 526 }
442 }; 527 }
443 528
444 /** 529 /**
445 * Begin playing the video. 530 * Begin playing the video.
446 */ 531 */
447 videojs.HlsHandler.prototype.play = function() { 532 play() {
448 this.loadingState_ = 'segments'; 533 this.loadingState_ = 'segments';
449 534
450 if (this.tech_.ended()) { 535 if (this.tech_.ended()) {
...@@ -462,11 +547,10 @@ videojs.HlsHandler.prototype.play = function() { ...@@ -462,11 +547,10 @@ videojs.HlsHandler.prototype.play = function() {
462 this.tech_.setCurrentTime(this.seekable().start(0)); 547 this.tech_.setCurrentTime(this.seekable().start(0));
463 } 548 }
464 } 549 }
465 }; 550 }
466 551
467 videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) { 552 setCurrentTime(currentTime) {
468 var 553 let buffered = this.findBufferedRange_();
469 buffered = this.findBufferedRange_();
470 554
471 if (!(this.playlists && this.playlists.media())) { 555 if (!(this.playlists && this.playlists.media())) {
472 // return immediately if the metadata is not ready yet 556 // return immediately if the metadata is not ready yet
...@@ -503,18 +587,20 @@ videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) { ...@@ -503,18 +587,20 @@ videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) {
503 587
504 // begin filling the buffer at the new position 588 // begin filling the buffer at the new position
505 this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime)); 589 this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime));
506 }; 590 }
591
592 duration() {
593 let playlists = this.playlists;
507 594
508 videojs.HlsHandler.prototype.duration = function() {
509 var playlists = this.playlists;
510 if (playlists) { 595 if (playlists) {
511 return videojs.Hls.Playlist.duration(playlists.media()); 596 return Hls.Playlist.duration(playlists.media());
512 } 597 }
513 return 0; 598 return 0;
514 }; 599 }
515 600
516 videojs.HlsHandler.prototype.seekable = function() { 601 seekable() {
517 var media, seekable; 602 let media;
603 let seekable;
518 604
519 if (!this.playlists) { 605 if (!this.playlists) {
520 return videojs.createTimeRanges(); 606 return videojs.createTimeRanges();
...@@ -524,7 +610,7 @@ videojs.HlsHandler.prototype.seekable = function() { ...@@ -524,7 +610,7 @@ videojs.HlsHandler.prototype.seekable = function() {
524 return videojs.createTimeRanges(); 610 return videojs.createTimeRanges();
525 } 611 }
526 612
527 seekable = videojs.Hls.Playlist.seekable(media); 613 seekable = Hls.Playlist.seekable(media);
528 if (seekable.length === 0) { 614 if (seekable.length === 0) {
529 return seekable; 615 return seekable;
530 } 616 }
...@@ -534,29 +620,27 @@ videojs.HlsHandler.prototype.seekable = function() { ...@@ -534,29 +620,27 @@ videojs.HlsHandler.prototype.seekable = function() {
534 // fall back to the playlist loader's running estimate of expired 620 // fall back to the playlist loader's running estimate of expired
535 // time 621 // time
536 if (seekable.start(0) === 0) { 622 if (seekable.start(0) === 0) {
537 return videojs.createTimeRanges([[ 623 return videojs.createTimeRanges([[this.playlists.expired_,
538 this.playlists.expired_, 624 this.playlists.expired_ + seekable.end(0)]]);
539 this.playlists.expired_ + seekable.end(0)
540 ]]);
541 } 625 }
542 626
543 // seekable has been calculated based on buffering video data so it 627 // seekable has been calculated based on buffering video data so it
544 // can be returned directly 628 // can be returned directly
545 return seekable; 629 return seekable;
546 }; 630 }
547 631
548 /** 632 /**
549 * Update the player duration 633 * Update the player duration
550 */ 634 */
551 videojs.HlsHandler.prototype.updateDuration = function(playlist) { 635 updateDuration(playlist) {
552 var oldDuration = this.mediaSource.duration, 636 let oldDuration = this.mediaSource.duration;
553 newDuration = videojs.Hls.Playlist.duration(playlist), 637 let newDuration = Hls.Playlist.duration(playlist);
554 setDuration = function() { 638 let setDuration = () => {
555 this.mediaSource.duration = newDuration; 639 this.mediaSource.duration = newDuration;
556 this.tech_.trigger('durationchange'); 640 this.tech_.trigger('durationchange');
557 641
558 this.mediaSource.removeEventListener('sourceopen', setDuration); 642 this.mediaSource.removeEventListener('sourceopen', setDuration);
559 }.bind(this); 643 };
560 644
561 // if the duration has changed, invalidate the cached value 645 // if the duration has changed, invalidate the cached value
562 if (oldDuration !== newDuration) { 646 if (oldDuration !== newDuration) {
...@@ -568,31 +652,31 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) { ...@@ -568,31 +652,31 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) {
568 this.tech_.trigger('durationchange'); 652 this.tech_.trigger('durationchange');
569 } 653 }
570 } 654 }
571 }; 655 }
572 656
573 /** 657 /**
574 * Clear all buffers and reset any state relevant to the current 658 * Clear all buffers and reset any state relevant to the current
575 * source. After this function is called, the tech should be in a 659 * source. After this function is called, the tech should be in a
576 * state suitable for switching to a different video. 660 * state suitable for switching to a different video.
577 */ 661 */
578 videojs.HlsHandler.prototype.resetSrc_ = function() { 662 resetSrc_() {
579 this.cancelSegmentXhr(); 663 this.cancelSegmentXhr();
580 this.cancelKeyXhr(); 664 this.cancelKeyXhr();
581 665
582 if (this.sourceBuffer && this.mediaSource.readyState === 'open') { 666 if (this.sourceBuffer && this.mediaSource.readyState === 'open') {
583 this.sourceBuffer.abort(); 667 this.sourceBuffer.abort();
584 } 668 }
585 }; 669 }
586 670
587 videojs.HlsHandler.prototype.cancelKeyXhr = function() { 671 cancelKeyXhr() {
588 if (this.keyXhr_) { 672 if (this.keyXhr_) {
589 this.keyXhr_.onreadystatechange = null; 673 this.keyXhr_.onreadystatechange = null;
590 this.keyXhr_.abort(); 674 this.keyXhr_.abort();
591 this.keyXhr_ = null; 675 this.keyXhr_ = null;
592 } 676 }
593 }; 677 }
594 678
595 videojs.HlsHandler.prototype.cancelSegmentXhr = function() { 679 cancelSegmentXhr() {
596 if (this.segmentXhr_) { 680 if (this.segmentXhr_) {
597 // Prevent error handler from running. 681 // Prevent error handler from running.
598 this.segmentXhr_.onreadystatechange = null; 682 this.segmentXhr_.onreadystatechange = null;
...@@ -602,12 +686,12 @@ videojs.HlsHandler.prototype.cancelSegmentXhr = function() { ...@@ -602,12 +686,12 @@ videojs.HlsHandler.prototype.cancelSegmentXhr = function() {
602 686
603 // clear out the segment being processed 687 // clear out the segment being processed
604 this.pendingSegment_ = null; 688 this.pendingSegment_ = null;
605 }; 689 }
606 690
607 /** 691 /**
608 * Abort all outstanding work and cleanup. 692 * Abort all outstanding work and cleanup.
609 */ 693 */
610 videojs.HlsHandler.prototype.dispose = function() { 694 dispose() {
611 this.stopCheckingBuffer_(); 695 this.stopCheckingBuffer_();
612 696
613 if (this.playlists) { 697 if (this.playlists) {
...@@ -615,36 +699,36 @@ videojs.HlsHandler.prototype.dispose = function() { ...@@ -615,36 +699,36 @@ videojs.HlsHandler.prototype.dispose = function() {
615 } 699 }
616 700
617 this.resetSrc_(); 701 this.resetSrc_();
618 Component.prototype.dispose.call(this); 702 super.dispose();
619 }; 703 }
620 704
621 /** 705 /**
622 * Chooses the appropriate media playlist based on the current 706 * Chooses the appropriate media playlist based on the current
623 * bandwidth estimate and the player size. 707 * bandwidth estimate and the player size.
624 * @return the highest bitrate playlist less than the currently detected 708 * @return the highest bitrate playlist less than the currently detected
625 * bandwidth, accounting for some amount of bandwidth variance 709 * bandwidth, accounting for some amount of bandwidth variance
626 */ 710 */
627 videojs.HlsHandler.prototype.selectPlaylist = function () { 711 selectPlaylist() {
628 var 712 let effectiveBitrate;
629 effectiveBitrate, 713 let sortedPlaylists = this.playlists.master.playlists.slice();
630 sortedPlaylists = this.playlists.master.playlists.slice(), 714 let bandwidthPlaylists = [];
631 bandwidthPlaylists = [], 715 let now = +new Date();
632 now = +new Date(), 716 let i;
633 i, 717 let variant;
634 variant, 718 let bandwidthBestVariant;
635 bandwidthBestVariant, 719 let resolutionPlusOne;
636 resolutionPlusOne, 720 let resolutionPlusOneAttribute;
637 resolutionBestVariant, 721 let resolutionBestVariant;
638 width, 722 let width;
639 height; 723 let height;
640 724
641 sortedPlaylists.sort(videojs.Hls.comparePlaylistBandwidth); 725 sortedPlaylists.sort(Hls.comparePlaylistBandwidth);
642 726
643 // filter out any playlists that have been excluded due to 727 // filter out any playlists that have been excluded due to
644 // incompatible configurations or playback errors 728 // incompatible configurations or playback errors
645 sortedPlaylists = sortedPlaylists.filter(function(variant) { 729 sortedPlaylists = sortedPlaylists.filter((localVariant) => {
646 if (variant.excludeUntil !== undefined) { 730 if (typeof localVariant.excludeUntil !== 'undefined') {
647 return now >= variant.excludeUntil; 731 return now >= localVariant.excludeUntil;
648 } 732 }
649 return true; 733 return true;
650 }); 734 });
...@@ -676,9 +760,10 @@ videojs.HlsHandler.prototype.selectPlaylist = function () { ...@@ -676,9 +760,10 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
676 i = bandwidthPlaylists.length; 760 i = bandwidthPlaylists.length;
677 761
678 // sort variants by resolution 762 // sort variants by resolution
679 bandwidthPlaylists.sort(videojs.Hls.comparePlaylistResolution); 763 bandwidthPlaylists.sort(Hls.comparePlaylistResolution);
680 764
681 // forget our old variant from above, or we might choose that in high-bandwidth scenarios 765 // forget our old variant from above,
766 // or we might choose that in high-bandwidth scenarios
682 // (this could be the lowest bitrate rendition as we go through all of them above) 767 // (this could be the lowest bitrate rendition as we go through all of them above)
683 variant = null; 768 variant = null;
684 769
...@@ -701,20 +786,22 @@ videojs.HlsHandler.prototype.selectPlaylist = function () { ...@@ -701,20 +786,22 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
701 // since the playlists are sorted, the first variant that has 786 // since the playlists are sorted, the first variant that has
702 // dimensions less than or equal to the player size is the best 787 // dimensions less than or equal to the player size is the best
703 788
704 if (variant.attributes.RESOLUTION.width === width && 789 let variantResolution = variant.attributes.RESOLUTION;
705 variant.attributes.RESOLUTION.height === height) { 790
791 if (variantResolution.width === width &&
792 variantResolution.height === height) {
706 // if we have the exact resolution as the player use it 793 // if we have the exact resolution as the player use it
707 resolutionPlusOne = null; 794 resolutionPlusOne = null;
708 resolutionBestVariant = variant; 795 resolutionBestVariant = variant;
709 break; 796 break;
710 } else if (variant.attributes.RESOLUTION.width < width && 797 } else if (variantResolution.width < width &&
711 variant.attributes.RESOLUTION.height < height) { 798 variantResolution.height < height) {
712 // if both dimensions are less than the player use the 799 // if both dimensions are less than the player use the
713 // previous (next-largest) variant 800 // previous (next-largest) variant
714 break; 801 break;
715 } else if (!resolutionPlusOne || 802 } else if (!resolutionPlusOne ||
716 (variant.attributes.RESOLUTION.width < resolutionPlusOne.attributes.RESOLUTION.width && 803 (variantResolution.width < resolutionPlusOneAttribute.width &&
717 variant.attributes.RESOLUTION.height < resolutionPlusOne.attributes.RESOLUTION.height)) { 804 variantResolution.height < resolutionPlusOneAttribute.height)) {
718 // If we still haven't found a good match keep a 805 // If we still haven't found a good match keep a
719 // reference to the previous variant for the next loop 806 // reference to the previous variant for the next loop
720 // iteration 807 // iteration
...@@ -724,17 +811,21 @@ videojs.HlsHandler.prototype.selectPlaylist = function () { ...@@ -724,17 +811,21 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
724 // the highest bandwidth variant that is just-larger-than 811 // the highest bandwidth variant that is just-larger-than
725 // the video player 812 // the video player
726 resolutionPlusOne = variant; 813 resolutionPlusOne = variant;
814 resolutionPlusOneAttribute = resolutionPlusOne.attributes.RESOLUTION;
727 } 815 }
728 } 816 }
729 817
730 // fallback chain of variants 818 // fallback chain of variants
731 return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; 819 return resolutionPlusOne ||
732 }; 820 resolutionBestVariant ||
821 bandwidthBestVariant ||
822 sortedPlaylists[0];
823 }
733 824
734 /** 825 /**
735 * Periodically request new segments and append video data. 826 * Periodically request new segments and append video data.
736 */ 827 */
737 videojs.HlsHandler.prototype.checkBuffer_ = function() { 828 checkBuffer_() {
738 // calling this method directly resets any outstanding buffer checks 829 // calling this method directly resets any outstanding buffer checks
739 if (this.checkBufferTimeout_) { 830 if (this.checkBufferTimeout_) {
740 window.clearTimeout(this.checkBufferTimeout_); 831 window.clearTimeout(this.checkBufferTimeout_);
...@@ -747,101 +838,44 @@ videojs.HlsHandler.prototype.checkBuffer_ = function() { ...@@ -747,101 +838,44 @@ videojs.HlsHandler.prototype.checkBuffer_ = function() {
747 // wait awhile and try again 838 // wait awhile and try again
748 this.checkBufferTimeout_ = window.setTimeout((this.checkBuffer_).bind(this), 839 this.checkBufferTimeout_ = window.setTimeout((this.checkBuffer_).bind(this),
749 bufferCheckInterval); 840 bufferCheckInterval);
750 }; 841 }
751 842
752 /** 843 /**
753 * Setup a periodic task to request new segments if necessary and 844 * Setup a periodic task to request new segments if necessary and
754 * append bytes into the SourceBuffer. 845 * append bytes into the SourceBuffer.
755 */ 846 */
756 videojs.HlsHandler.prototype.startCheckingBuffer_ = function() { 847 startCheckingBuffer_() {
757 this.checkBuffer_(); 848 this.checkBuffer_();
758 }; 849 }
759 850
760 /** 851 /**
761 * Stop the periodic task requesting new segments and feeding the 852 * Stop the periodic task requesting new segments and feeding the
762 * SourceBuffer. 853 * SourceBuffer.
763 */ 854 */
764 videojs.HlsHandler.prototype.stopCheckingBuffer_ = function() { 855 stopCheckingBuffer_() {
765 if (this.checkBufferTimeout_) { 856 if (this.checkBufferTimeout_) {
766 window.clearTimeout(this.checkBufferTimeout_); 857 window.clearTimeout(this.checkBufferTimeout_);
767 this.checkBufferTimeout_ = null; 858 this.checkBufferTimeout_ = null;
768 } 859 }
769 };
770
771 var filterBufferedRanges = function(predicate) {
772 return function(time) {
773 var
774 i,
775 ranges = [],
776 tech = this.tech_,
777 // !!The order of the next two assignments is important!!
778 // `currentTime` must be equal-to or greater-than the start of the
779 // buffered range. Flash executes out-of-process so, every value can
780 // change behind the scenes from line-to-line. By reading `currentTime`
781 // after `buffered`, we ensure that it is always a current or later
782 // value during playback.
783 buffered = tech.buffered();
784
785
786 if (time === undefined) {
787 time = tech.currentTime();
788 }
789
790 if (buffered && buffered.length) {
791 // Search for a range containing the play-head
792 for (i = 0; i < buffered.length; i++) {
793 if (predicate(buffered.start(i), buffered.end(i), time)) {
794 ranges.push([buffered.start(i), buffered.end(i)]);
795 }
796 } 860 }
797 }
798
799 return videojs.createTimeRanges(ranges);
800 };
801 };
802
803 /**
804 * Attempts to find the buffered TimeRange that contains the specified
805 * time, or where playback is currently happening if no specific time
806 * is specified.
807 * @param time (optional) {number} the time to filter on. Defaults to
808 * currentTime.
809 * @return a new TimeRanges object.
810 */
811 videojs.HlsHandler.prototype.findBufferedRange_ = filterBufferedRanges(function(start, end, time) {
812 return start - TIME_FUDGE_FACTOR <= time &&
813 end + TIME_FUDGE_FACTOR >= time;
814 });
815
816 /**
817 * Returns the TimeRanges that begin at or later than the specified
818 * time.
819 * @param time (optional) {number} the time to filter on. Defaults to
820 * currentTime.
821 * @return a new TimeRanges object.
822 */
823 videojs.HlsHandler.prototype.findNextBufferedRange_ = filterBufferedRanges(function(start, end, time) {
824 return start - TIME_FUDGE_FACTOR >= time;
825 });
826 861
827 /** 862 /**
828 * Determines whether there is enough video data currently in the buffer 863 * Determines whether there is enough video data currently in the buffer
829 * and downloads a new segment if the buffered time is less than the goal. 864 * and downloads a new segment if the buffered time is less than the goal.
830 * @param seekToTime (optional) {number} the offset into the downloaded segment 865 * @param seekToTime (optional) {number} the offset into the downloaded segment
831 * to seek to, in seconds 866 * to seek to, in seconds
832 */ 867 */
833 videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { 868 fillBuffer(mediaIndex) {
834 var 869 let tech = this.tech_;
835 tech = this.tech_, 870 let currentTime = tech.currentTime();
836 currentTime = tech.currentTime(), 871 let hasBufferedContent = (this.tech_.buffered().length !== 0);
837 hasBufferedContent = (this.tech_.buffered().length !== 0), 872 let currentBuffered = this.findBufferedRange_();
838 currentBuffered = this.findBufferedRange_(), 873 let outsideBufferedRanges = !(currentBuffered && currentBuffered.length);
839 outsideBufferedRanges = !(currentBuffered && currentBuffered.length), 874 let currentBufferedEnd = 0;
840 currentBufferedEnd = 0, 875 let bufferedTime = 0;
841 bufferedTime = 0, 876 let segment;
842 segment, 877 let segmentInfo;
843 segmentInfo, 878 let segmentTimestampOffset;
844 segmentTimestampOffset;
845 879
846 // if preload is set to "none", do not download segments until playback is requested 880 // if preload is set to "none", do not download segments until playback is requested
847 if (this.loadingState_ !== 'segments') { 881 if (this.loadingState_ !== 'segments') {
...@@ -864,7 +898,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -864,7 +898,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
864 } 898 }
865 899
866 // if no segments are available, do nothing 900 // if no segments are available, do nothing
867 if (this.playlists.state === "HAVE_NOTHING" || 901 if (this.playlists.state === 'HAVE_NOTHING' ||
868 !this.playlists.media() || 902 !this.playlists.media() ||
869 !this.playlists.media().segments) { 903 !this.playlists.media().segments) {
870 return; 904 return;
...@@ -875,7 +909,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -875,7 +909,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
875 return; 909 return;
876 } 910 }
877 911
878 if (mediaIndex === undefined) { 912 if (typeof mediaIndex === 'undefined') {
879 if (currentBuffered && currentBuffered.length) { 913 if (currentBuffered && currentBuffered.length) {
880 currentBufferedEnd = currentBuffered.end(0); 914 currentBufferedEnd = currentBuffered.end(0);
881 mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd); 915 mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd);
...@@ -883,7 +917,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -883,7 +917,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
883 917
884 // if there is plenty of content in the buffer and we're not 918 // if there is plenty of content in the buffer and we're not
885 // seeking, relax for awhile 919 // seeking, relax for awhile
886 if (bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { 920 if (bufferedTime >= Hls.GOAL_BUFFER_LENGTH) {
887 return; 921 return;
888 } 922 }
889 } else { 923 } else {
...@@ -910,12 +944,12 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -910,12 +944,12 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
910 // resolve the segment URL relative to the playlist 944 // resolve the segment URL relative to the playlist
911 uri: this.playlistUriToUrl(segment.uri), 945 uri: this.playlistUriToUrl(segment.uri),
912 // the segment's mediaIndex & mediaSequence at the time it was requested 946 // the segment's mediaIndex & mediaSequence at the time it was requested
913 mediaIndex: mediaIndex, 947 mediaIndex,
914 mediaSequence: this.playlists.media().mediaSequence, 948 mediaSequence: this.playlists.media().mediaSequence,
915 // the segment's playlist 949 // the segment's playlist
916 playlist: this.playlists.media(), 950 playlist: this.playlists.media(),
917 // The state of the buffer when this segment was requested 951 // The state of the buffer when this segment was requested
918 currentBufferedEnd: currentBufferedEnd, 952 currentBufferedEnd,
919 // unencrypted bytes of the segment 953 // unencrypted bytes of the segment
920 bytes: null, 954 bytes: null,
921 // when a key is defined for this segment, the encrypted bytes 955 // when a key is defined for this segment, the encrypted bytes
...@@ -932,7 +966,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -932,7 +966,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
932 }; 966 };
933 967
934 if (mediaIndex > 0) { 968 if (mediaIndex > 0) {
935 segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, 969 segmentTimestampOffset = Hls.Playlist.duration(segmentInfo.playlist,
936 segmentInfo.playlist.mediaSequence + mediaIndex) + this.playlists.expired_; 970 segmentInfo.playlist.mediaSequence + mediaIndex) + this.playlists.expired_;
937 } 971 }
938 972
...@@ -955,43 +989,50 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) { ...@@ -955,43 +989,50 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
955 } 989 }
956 990
957 this.loadSegment(segmentInfo); 991 this.loadSegment(segmentInfo);
958 }; 992 }
993
994 playlistUriToUrl(segmentRelativeUrl) {
995 let playListUrl;
959 996
960 videojs.HlsHandler.prototype.playlistUriToUrl = function(segmentRelativeUrl) {
961 var playListUrl;
962 // resolve the segment URL relative to the playlist 997 // resolve the segment URL relative to the playlist
963 if (this.playlists.media().uri === this.source_.src) { 998 if (this.playlists.media().uri === this.source_.src) {
964 playListUrl = resolveUrl(this.source_.src, segmentRelativeUrl); 999 playListUrl = resolveUrl(this.source_.src, segmentRelativeUrl);
965 } else { 1000 } else {
966 playListUrl = resolveUrl(resolveUrl(this.source_.src, this.playlists.media().uri || ''), segmentRelativeUrl); 1001 playListUrl =
1002 resolveUrl(resolveUrl(this.source_.src, this.playlists.media().uri || ''),
1003 segmentRelativeUrl);
967 } 1004 }
968 return playListUrl; 1005 return playListUrl;
969 }; 1006 }
970 1007
971 /* Turns segment byterange into a string suitable for use in 1008 /*
1009 * Turns segment byterange into a string suitable for use in
972 * HTTP Range requests 1010 * HTTP Range requests
973 */ 1011 */
974 videojs.HlsHandler.prototype.byterangeStr_ = function(byterange) { 1012 byterangeStr_(byterange) {
975 var byterangeStart, byterangeEnd; 1013 let byterangeStart;
1014 let byterangeEnd;
976 1015
977 // `byterangeEnd` is one less than `offset + length` because the HTTP range 1016 // `byterangeEnd` is one less than `offset + length` because the HTTP range
978 // header uses inclusive ranges 1017 // header uses inclusive ranges
979 byterangeEnd = byterange.offset + byterange.length - 1; 1018 byterangeEnd = byterange.offset + byterange.length - 1;
980 byterangeStart = byterange.offset; 1019 byterangeStart = byterange.offset;
981 return "bytes=" + byterangeStart + "-" + byterangeEnd; 1020 return 'bytes=' + byterangeStart + '-' + byterangeEnd;
982 }; 1021 }
983 1022
984 /* Defines headers for use in the xhr request for a particular segment. 1023 /*
1024 * Defines headers for use in the xhr request for a particular segment.
985 */ 1025 */
986 videojs.HlsHandler.prototype.segmentXhrHeaders_ = function(segment) { 1026 segmentXhrHeaders_(segment) {
987 var headers = {}; 1027 let headers = {};
1028
988 if ('byterange' in segment) { 1029 if ('byterange' in segment) {
989 headers['Range'] = this.byterangeStr_(segment.byterange); 1030 headers.Range = this.byterangeStr_(segment.byterange);
990 } 1031 }
991 return headers; 1032 return headers;
992 }; 1033 }
993 1034
994 /* 1035 /*
995 * Sets `bandwidth`, `segmentXhrTime`, and appends to the `bytesReceived. 1036 * Sets `bandwidth`, `segmentXhrTime`, and appends to the `bytesReceived.
996 * Expects an object with: 1037 * Expects an object with:
997 * * `roundTripTime` - the round trip time for the request we're setting the time for 1038 * * `roundTripTime` - the round trip time for the request we're setting the time for
...@@ -999,22 +1040,23 @@ videojs.HlsHandler.prototype.segmentXhrHeaders_ = function(segment) { ...@@ -999,22 +1040,23 @@ videojs.HlsHandler.prototype.segmentXhrHeaders_ = function(segment) {
999 * * `bytesReceived` - amount of bytes downloaded 1040 * * `bytesReceived` - amount of bytes downloaded
1000 * `bandwidth` is the only required property. 1041 * `bandwidth` is the only required property.
1001 */ 1042 */
1002 videojs.HlsHandler.prototype.setBandwidth = function(xhr) { 1043 setBandwidth(localXhr) {
1003 // calculate the download bandwidth 1044 // calculate the download bandwidth
1004 this.segmentXhrTime = xhr.roundTripTime; 1045 this.segmentXhrTime = localXhr.roundTripTime;
1005 this.bandwidth = xhr.bandwidth; 1046 this.bandwidth = localXhr.bandwidth;
1006 this.bytesReceived += xhr.bytesReceived || 0; 1047 this.bytesReceived += localXhr.bytesReceived || 0;
1007 1048
1008 this.tech_.trigger('bandwidthupdate'); 1049 this.tech_.trigger('bandwidthupdate');
1009 }; 1050 }
1010 1051
1011 /* 1052 /*
1012 * Blacklists a playlist when an error occurs for a set amount of time 1053 * Blacklists a playlist when an error occurs for a set amount of time
1013 * making it unavailable for selection by the rendition selection algorithm 1054 * making it unavailable for selection by the rendition selection algorithm
1014 * and then forces a new playlist (rendition) selection. 1055 * and then forces a new playlist (rendition) selection.
1015 */ 1056 */
1016 videojs.HlsHandler.prototype.blacklistCurrentPlaylist_ = function(error) { 1057 blacklistCurrentPlaylist_(error) {
1017 var currentPlaylist, nextPlaylist; 1058 let currentPlaylist;
1059 let nextPlaylist;
1018 1060
1019 // If the `error` was generated by the playlist loader, it will contain 1061 // If the `error` was generated by the playlist loader, it will contain
1020 // the playlist we were trying to load (but failed) and that should be 1062 // the playlist we were trying to load (but failed) and that should be
...@@ -1036,26 +1078,27 @@ videojs.HlsHandler.prototype.blacklistCurrentPlaylist_ = function(error) { ...@@ -1036,26 +1078,27 @@ videojs.HlsHandler.prototype.blacklistCurrentPlaylist_ = function(error) {
1036 nextPlaylist = this.selectPlaylist(); 1078 nextPlaylist = this.selectPlaylist();
1037 1079
1038 if (nextPlaylist) { 1080 if (nextPlaylist) {
1039 videojs.log.warn('Problem encountered with the current HLS playlist. Switching to another playlist.'); 1081 videojs.log.warn('Problem encountered with the current ' +
1082 'HLS playlist. Switching to another playlist.');
1040 1083
1041 return this.playlists.media(nextPlaylist); 1084 return this.playlists.media(nextPlaylist);
1042 } else { 1085 }
1043 videojs.log.warn('Problem encountered with the current HLS playlist. No suitable alternatives found.'); 1086 videojs.log.warn('Problem encountered with the current ' +
1087 'HLS playlist. No suitable alternatives found.');
1044 // We have no more playlists we can select so we must fail 1088 // We have no more playlists we can select so we must fail
1045 this.error = error; 1089 this.error = error;
1046 return this.mediaSource.endOfStream('network'); 1090 return this.mediaSource.endOfStream('network');
1047 } 1091 }
1048 };
1049 1092
1050 videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) { 1093 loadSegment(segmentInfo) {
1051 var 1094 let segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
1052 self = this, 1095 let removeToTime = 0;
1053 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex], 1096 let seekable = this.seekable();
1054 removeToTime = 0,
1055 seekable = this.seekable();
1056 1097
1057 // Chrome has a hard limit of 150mb of buffer and a very conservative "garbage collector" 1098 // Chrome has a hard limit of 150mb of
1058 // We manually clear out the old buffer to ensure we don't trigger the QuotaExceeded error 1099 // buffer and a very conservative "garbage collector"
1100 // We manually clear out the old buffer to ensure
1101 // we don't trigger the QuotaExceeded error
1059 // on the source buffer during subsequent appends 1102 // on the source buffer during subsequent appends
1060 if (this.sourceBuffer && !this.sourceBuffer.updating) { 1103 if (this.sourceBuffer && !this.sourceBuffer.updating) {
1061 // If we have a seekable range use that as the limit for what can be removed safely 1104 // If we have a seekable range use that as the limit for what can be removed safely
...@@ -1077,7 +1120,7 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) { ...@@ -1077,7 +1120,7 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
1077 } 1120 }
1078 1121
1079 // request the next segment 1122 // request the next segment
1080 this.segmentXhr_ = videojs.Hls.xhr({ 1123 this.segmentXhr_ = Hls.xhr({
1081 uri: segmentInfo.uri, 1124 uri: segmentInfo.uri,
1082 responseType: 'arraybuffer', 1125 responseType: 'arraybuffer',
1083 withCredentials: this.source_.withCredentials, 1126 withCredentials: this.source_.withCredentials,
...@@ -1086,25 +1129,25 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) { ...@@ -1086,25 +1129,25 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
1086 // decrease in network performance or a server issue. 1129 // decrease in network performance or a server issue.
1087 timeout: (segment.duration * 1.5) * 1000, 1130 timeout: (segment.duration * 1.5) * 1000,
1088 headers: this.segmentXhrHeaders_(segment) 1131 headers: this.segmentXhrHeaders_(segment)
1089 }, function(error, request) { 1132 }, (error, request) => {
1090 // This is a timeout of a previously aborted segment request 1133 // This is a timeout of a previously aborted segment request
1091 // so simply ignore it 1134 // so simply ignore it
1092 if (!self.segmentXhr_ || request !== self.segmentXhr_) { 1135 if (!this.segmentXhr_ || request !== this.segmentXhr_) {
1093 return; 1136 return;
1094 } 1137 }
1095 1138
1096 // the segment request is no longer outstanding 1139 // the segment request is no longer outstanding
1097 self.segmentXhr_ = null; 1140 this.segmentXhr_ = null;
1098 1141
1099 // if a segment request times out, we may have better luck with another playlist 1142 // if a segment request times out, we may have better luck with another playlist
1100 if (request.timedout) { 1143 if (request.timedout) {
1101 self.bandwidth = 1; 1144 this.bandwidth = 1;
1102 return self.playlists.media(self.selectPlaylist()); 1145 return this.playlists.media(this.selectPlaylist());
1103 } 1146 }
1104 1147
1105 // otherwise, trigger a network error 1148 // otherwise, trigger a network error
1106 if (!request.aborted && error) { 1149 if (!request.aborted && error) {
1107 return self.blacklistCurrentPlaylist_({ 1150 return this.blacklistCurrentPlaylist_({
1108 status: request.status, 1151 status: request.status,
1109 message: 'HLS segment request error at URL: ' + segmentInfo.uri, 1152 message: 'HLS segment request error at URL: ' + segmentInfo.uri,
1110 code: (request.status >= 500) ? 4 : 2 1153 code: (request.status >= 500) ? 4 : 2
...@@ -1116,8 +1159,8 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) { ...@@ -1116,8 +1159,8 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
1116 return; 1159 return;
1117 } 1160 }
1118 1161
1119 self.lastSegmentLoaded_ = segment; 1162 this.lastSegmentLoaded_ = segment;
1120 self.setBandwidth(request); 1163 this.setBandwidth(request);
1121 1164
1122 if (segment.key) { 1165 if (segment.key) {
1123 segmentInfo.encryptedBytes = new Uint8Array(request.response); 1166 segmentInfo.encryptedBytes = new Uint8Array(request.response);
...@@ -1125,28 +1168,26 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) { ...@@ -1125,28 +1168,26 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
1125 segmentInfo.bytes = new Uint8Array(request.response); 1168 segmentInfo.bytes = new Uint8Array(request.response);
1126 } 1169 }
1127 1170
1128 self.pendingSegment_ = segmentInfo; 1171 this.pendingSegment_ = segmentInfo;
1129 1172
1130 self.tech_.trigger('progress'); 1173 this.tech_.trigger('progress');
1131 self.drainBuffer(); 1174 this.drainBuffer();
1132 1175
1133 // figure out what stream the next segment should be downloaded from 1176 // figure out what stream the next segment should be downloaded from
1134 // with the updated bandwidth information 1177 // with the updated bandwidth information
1135 self.playlists.media(self.selectPlaylist()); 1178 this.playlists.media(this.selectPlaylist());
1136 }); 1179 });
1137 1180
1138 }; 1181 }
1139 1182
1140 videojs.HlsHandler.prototype.drainBuffer = function() { 1183 drainBuffer() {
1141 var 1184 let segmentInfo;
1142 segmentInfo, 1185 let mediaIndex;
1143 mediaIndex, 1186 let playlist;
1144 playlist, 1187 let bytes;
1145 offset, 1188 let segment;
1146 bytes, 1189 let decrypter;
1147 segment, 1190 let segIv;
1148 decrypter,
1149 segIv;
1150 1191
1151 // if the buffer is empty or the source buffer hasn't been created 1192 // if the buffer is empty or the source buffer hasn't been created
1152 // yet, do nothing 1193 // yet, do nothing
...@@ -1169,7 +1210,6 @@ videojs.HlsHandler.prototype.drainBuffer = function() { ...@@ -1169,7 +1210,6 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
1169 segmentInfo = this.pendingSegment_; 1210 segmentInfo = this.pendingSegment_;
1170 mediaIndex = segmentInfo.mediaIndex; 1211 mediaIndex = segmentInfo.mediaIndex;
1171 playlist = segmentInfo.playlist; 1212 playlist = segmentInfo.playlist;
1172 offset = segmentInfo.offset;
1173 bytes = segmentInfo.bytes; 1213 bytes = segmentInfo.bytes;
1174 segment = playlist.segments[mediaIndex]; 1214 segment = playlist.segments[mediaIndex];
1175 1215
...@@ -1183,30 +1223,30 @@ videojs.HlsHandler.prototype.drainBuffer = function() { ...@@ -1183,30 +1223,30 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
1183 code: 4 1223 code: 4
1184 }); 1224 });
1185 } else if (!segment.key.bytes) { 1225 } else if (!segment.key.bytes) {
1186
1187 // waiting for the key bytes, try again later 1226 // waiting for the key bytes, try again later
1188 return; 1227 return;
1189 } else if (segmentInfo.decrypter) { 1228 } else if (segmentInfo.decrypter) {
1190
1191 // decryption is in progress, try again later 1229 // decryption is in progress, try again later
1192 return; 1230 return;
1193 } else { 1231 }
1194
1195 // if the media sequence is greater than 2^32, the IV will be incorrect 1232 // if the media sequence is greater than 2^32, the IV will be incorrect
1196 // assuming 10s segments, that would be about 1300 years 1233 // assuming 10s segments, that would be about 1300 years
1197 segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]); 1234 segIv = segment.key.iv ||
1235 new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]);
1198 1236
1199 // create a decrypter to incrementally decrypt the segment 1237 // create a decrypter to incrementally decrypt the segment
1200 decrypter = new videojs.Hls.Decrypter(segmentInfo.encryptedBytes, 1238 decrypter = new Hls.Decrypter(segmentInfo.encryptedBytes,
1201 segment.key.bytes, 1239 segment.key.bytes,
1202 segIv, 1240 segIv,
1203 function(err, bytes) { 1241 function(error, localBytes) {
1204 segmentInfo.bytes = bytes; 1242 if (error) {
1243 videojs.log.warn(error);
1244 }
1245 segmentInfo.bytes = localBytes;
1205 }); 1246 });
1206 segmentInfo.decrypter = decrypter; 1247 segmentInfo.decrypter = decrypter;
1207 return; 1248 return;
1208 } 1249 }
1209 }
1210 1250
1211 this.pendingSegment_.buffered = this.tech_.buffered(); 1251 this.pendingSegment_.buffered = this.tech_.buffered();
1212 1252
...@@ -1216,18 +1256,17 @@ videojs.HlsHandler.prototype.drainBuffer = function() { ...@@ -1216,18 +1256,17 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
1216 1256
1217 // the segment is asynchronously added to the current buffered data 1257 // the segment is asynchronously added to the current buffered data
1218 this.sourceBuffer.appendBuffer(bytes); 1258 this.sourceBuffer.appendBuffer(bytes);
1219 }; 1259 }
1220 1260
1221 videojs.HlsHandler.prototype.updateEndHandler_ = function () { 1261 updateEndHandler_() {
1222 var 1262 let segmentInfo = this.pendingSegment_;
1223 segmentInfo = this.pendingSegment_, 1263 let segment;
1224 segment, 1264 let segments;
1225 segments, 1265 let playlist;
1226 playlist, 1266 let currentMediaIndex;
1227 currentMediaIndex, 1267 let currentBuffered;
1228 currentBuffered, 1268 let seekable;
1229 seekable, 1269 let timelineUpdate;
1230 timelineUpdate;
1231 1270
1232 this.pendingSegment_ = null; 1271 this.pendingSegment_ = null;
1233 1272
...@@ -1238,7 +1277,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () { ...@@ -1238,7 +1277,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
1238 1277
1239 playlist = this.playlists.media(); 1278 playlist = this.playlists.media();
1240 segments = playlist.segments; 1279 segments = playlist.segments;
1241 currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence); 1280 currentMediaIndex = segmentInfo.mediaIndex +
1281 (segmentInfo.mediaSequence - playlist.mediaSequence);
1242 currentBuffered = this.findBufferedRange_(); 1282 currentBuffered = this.findBufferedRange_();
1243 1283
1244 // if we switched renditions don't try to add segment timeline 1284 // if we switched renditions don't try to add segment timeline
...@@ -1260,16 +1300,17 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () { ...@@ -1260,16 +1300,17 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
1260 currentBuffered.length === 0) { 1300 currentBuffered.length === 0) {
1261 if (seekable.length && 1301 if (seekable.length &&
1262 this.tech_.currentTime() < seekable.start(0)) { 1302 this.tech_.currentTime() < seekable.start(0)) {
1263 var next = this.findNextBufferedRange_(); 1303 let next = this.findNextBufferedRange_();
1304
1264 if (next.length) { 1305 if (next.length) {
1265 videojs.log('tried seeking to', this.tech_.currentTime(), 'but that was too early, retrying at', next.start(0)); 1306 videojs.log('tried seeking to', this.tech_.currentTime(),
1307 'but that was too early, retrying at', next.start(0));
1266 this.tech_.setCurrentTime(next.start(0) + TIME_FUDGE_FACTOR); 1308 this.tech_.setCurrentTime(next.start(0) + TIME_FUDGE_FACTOR);
1267 } 1309 }
1268 } 1310 }
1269 } 1311 }
1270 1312
1271 1313 timelineUpdate = Hls.findSoleUncommonTimeRangesEnd_(segmentInfo.buffered,
1272 timelineUpdate = videojs.Hls.findSoleUncommonTimeRangesEnd_(segmentInfo.buffered,
1273 this.tech_.buffered()); 1314 this.tech_.buffered());
1274 1315
1275 if (timelineUpdate && segment) { 1316 if (timelineUpdate && segment) {
...@@ -1299,42 +1340,44 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () { ...@@ -1299,42 +1340,44 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
1299 // improves subsequent media index calculations. 1340 // improves subsequent media index calculations.
1300 this.fillBuffer(currentMediaIndex + 1); 1341 this.fillBuffer(currentMediaIndex + 1);
1301 return; 1342 return;
1302 }; 1343 }
1303 1344
1304 /** 1345 /**
1305 * Attempt to retrieve the key for a particular media segment. 1346 * Attempt to retrieve the key for a particular media segment.
1306 */ 1347 */
1307 videojs.HlsHandler.prototype.fetchKey_ = function(segment) { 1348 fetchKey_(segment) {
1308 var key, self, settings, receiveKey; 1349 let key;
1350 let settings;
1351 let receiveKey;
1309 1352
1310 // if there is a pending XHR or no segments, don't do anything 1353 // if there is a pending XHR or no segments, don't do anything
1311 if (this.keyXhr_) { 1354 if (this.keyXhr_) {
1312 return; 1355 return;
1313 } 1356 }
1314 1357
1315 self = this;
1316 settings = this.options_; 1358 settings = this.options_;
1317 1359
1318 /** 1360 /**
1319 * Handle a key XHR response. 1361 * Handle a key XHR response.
1320 */ 1362 */
1321 receiveKey = function(key) { 1363 receiveKey = (keyRecieved) => {
1322 return function(error, request) { 1364 return (error, request) => {
1323 var view; 1365 let view;
1324 self.keyXhr_ = null; 1366
1367 this.keyXhr_ = null;
1325 1368
1326 if (error || !request.response || request.response.byteLength !== 16) { 1369 if (error || !request.response || request.response.byteLength !== 16) {
1327 key.retries = key.retries || 0; 1370 keyRecieved.retries = keyRecieved.retries || 0;
1328 key.retries++; 1371 keyRecieved.retries++;
1329 if (!request.aborted) { 1372 if (!request.aborted) {
1330 // try fetching again 1373 // try fetching again
1331 self.fetchKey_(segment); 1374 this.fetchKey_(segment);
1332 } 1375 }
1333 return; 1376 return;
1334 } 1377 }
1335 1378
1336 view = new DataView(request.response); 1379 view = new DataView(request.response);
1337 key.bytes = new Uint32Array([ 1380 keyRecieved.bytes = new Uint32Array([
1338 view.getUint32(0), 1381 view.getUint32(0),
1339 view.getUint32(4), 1382 view.getUint32(4),
1340 view.getUint32(8), 1383 view.getUint32(8),
...@@ -1342,7 +1385,7 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) { ...@@ -1342,7 +1385,7 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
1342 ]); 1385 ]);
1343 1386
1344 // check to see if this allows us to make progress buffering now 1387 // check to see if this allows us to make progress buffering now
1345 self.checkBuffer_(); 1388 this.checkBuffer_();
1346 }; 1389 };
1347 }; 1390 };
1348 1391
...@@ -1355,135 +1398,105 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) { ...@@ -1355,135 +1398,105 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
1355 1398
1356 // request the key if the retry limit hasn't been reached 1399 // request the key if the retry limit hasn't been reached
1357 if (!key.bytes && !keyFailed(key)) { 1400 if (!key.bytes && !keyFailed(key)) {
1358 this.keyXhr_ = videojs.Hls.xhr({ 1401 this.keyXhr_ = Hls.xhr({
1359 uri: this.playlistUriToUrl(key.uri), 1402 uri: this.playlistUriToUrl(key.uri),
1360 responseType: 'arraybuffer', 1403 responseType: 'arraybuffer',
1361 withCredentials: settings.withCredentials 1404 withCredentials: settings.withCredentials
1362 }, receiveKey(key)); 1405 }, receiveKey(key));
1363 return; 1406 return;
1364 } 1407 }
1365 }; 1408 }
1409 }
1366 1410
1367 /** 1411 /**
1368 * Whether the browser has built-in HLS support. 1412 * Attempts to find the buffered TimeRange that contains the specified
1413 * time, or where playback is currently happening if no specific time
1414 * is specified.
1415 * @param time (optional) {number} the time to filter on. Defaults to
1416 * currentTime.
1417 * @return a new TimeRanges object.
1369 */ 1418 */
1370 videojs.Hls.supportsNativeHls = (function() { 1419 HlsHandler.prototype.findBufferedRange_ =
1371 var 1420 filterBufferedRanges(function(start, end, time) {
1372 video = document.createElement('video'), 1421 return start - TIME_FUDGE_FACTOR <= time &&
1373 xMpegUrl, 1422 end + TIME_FUDGE_FACTOR >= time;
1374 vndMpeg; 1423 });
1375
1376 // native HLS is definitely not supported if HTML5 video isn't
1377 if (!videojs.getComponent('Html5').isSupported()) {
1378 return false;
1379 }
1380
1381 xMpegUrl = video.canPlayType('application/x-mpegURL');
1382 vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
1383 return (/probably|maybe/).test(xMpegUrl) ||
1384 (/probably|maybe/).test(vndMpeg);
1385 })();
1386
1387 // HLS is a source handler, not a tech. Make sure attempts to use it
1388 // as one do not cause exceptions.
1389 videojs.Hls.isSupported = function() {
1390 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
1391 'your player\'s techOrder.');
1392 };
1393
1394 /** 1424 /**
1395 * A comparator function to sort two playlist object by bandwidth. 1425 * Returns the TimeRanges that begin at or later than the specified
1396 * @param left {object} a media playlist object 1426 * time.
1397 * @param right {object} a media playlist object 1427 * @param time (optional) {number} the time to filter on. Defaults to
1398 * @return {number} Greater than zero if the bandwidth attribute of 1428 * currentTime.
1399 * left is greater than the corresponding attribute of right. Less 1429 * @return a new TimeRanges object.
1400 * than zero if the bandwidth of right is greater than left and
1401 * exactly zero if the two are equal.
1402 */ 1430 */
1403 videojs.Hls.comparePlaylistBandwidth = function(left, right) { 1431 HlsHandler.prototype.findNextBufferedRange_ =
1404 var leftBandwidth, rightBandwidth; 1432 filterBufferedRanges(function(start, end, time) {
1405 if (left.attributes && left.attributes.BANDWIDTH) { 1433 return start - TIME_FUDGE_FACTOR >= time;
1406 leftBandwidth = left.attributes.BANDWIDTH; 1434 });
1407 }
1408 leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
1409 if (right.attributes && right.attributes.BANDWIDTH) {
1410 rightBandwidth = right.attributes.BANDWIDTH;
1411 }
1412 rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
1413
1414 return leftBandwidth - rightBandwidth;
1415 };
1416 1435
1417 /** 1436 /**
1418 * A comparator function to sort two playlist object by resolution (width). 1437 * The Source Handler object, which informs video.js what additional
1419 * @param left {object} a media playlist object 1438 * MIME types are supported and sets up playback. It is registered
1420 * @param right {object} a media playlist object 1439 * automatically to the appropriate tech based on the capabilities of
1421 * @return {number} Greater than zero if the resolution.width attribute of 1440 * the browser it is running in. It is not necessary to use or modify
1422 * left is greater than the corresponding attribute of right. Less 1441 * this object in normal usage.
1423 * than zero if the resolution.width of right is greater than left and
1424 * exactly zero if the two are equal.
1425 */ 1442 */
1426 videojs.Hls.comparePlaylistResolution = function(left, right) { 1443 const HlsSourceHandler = function(mode) {
1427 var leftWidth, rightWidth; 1444 return {
1428 1445 canHandleSource(srcObj) {
1429 if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 1446 return HlsSourceHandler.canPlayType(srcObj.type);
1430 leftWidth = left.attributes.RESOLUTION.width; 1447 },
1448 handleSource(source, tech) {
1449 if (mode === 'flash') {
1450 // We need to trigger this asynchronously to give others the chance
1451 // to bind to the event when a source is set at player creation
1452 tech.setTimeout(function() {
1453 tech.trigger('loadstart');
1454 }, 1);
1431 } 1455 }
1432 1456 tech.hls = new HlsHandler(tech, {
1433 leftWidth = leftWidth || window.Number.MAX_VALUE; 1457 source,
1434 1458 mode
1435 if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 1459 });
1436 rightWidth = right.attributes.RESOLUTION.width; 1460 tech.hls.src(source.src);
1461 return tech.hls;
1462 },
1463 canPlayType(type) {
1464 return HlsSourceHandler.canPlayType(type);
1437 } 1465 }
1466 };
1467 };
1438 1468
1439 rightWidth = rightWidth || window.Number.MAX_VALUE; 1469 HlsSourceHandler.canPlayType = function(type) {
1470 let mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
1440 1471
1441 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 1472 // favor native HLS support if it's available
1442 // have the same media dimensions/ resolution 1473 if (Hls.supportsNativeHls) {
1443 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 1474 return false;
1444 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
1445 } else {
1446 return leftWidth - rightWidth;
1447 } 1475 }
1476 return mpegurlRE.test(type);
1448 }; 1477 };
1449 1478
1450 /** 1479 if (typeof videojs.MediaSource === 'undefined' ||
1451 * Constructs a new URI by interpreting a path relative to another 1480 typeof videojs.URL === 'undefined') {
1452 * URI. 1481 videojs.MediaSource = MediaSource;
1453 * @param basePath {string} a relative or absolute URI 1482 videojs.URL = URL;
1454 * @param path {string} a path part to combine with the base 1483 }
1455 * @return {string} a URI that is equivalent to composing `base` 1484
1456 * with `path` 1485 // register source handlers with the appropriate techs
1457 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 1486 if (MediaSource.supportsNativeMediaSources()) {
1458 */ 1487 videojs.getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5'));
1459 resolveUrl = videojs.Hls.resolveUrl = function(basePath, path) { 1488 }
1460 // use the base element to get the browser to handle URI resolution 1489 if (window.Uint8Array) {
1461 var 1490 videojs.getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash'));
1462 oldBase = document.querySelector('base'), 1491 }
1463 docHead = document.querySelector('head'),
1464 a = document.createElement('a'),
1465 base = oldBase,
1466 oldHref,
1467 result;
1468
1469 // prep the document
1470 if (oldBase) {
1471 oldHref = oldBase.href;
1472 } else {
1473 base = docHead.appendChild(document.createElement('base'));
1474 }
1475 1492
1476 base.href = basePath; 1493 videojs.HlsHandler = HlsHandler;
1477 a.href = path; 1494 videojs.HlsSourceHandler = HlsSourceHandler;
1478 result = a.href; 1495 videojs.Hls = Hls;
1496 videojs.m3u8 = m3u8;
1479 1497
1480 // clean up 1498 export default {
1481 if (oldBase) { 1499 Hls,
1482 oldBase.href = oldHref; 1500 HlsHandler,
1483 } else { 1501 HlsSourceHandler
1484 docHead.removeChild(base);
1485 }
1486 return result;
1487 }; 1502 };
1488
1489 })(window, window.videojs, document);
......
...@@ -90,7 +90,6 @@ function() { ...@@ -90,7 +90,6 @@ function() {
90 90
91 }); 91 });
92 92
93
94 QUnit.module('Incremental Processing', { 93 QUnit.module('Incremental Processing', {
95 beforeEach() { 94 beforeEach() {
96 this.clock = sinon.useFakeTimers(); 95 this.clock = sinon.useFakeTimers();
......
...@@ -13,13 +13,6 @@ ...@@ -13,13 +13,6 @@
13 <script src="/node_modules/sinon/pkg/sinon.js"></script> 13 <script src="/node_modules/sinon/pkg/sinon.js"></script>
14 <script src="/node_modules/qunitjs/qunit/qunit.js"></script> 14 <script src="/node_modules/qunitjs/qunit/qunit.js"></script>
15 <script src="/node_modules/video.js/dist/video.js"></script> 15 <script src="/node_modules/video.js/dist/video.js"></script>
16 <script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
17
18 <script src="/src/videojs-contrib-hls.js"></script>
19 <script src="/dist/videojs-contrib-hls.js"></script>
20 <script src="/src/bin-utils.js"></script>
21
22 <script src="/test/videojs-contrib-hls.test.js"></script>
23 <script src="/dist-test/videojs-contrib-hls.js"></script> 16 <script src="/dist-test/videojs-contrib-hls.js"></script>
24 17
25 </body> 18 </body>
......
...@@ -11,29 +11,11 @@ var DEFAULTS = { ...@@ -11,29 +11,11 @@ var DEFAULTS = {
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 13
14 // REMOVE ME WHEN BROWSERIFIED 14 'test/**/*.test.js'
15 'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
16
17 // these two stub old functionality
18 'src/videojs-contrib-hls.js',
19 'dist/videojs-contrib-hls.js',
20
21 'src/bin-utils.js',
22
23 'test/stub.test.js',
24
25 'test/videojs-contrib-hls.test.js',
26 'test/m3u8.test.js',
27 'test/playlist.test.js',
28 'test/playlist-loader.test.js',
29 'test/decrypter.test.js',
30 // END REMOVE ME
31 // 'test/**/*.js'
32 ], 15 ],
33 16
34 exclude: [ 17 exclude: [
35 'test/bundle.js', 18 'test/data/**'
36 // 'test/data/**'
37 ], 19 ],
38 20
39 plugins: [ 21 plugins: [
...@@ -42,7 +24,7 @@ var DEFAULTS = { ...@@ -42,7 +24,7 @@ var DEFAULTS = {
42 ], 24 ],
43 25
44 preprocessors: { 26 preprocessors: {
45 'test/{playlist*,decrypter,stub,m3u8}.test.js': ['browserify'] 27 'test/**/*.test.js': ['browserify']
46 }, 28 },
47 29
48 reporters: ['dots'], 30 reporters: ['dots'],
......
...@@ -29,9 +29,11 @@ QUnit.test('the environment is sane', function(assert) { ...@@ -29,9 +29,11 @@ QUnit.test('the environment is sane', function(assert) {
29 assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists'); 29 assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
30 assert.strictEqual(typeof sinon, 'object', 'sinon exists'); 30 assert.strictEqual(typeof sinon, 'object', 'sinon exists');
31 assert.strictEqual(typeof videojs, 'function', 'videojs exists'); 31 assert.strictEqual(typeof videojs, 'function', 'videojs exists');
32 assert.strictEqual(typeof videojs.MediaSource, 'object', 'MediaSource is an object'); 32 assert.strictEqual(typeof videojs.MediaSource, 'function', 'MediaSource is an object');
33 assert.strictEqual(typeof videojs.URL, 'object', 'URL 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'); 34 assert.strictEqual(typeof videojs.Hls, 'object', 'Hls is an object');
35 assert.strictEqual(typeof videojs.HlsSourceHandler,'function', 'HlsSourceHandler is a function'); 35 assert.strictEqual(typeof videojs.HlsSourceHandler,
36 'function',
37 'HlsSourceHandler is a function');
36 assert.strictEqual(typeof videojs.HlsHandler, 'function', 'HlsHandler is a function'); 38 assert.strictEqual(typeof videojs.HlsHandler, 'function', 'HlsHandler is a function');
37 }); 39 });
......
1 import manifests from './test-manifests';
2 import expected from './test-expected';
3 window.manifests = manifests;
4 window.expected = expected;
5
This diff could not be displayed because it is too large.