Merge pull request #568 from videojs/browserify-p5
Browserify p5
Showing
16 changed files
with
621 additions
and
662 deletions
... | @@ -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 | ... | ... |
src/stub.js
deleted
100644 → 0
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 | }); | ... | ... |
test/stub.test.js
deleted
100644 → 0
This diff could not be displayed because it is too large.
-
Please register or sign in to post a comment