Merge pull request #62 from videojs/tech2
HLS Tech
Showing
20 changed files
with
542 additions
and
473 deletions
... | @@ -45,9 +45,6 @@ module.exports = function(grunt) { | ... | @@ -45,9 +45,6 @@ module.exports = function(grunt) { |
45 | dest: 'dist/videojs.hls.min.js' | 45 | dest: 'dist/videojs.hls.min.js' |
46 | } | 46 | } |
47 | }, | 47 | }, |
48 | qunit: { | ||
49 | files: ['test/**/*.html', '!test/perf.html', '!test/muxer/**'] | ||
50 | }, | ||
51 | jshint: { | 48 | jshint: { |
52 | gruntfile: { | 49 | gruntfile: { |
53 | options: { | 50 | options: { |
... | @@ -93,11 +90,11 @@ module.exports = function(grunt) { | ... | @@ -93,11 +90,11 @@ module.exports = function(grunt) { |
93 | }, | 90 | }, |
94 | src: { | 91 | src: { |
95 | files: '<%= jshint.src.src %>', | 92 | files: '<%= jshint.src.src %>', |
96 | tasks: ['jshint:src', 'qunit'] | 93 | tasks: ['jshint:src', 'test'] |
97 | }, | 94 | }, |
98 | test: { | 95 | test: { |
99 | files: '<%= jshint.test.src %>', | 96 | files: '<%= jshint.test.src %>', |
100 | tasks: ['jshint:test', 'qunit'] | 97 | tasks: ['jshint:test', 'test'] |
101 | } | 98 | } |
102 | }, | 99 | }, |
103 | concurrent: { | 100 | concurrent: { |
... | @@ -194,7 +191,6 @@ module.exports = function(grunt) { | ... | @@ -194,7 +191,6 @@ module.exports = function(grunt) { |
194 | grunt.loadNpmTasks('grunt-contrib-clean'); | 191 | grunt.loadNpmTasks('grunt-contrib-clean'); |
195 | grunt.loadNpmTasks('grunt-contrib-concat'); | 192 | grunt.loadNpmTasks('grunt-contrib-concat'); |
196 | grunt.loadNpmTasks('grunt-contrib-uglify'); | 193 | grunt.loadNpmTasks('grunt-contrib-uglify'); |
197 | grunt.loadNpmTasks('grunt-contrib-qunit'); | ||
198 | grunt.loadNpmTasks('grunt-contrib-jshint'); | 194 | grunt.loadNpmTasks('grunt-contrib-jshint'); |
199 | grunt.loadNpmTasks('grunt-contrib-watch'); | 195 | grunt.loadNpmTasks('grunt-contrib-watch'); |
200 | grunt.loadNpmTasks('grunt-contrib-connect'); | 196 | grunt.loadNpmTasks('grunt-contrib-connect'); |
... | @@ -255,7 +251,7 @@ module.exports = function(grunt) { | ... | @@ -255,7 +251,7 @@ module.exports = function(grunt) { |
255 | ['clean', | 251 | ['clean', |
256 | 'jshint', | 252 | 'jshint', |
257 | 'manifests-to-js', | 253 | 'manifests-to-js', |
258 | 'qunit', | 254 | 'test', |
259 | 'concat', | 255 | 'concat', |
260 | 'uglify']); | 256 | 'uglify']); |
261 | 257 | ... | ... |
... | @@ -31,7 +31,7 @@ | ... | @@ -31,7 +31,7 @@ |
31 | <!-- bipbop --> | 31 | <!-- bipbop --> |
32 | <!-- <script src="test/tsSegment.js"></script> --> | 32 | <!-- <script src="test/tsSegment.js"></script> --> |
33 | <!-- bunnies --> | 33 | <!-- bunnies --> |
34 | <script src="test/tsSegment-bc.js"></script> | 34 | <!--<script src="test/tsSegment-bc.js"></script>--> |
35 | 35 | ||
36 | <style> | 36 | <style> |
37 | body { | 37 | body { |
... | @@ -63,10 +63,12 @@ | ... | @@ -63,10 +63,12 @@ |
63 | <script> | 63 | <script> |
64 | videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; | 64 | videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf'; |
65 | // initialize the player | 65 | // initialize the player |
66 | var player = videojs('video'); | 66 | var player = videojs('video', { |
67 | techOrder: ['hls'] | ||
68 | }); | ||
67 | 69 | ||
68 | // initialize the plugin | 70 | // initialize the plugin |
69 | player.hls(); | 71 | //player.hls() |
70 | </script> | 72 | </script> |
71 | </body> | 73 | </body> |
72 | </html> | 74 | </html> | ... | ... |
... | @@ -19,7 +19,6 @@ | ... | @@ -19,7 +19,6 @@ |
19 | "grunt-contrib-concat": "~0.3.0", | 19 | "grunt-contrib-concat": "~0.3.0", |
20 | "grunt-contrib-connect": "~0.6.0", | 20 | "grunt-contrib-connect": "~0.6.0", |
21 | "grunt-contrib-jshint": "~0.6.0", | 21 | "grunt-contrib-jshint": "~0.6.0", |
22 | "grunt-contrib-qunit": "~0.2.0", | ||
23 | "grunt-contrib-uglify": "~0.2.0", | 22 | "grunt-contrib-uglify": "~0.2.0", |
24 | "grunt-contrib-watch": "~0.4.0", | 23 | "grunt-contrib-watch": "~0.4.0", |
25 | "grunt-karma": "~0.6.2", | 24 | "grunt-karma": "~0.6.2", | ... | ... |
... | @@ -8,7 +8,7 @@ | ... | @@ -8,7 +8,7 @@ |
8 | 8 | ||
9 | (function(window) { | 9 | (function(window) { |
10 | var | 10 | var |
11 | FlvTag = window.videojs.hls.FlvTag, | 11 | FlvTag = window.videojs.Hls.FlvTag, |
12 | adtsSampleingRates = [ | 12 | adtsSampleingRates = [ |
13 | 96000, 88200, | 13 | 96000, 88200, |
14 | 64000, 48000, | 14 | 64000, 48000, |
... | @@ -17,7 +17,7 @@ var | ... | @@ -17,7 +17,7 @@ var |
17 | 16000, 12000 | 17 | 16000, 12000 |
18 | ]; | 18 | ]; |
19 | 19 | ||
20 | window.videojs.hls.AacStream = function() { | 20 | window.videojs.Hls.AacStream = function() { |
21 | var | 21 | var |
22 | next_pts, // :uint | 22 | next_pts, // :uint |
23 | pts_offset, // :int | 23 | pts_offset, // :int | ... | ... |
... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
4 | * Parser for exponential Golomb codes, a variable-bitwidth number encoding | 4 | * Parser for exponential Golomb codes, a variable-bitwidth number encoding |
5 | * scheme used by h264. | 5 | * scheme used by h264. |
6 | */ | 6 | */ |
7 | window.videojs.hls.ExpGolomb = function(workingData) { | 7 | window.videojs.Hls.ExpGolomb = function(workingData) { |
8 | var | 8 | var |
9 | // the number of bytes left to examine in workingData | 9 | // the number of bytes left to examine in workingData |
10 | workingBytesAvailable = workingData.byteLength, | 10 | workingBytesAvailable = workingData.byteLength, | ... | ... |
1 | (function(window) { | 1 | (function(window) { |
2 | 2 | ||
3 | window.videojs = window.videojs || {}; | 3 | window.videojs = window.videojs || {}; |
4 | window.videojs.hls = window.videojs.hls || {}; | 4 | window.videojs.Hls = window.videojs.Hls || {}; |
5 | 5 | ||
6 | var hls = window.videojs.hls; | 6 | var hls = window.videojs.Hls; |
7 | 7 | ||
8 | // (type:uint, extraData:Boolean = false) extends ByteArray | 8 | // (type:uint, extraData:Boolean = false) extends ByteArray |
9 | hls.FlvTag = function(type, extraData) { | 9 | hls.FlvTag = function(type, extraData) { | ... | ... |
... | @@ -8,8 +8,8 @@ | ... | @@ -8,8 +8,8 @@ |
8 | 8 | ||
9 | (function(window) { | 9 | (function(window) { |
10 | var | 10 | var |
11 | ExpGolomb = window.videojs.hls.ExpGolomb, | 11 | ExpGolomb = window.videojs.Hls.ExpGolomb, |
12 | FlvTag = window.videojs.hls.FlvTag, | 12 | FlvTag = window.videojs.Hls.FlvTag, |
13 | 13 | ||
14 | H264ExtraData = function() { | 14 | H264ExtraData = function() { |
15 | this.sps = []; // :Array | 15 | this.sps = []; // :Array |
... | @@ -234,7 +234,7 @@ | ... | @@ -234,7 +234,7 @@ |
234 | * an h264 stream. Exactly one byte. | 234 | * an h264 stream. Exactly one byte. |
235 | */ | 235 | */ |
236 | // incomplete, see Table 7.1 of ITU-T H.264 for 12-32 | 236 | // incomplete, see Table 7.1 of ITU-T H.264 for 12-32 |
237 | window.videojs.hls.NALUnitType = NALUnitType = { | 237 | window.videojs.Hls.NALUnitType = NALUnitType = { |
238 | unspecified: 0, | 238 | unspecified: 0, |
239 | slice_layer_without_partitioning_rbsp_non_idr: 1, | 239 | slice_layer_without_partitioning_rbsp_non_idr: 1, |
240 | slice_data_partition_a_layer_rbsp: 2, | 240 | slice_data_partition_a_layer_rbsp: 2, |
... | @@ -249,7 +249,7 @@ | ... | @@ -249,7 +249,7 @@ |
249 | end_of_stream_rbsp: 11 | 249 | end_of_stream_rbsp: 11 |
250 | }; | 250 | }; |
251 | 251 | ||
252 | window.videojs.hls.H264Stream = function() { | 252 | window.videojs.Hls.H264Stream = function() { |
253 | var | 253 | var |
254 | next_pts, // :uint; | 254 | next_pts, // :uint; |
255 | next_dts, // :uint; | 255 | next_dts, // :uint; | ... | ... |
... | @@ -5,8 +5,8 @@ | ... | @@ -5,8 +5,8 @@ |
5 | (function(window, videojs) { | 5 | (function(window, videojs) { |
6 | 'use strict'; | 6 | 'use strict'; |
7 | var | 7 | var |
8 | resolveUrl = videojs.hls.resolveUrl, | 8 | resolveUrl = videojs.Hls.resolveUrl, |
9 | xhr = videojs.hls.xhr, | 9 | xhr = videojs.Hls.xhr, |
10 | 10 | ||
11 | /** | 11 | /** |
12 | * Returns a new master playlist that is the result of merging an | 12 | * Returns a new master playlist that is the result of merging an |
... | @@ -51,6 +51,7 @@ | ... | @@ -51,6 +51,7 @@ |
51 | var | 51 | var |
52 | loader = this, | 52 | loader = this, |
53 | media, | 53 | media, |
54 | mediaUpdateTimeout, | ||
54 | request, | 55 | request, |
55 | 56 | ||
56 | haveMetadata = function(error, xhr, url) { | 57 | haveMetadata = function(error, xhr, url) { |
... | @@ -88,7 +89,7 @@ | ... | @@ -88,7 +89,7 @@ |
88 | 89 | ||
89 | // refresh live playlists after a target duration passes | 90 | // refresh live playlists after a target duration passes |
90 | if (!loader.media().endList) { | 91 | if (!loader.media().endList) { |
91 | window.setTimeout(function() { | 92 | mediaUpdateTimeout = window.setTimeout(function() { |
92 | loader.trigger('mediaupdatetimeout'); | 93 | loader.trigger('mediaupdatetimeout'); |
93 | }, refreshDelay); | 94 | }, refreshDelay); |
94 | } | 95 | } |
... | @@ -104,6 +105,13 @@ | ... | @@ -104,6 +105,13 @@ |
104 | 105 | ||
105 | loader.state = 'HAVE_NOTHING'; | 106 | loader.state = 'HAVE_NOTHING'; |
106 | 107 | ||
108 | loader.dispose = function() { | ||
109 | if (request) { | ||
110 | request.abort(); | ||
111 | } | ||
112 | window.clearTimeout(mediaUpdateTimeout); | ||
113 | }; | ||
114 | |||
107 | /** | 115 | /** |
108 | * When called without any arguments, returns the currently | 116 | * When called without any arguments, returns the currently |
109 | * active media playlist. When called with a single argument, | 117 | * active media playlist. When called with a single argument, |
... | @@ -213,7 +221,9 @@ | ... | @@ -213,7 +221,9 @@ |
213 | haveMetadata(error, | 221 | haveMetadata(error, |
214 | this, | 222 | this, |
215 | parser.manifest.playlists[0].uri); | 223 | parser.manifest.playlists[0].uri); |
216 | loader.trigger('loadedmetadata'); | 224 | if (!error) { |
225 | loader.trigger('loadedmetadata'); | ||
226 | } | ||
217 | }); | 227 | }); |
218 | return loader.trigger('loadedplaylist'); | 228 | return loader.trigger('loadedplaylist'); |
219 | } | 229 | } |
... | @@ -231,7 +241,7 @@ | ... | @@ -231,7 +241,7 @@ |
231 | return loader.trigger('loadedmetadata'); | 241 | return loader.trigger('loadedmetadata'); |
232 | }); | 242 | }); |
233 | }; | 243 | }; |
234 | PlaylistLoader.prototype = new videojs.hls.Stream(); | 244 | PlaylistLoader.prototype = new videojs.Hls.Stream(); |
235 | 245 | ||
236 | videojs.hls.PlaylistLoader = PlaylistLoader; | 246 | videojs.Hls.PlaylistLoader = PlaylistLoader; |
237 | })(window, window.videojs); | 247 | })(window, window.videojs); | ... | ... |
1 | (function(window) { | 1 | (function(window) { |
2 | var | 2 | var |
3 | videojs = window.videojs, | 3 | videojs = window.videojs, |
4 | FlvTag = videojs.hls.FlvTag, | 4 | FlvTag = videojs.Hls.FlvTag, |
5 | H264Stream = videojs.hls.H264Stream, | 5 | H264Stream = videojs.Hls.H264Stream, |
6 | AacStream = videojs.hls.AacStream, | 6 | AacStream = videojs.Hls.AacStream, |
7 | MP2T_PACKET_LENGTH, | 7 | MP2T_PACKET_LENGTH, |
8 | STREAM_TYPES; | 8 | STREAM_TYPES; |
9 | 9 | ||
... | @@ -11,7 +11,7 @@ | ... | @@ -11,7 +11,7 @@ |
11 | * An object that incrementally transmuxes MPEG2 Trasport Stream | 11 | * An object that incrementally transmuxes MPEG2 Trasport Stream |
12 | * chunks into an FLV. | 12 | * chunks into an FLV. |
13 | */ | 13 | */ |
14 | videojs.hls.SegmentParser = function() { | 14 | videojs.Hls.SegmentParser = function() { |
15 | var | 15 | var |
16 | self = this, | 16 | self = this, |
17 | parseTSPacket, | 17 | parseTSPacket, |
... | @@ -432,8 +432,8 @@ | ... | @@ -432,8 +432,8 @@ |
432 | }; | 432 | }; |
433 | 433 | ||
434 | // MPEG2-TS constants | 434 | // MPEG2-TS constants |
435 | videojs.hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188; | 435 | videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188; |
436 | videojs.hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = { | 436 | videojs.Hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = { |
437 | h264: 0x1b, | 437 | h264: 0x1b, |
438 | adts: 0x0f | 438 | adts: 0x0f |
439 | }; | 439 | }; | ... | ... |
... | @@ -8,28 +8,6 @@ | ... | @@ -8,28 +8,6 @@ |
8 | 8 | ||
9 | (function(window, videojs, document, undefined) { | 9 | (function(window, videojs, document, undefined) { |
10 | 10 | ||
11 | videojs.hls = { | ||
12 | /** | ||
13 | * Whether the browser has built-in HLS support. | ||
14 | */ | ||
15 | supportsNativeHls: (function() { | ||
16 | var | ||
17 | video = document.createElement('video'), | ||
18 | xMpegUrl, | ||
19 | vndMpeg; | ||
20 | |||
21 | // native HLS is definitely not supported if HTML5 video isn't | ||
22 | if (!videojs.Html5.isSupported()) { | ||
23 | return false; | ||
24 | } | ||
25 | |||
26 | xMpegUrl = video.canPlayType('application/x-mpegURL'); | ||
27 | vndMpeg = video.canPlayType('application/vnd.apple.mpegURL'); | ||
28 | return (/probably|maybe/).test(xMpegUrl) || | ||
29 | (/probably|maybe/).test(vndMpeg); | ||
30 | })() | ||
31 | }; | ||
32 | |||
33 | var | 11 | var |
34 | 12 | ||
35 | // the desired length of video to maintain in the buffer, in seconds | 13 | // the desired length of video to maintain in the buffer, in seconds |
... | @@ -95,60 +73,7 @@ var | ... | @@ -95,60 +73,7 @@ var |
95 | } | 73 | } |
96 | }, | 74 | }, |
97 | 75 | ||
98 | /** | 76 | xhr, |
99 | * Creates and sends an XMLHttpRequest. | ||
100 | * @param options {string | object} if this argument is a string, it | ||
101 | * is intrepreted as a URL and a simple GET request is | ||
102 | * inititated. If it is an object, it should contain a `url` | ||
103 | * property that indicates the URL to request and optionally a | ||
104 | * `method` which is the type of HTTP request to send. | ||
105 | * @param callback (optional) {function} a function to call when the | ||
106 | * request completes. If the request was not successful, the first | ||
107 | * argument will be falsey. | ||
108 | * @return {object} the XMLHttpRequest that was initiated. | ||
109 | */ | ||
110 | xhr = videojs.hls.xhr = function(url, callback) { | ||
111 | var | ||
112 | options = { | ||
113 | method: 'GET' | ||
114 | }, | ||
115 | request; | ||
116 | |||
117 | if (typeof callback !== 'function') { | ||
118 | callback = function() {}; | ||
119 | } | ||
120 | |||
121 | if (typeof url === 'object') { | ||
122 | options = videojs.util.mergeOptions(options, url); | ||
123 | url = options.url; | ||
124 | } | ||
125 | |||
126 | request = new window.XMLHttpRequest(); | ||
127 | request.open(options.method, url); | ||
128 | |||
129 | if (options.responseType) { | ||
130 | request.responseType = options.responseType; | ||
131 | } | ||
132 | if (options.withCredentials) { | ||
133 | request.withCredentials = true; | ||
134 | } | ||
135 | |||
136 | request.onreadystatechange = function() { | ||
137 | // wait until the request completes | ||
138 | if (this.readyState !== 4) { | ||
139 | return; | ||
140 | } | ||
141 | |||
142 | // request error | ||
143 | if (this.status >= 400 || this.status === 0) { | ||
144 | return callback.call(this, true, url); | ||
145 | } | ||
146 | |||
147 | return callback.call(this, false, url); | ||
148 | }; | ||
149 | request.send(null); | ||
150 | return request; | ||
151 | }, | ||
152 | 77 | ||
153 | /** | 78 | /** |
154 | * TODO - Document this great feature. | 79 | * TODO - Document this great feature. |
... | @@ -228,7 +153,13 @@ var | ... | @@ -228,7 +153,13 @@ var |
228 | var | 153 | var |
229 | duration = 0, | 154 | duration = 0, |
230 | segment, | 155 | segment, |
231 | i = (playlist.segments || []).length; | 156 | i; |
157 | |||
158 | if (!playlist) { | ||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | i = (playlist.segments || []).length; | ||
232 | 163 | ||
233 | // if present, use the duration specified in the playlist | 164 | // if present, use the duration specified in the playlist |
234 | if (playlist.totalDuration) { | 165 | if (playlist.totalDuration) { |
... | @@ -247,134 +178,17 @@ var | ... | @@ -247,134 +178,17 @@ var |
247 | return duration; | 178 | return duration; |
248 | }, | 179 | }, |
249 | 180 | ||
250 | /** | 181 | resolveUrl, |
251 | * Constructs a new URI by interpreting a path relative to another | ||
252 | * URI. | ||
253 | * @param basePath {string} a relative or absolute URI | ||
254 | * @param path {string} a path part to combine with the base | ||
255 | * @return {string} a URI that is equivalent to composing `base` | ||
256 | * with `path` | ||
257 | * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue | ||
258 | */ | ||
259 | resolveUrl = videojs.hls.resolveUrl = function(basePath, path) { | ||
260 | // use the base element to get the browser to handle URI resolution | ||
261 | var | ||
262 | oldBase = document.querySelector('base'), | ||
263 | docHead = document.querySelector('head'), | ||
264 | a = document.createElement('a'), | ||
265 | base = oldBase, | ||
266 | oldHref, | ||
267 | result; | ||
268 | |||
269 | // prep the document | ||
270 | if (oldBase) { | ||
271 | oldHref = oldBase.href; | ||
272 | } else { | ||
273 | base = docHead.appendChild(document.createElement('base')); | ||
274 | } | ||
275 | |||
276 | base.href = basePath; | ||
277 | a.href = path; | ||
278 | result = a.href; | ||
279 | 182 | ||
280 | // clean up | 183 | initSource = function(player, mediaSource, srcUrl) { |
281 | if (oldBase) { | ||
282 | oldBase.href = oldHref; | ||
283 | } else { | ||
284 | docHead.removeChild(base); | ||
285 | } | ||
286 | return result; | ||
287 | }, | ||
288 | |||
289 | /** | ||
290 | * Initializes the HLS plugin. | ||
291 | * @param options {mixed} the URL to an HLS playlist | ||
292 | */ | ||
293 | init = function(options) { | ||
294 | var | 184 | var |
295 | mediaSource = new videojs.MediaSource(), | 185 | segmentParser = new videojs.Hls.SegmentParser(), |
296 | segmentParser = new videojs.hls.SegmentParser(), | ||
297 | player = this, | ||
298 | srcUrl, | ||
299 | 186 | ||
300 | segmentXhr, | 187 | segmentXhr, |
301 | settings, | 188 | settings = videojs.util.mergeOptions({}, player.options().hls), |
302 | fillBuffer, | 189 | fillBuffer, |
303 | updateDuration; | 190 | updateDuration; |
304 | 191 | ||
305 | // if the video element supports HLS natively, do nothing | ||
306 | if (videojs.hls.supportsNativeHls) { | ||
307 | return; | ||
308 | } | ||
309 | |||
310 | settings = videojs.util.mergeOptions({}, options); | ||
311 | |||
312 | srcUrl = (function() { | ||
313 | var | ||
314 | extname, | ||
315 | i = 0, | ||
316 | j = 0, | ||
317 | src = player.el().querySelector('.vjs-tech').src, | ||
318 | sources = player.options().sources, | ||
319 | techName, | ||
320 | length = sources.length; | ||
321 | |||
322 | // use the URL specified in options if one was provided | ||
323 | if (typeof options === 'string') { | ||
324 | return options; | ||
325 | } else if (options && options.url) { | ||
326 | return options.url; | ||
327 | } | ||
328 | |||
329 | // src attributes take precedence over source children | ||
330 | if (src) { | ||
331 | |||
332 | // assume files with the m3u8 extension are HLS | ||
333 | extname = (/[^#?]*(?:\/[^#?]*\.([^#?]*))/).exec(src); | ||
334 | if (extname && extname[1] === 'm3u8') { | ||
335 | return src; | ||
336 | } | ||
337 | return; | ||
338 | } | ||
339 | |||
340 | // find the first playable source | ||
341 | for (; i < length; i++) { | ||
342 | |||
343 | // ignore sources without a specified type | ||
344 | if (!sources[i].type) { | ||
345 | continue; | ||
346 | } | ||
347 | |||
348 | // do nothing if the source is handled by one of the standard techs | ||
349 | for (j in player.options().techOrder) { | ||
350 | techName = player.options().techOrder[j]; | ||
351 | techName = techName[0].toUpperCase() + techName.substring(1); | ||
352 | if (videojs[techName].canPlaySource({ type: sources[i].type })) { | ||
353 | return; | ||
354 | } | ||
355 | } | ||
356 | |||
357 | // use the plugin if the MIME type specifies HLS | ||
358 | if ((/application\/x-mpegURL/).test(sources[i].type) || | ||
359 | (/application\/vnd\.apple\.mpegURL/).test(sources[i].type)) { | ||
360 | return sources[i].src; | ||
361 | } | ||
362 | } | ||
363 | })(); | ||
364 | |||
365 | if (!srcUrl) { | ||
366 | // do nothing until the plugin is initialized with a valid URL | ||
367 | videojs.log('hls: no valid playlist URL specified'); | ||
368 | return; | ||
369 | } | ||
370 | |||
371 | // expose the HLS plugin state | ||
372 | player.hls.readyState = function() { | ||
373 | if (!player.hls.media) { | ||
374 | return 0; // HAVE_NOTHING | ||
375 | } | ||
376 | return 1; // HAVE_METADATA | ||
377 | }; | ||
378 | 192 | ||
379 | player.on('seeking', function() { | 193 | player.on('seeking', function() { |
380 | var currentTime = player.currentTime(); | 194 | var currentTime = player.currentTime(); |
... | @@ -397,16 +211,8 @@ var | ... | @@ -397,16 +211,8 @@ var |
397 | * Update the player duration | 211 | * Update the player duration |
398 | */ | 212 | */ |
399 | updateDuration = function(playlist) { | 213 | updateDuration = function(playlist) { |
400 | var tech; | ||
401 | // update the duration | 214 | // update the duration |
402 | player.duration(totalDuration(playlist)); | 215 | player.duration(totalDuration(playlist)); |
403 | // tell the flash tech of the new duration | ||
404 | tech = player.el().querySelector('.vjs-tech'); | ||
405 | if(tech.vjs_setProperty) { | ||
406 | tech.vjs_setProperty('duration', player.duration()); | ||
407 | } | ||
408 | // manually fire the duration change | ||
409 | player.trigger('durationchange'); | ||
410 | }; | 216 | }; |
411 | 217 | ||
412 | /** | 218 | /** |
... | @@ -502,7 +308,8 @@ var | ... | @@ -502,7 +308,8 @@ var |
502 | } | 308 | } |
503 | 309 | ||
504 | // if no segments are available, do nothing | 310 | // if no segments are available, do nothing |
505 | if (!player.hls.playlists.media().segments) { | 311 | if (player.hls.playlists.state === "HAVE_NOTHING" || |
312 | !player.hls.playlists.media().segments) { | ||
506 | return; | 313 | return; |
507 | } | 314 | } |
508 | 315 | ||
... | @@ -609,7 +416,7 @@ var | ... | @@ -609,7 +416,7 @@ var |
609 | 416 | ||
610 | player.hls.mediaIndex = 0; | 417 | player.hls.mediaIndex = 0; |
611 | player.hls.playlists = | 418 | player.hls.playlists = |
612 | new videojs.hls.PlaylistLoader(srcUrl, settings.withCredentials); | 419 | new videojs.Hls.PlaylistLoader(srcUrl, settings.withCredentials); |
613 | player.hls.playlists.on('loadedmetadata', function() { | 420 | player.hls.playlists.on('loadedmetadata', function() { |
614 | oldMediaPlaylist = player.hls.playlists.media(); | 421 | oldMediaPlaylist = player.hls.playlists.media(); |
615 | 422 | ||
... | @@ -638,28 +445,160 @@ var | ... | @@ -638,28 +445,160 @@ var |
638 | oldMediaPlaylist = updatedPlaylist; | 445 | oldMediaPlaylist = updatedPlaylist; |
639 | }); | 446 | }); |
640 | }); | 447 | }); |
641 | player.src([{ | 448 | }; |
449 | |||
450 | var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; | ||
451 | |||
452 | videojs.Hls = videojs.Flash.extend({ | ||
453 | init: function(player, options, ready) { | ||
454 | var | ||
455 | source = options.source, | ||
456 | settings = player.options(); | ||
457 | |||
458 | player.hls = this; | ||
459 | delete options.source; | ||
460 | options.swf = settings.flash.swf; | ||
461 | videojs.Flash.call(this, player, options, ready); | ||
462 | options.source = source; | ||
463 | videojs.Hls.prototype.src.call(this, options.source && options.source.src); | ||
464 | } | ||
465 | }); | ||
466 | |||
467 | videojs.Hls.prototype.src = function(src) { | ||
468 | var | ||
469 | player = this.player(), | ||
470 | mediaSource, | ||
471 | source; | ||
472 | |||
473 | if (src) { | ||
474 | mediaSource = new videojs.MediaSource(); | ||
475 | source = { | ||
642 | src: videojs.URL.createObjectURL(mediaSource), | 476 | src: videojs.URL.createObjectURL(mediaSource), |
643 | type: "video/flv" | 477 | type: "video/flv" |
644 | }]); | 478 | }; |
479 | this.mediaSource = mediaSource; | ||
480 | initSource(player, mediaSource, src); | ||
481 | this.ready(function() { | ||
482 | this.el().vjs_src(source.src); | ||
483 | }); | ||
484 | } | ||
485 | }; | ||
486 | |||
487 | videojs.Hls.prototype.duration = function() { | ||
488 | var playlists = this.playlists; | ||
489 | if (playlists) { | ||
490 | return totalDuration(playlists.media()); | ||
491 | } | ||
492 | return 0; | ||
493 | }; | ||
645 | 494 | ||
646 | if (player.options().autoplay) { | 495 | videojs.Hls.prototype.dispose = function() { |
647 | player.play(); | 496 | if (this.playlists) { |
497 | this.playlists.dispose(); | ||
498 | } | ||
499 | videojs.Flash.prototype.dispose.call(this); | ||
500 | }; | ||
501 | |||
502 | videojs.Hls.isSupported = function() { | ||
503 | return videojs.Flash.isSupported() && videojs.MediaSource; | ||
504 | }; | ||
505 | |||
506 | videojs.Hls.canPlaySource = function(srcObj) { | ||
507 | return mpegurlRE.test(srcObj.type) || videojs.Flash.canPlaySource.call(this, srcObj); | ||
508 | }; | ||
509 | |||
510 | /** | ||
511 | * Creates and sends an XMLHttpRequest. | ||
512 | * @param options {string | object} if this argument is a string, it | ||
513 | * is intrepreted as a URL and a simple GET request is | ||
514 | * inititated. If it is an object, it should contain a `url` | ||
515 | * property that indicates the URL to request and optionally a | ||
516 | * `method` which is the type of HTTP request to send. | ||
517 | * @param callback (optional) {function} a function to call when the | ||
518 | * request completes. If the request was not successful, the first | ||
519 | * argument will be falsey. | ||
520 | * @return {object} the XMLHttpRequest that was initiated. | ||
521 | */ | ||
522 | xhr = videojs.Hls.xhr = function(url, callback) { | ||
523 | var | ||
524 | options = { | ||
525 | method: 'GET' | ||
526 | }, | ||
527 | request; | ||
528 | |||
529 | if (typeof callback !== 'function') { | ||
530 | callback = function() {}; | ||
531 | } | ||
532 | |||
533 | if (typeof url === 'object') { | ||
534 | options = videojs.util.mergeOptions(options, url); | ||
535 | url = options.url; | ||
536 | } | ||
537 | |||
538 | request = new window.XMLHttpRequest(); | ||
539 | request.open(options.method, url); | ||
540 | |||
541 | if (options.responseType) { | ||
542 | request.responseType = options.responseType; | ||
543 | } | ||
544 | if (options.withCredentials) { | ||
545 | request.withCredentials = true; | ||
546 | } | ||
547 | |||
548 | request.onreadystatechange = function() { | ||
549 | // wait until the request completes | ||
550 | if (this.readyState !== 4) { | ||
551 | return; | ||
648 | } | 552 | } |
553 | |||
554 | // request error | ||
555 | if (this.status >= 400 || this.status === 0) { | ||
556 | return callback.call(this, true, url); | ||
557 | } | ||
558 | |||
559 | return callback.call(this, false, url); | ||
649 | }; | 560 | }; |
561 | request.send(null); | ||
562 | return request; | ||
563 | }; | ||
650 | 564 | ||
651 | videojs.plugin('hls', function() { | 565 | /** |
652 | if (typeof Uint8Array === 'undefined') { | 566 | * Constructs a new URI by interpreting a path relative to another |
653 | return; | 567 | * URI. |
568 | * @param basePath {string} a relative or absolute URI | ||
569 | * @param path {string} a path part to combine with the base | ||
570 | * @return {string} a URI that is equivalent to composing `base` | ||
571 | * with `path` | ||
572 | * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue | ||
573 | */ | ||
574 | resolveUrl = videojs.Hls.resolveUrl = function(basePath, path) { | ||
575 | // use the base element to get the browser to handle URI resolution | ||
576 | var | ||
577 | oldBase = document.querySelector('base'), | ||
578 | docHead = document.querySelector('head'), | ||
579 | a = document.createElement('a'), | ||
580 | base = oldBase, | ||
581 | oldHref, | ||
582 | result; | ||
583 | |||
584 | // prep the document | ||
585 | if (oldBase) { | ||
586 | oldHref = oldBase.href; | ||
587 | } else { | ||
588 | base = docHead.appendChild(document.createElement('base')); | ||
654 | } | 589 | } |
655 | 590 | ||
656 | var initialize = function() { | 591 | base.href = basePath; |
657 | return function() { | 592 | a.href = path; |
658 | this.hls = initialize(); | 593 | result = a.href; |
659 | init.apply(this, arguments); | 594 | |
660 | }; | 595 | // clean up |
661 | }; | 596 | if (oldBase) { |
662 | initialize().apply(this, arguments); | 597 | oldBase.href = oldHref; |
663 | }); | 598 | } else { |
599 | docHead.removeChild(base); | ||
600 | } | ||
601 | return result; | ||
602 | }; | ||
664 | 603 | ||
665 | })(window, window.videojs, document); | 604 | })(window, window.videojs, document); | ... | ... |
... | @@ -21,7 +21,7 @@ | ... | @@ -21,7 +21,7 @@ |
21 | */ | 21 | */ |
22 | var | 22 | var |
23 | buffer, | 23 | buffer, |
24 | ExpGolomb = window.videojs.hls.ExpGolomb, | 24 | ExpGolomb = window.videojs.Hls.ExpGolomb, |
25 | expGolomb; | 25 | expGolomb; |
26 | 26 | ||
27 | module('Exponential Golomb coding'); | 27 | module('Exponential Golomb coding'); | ... | ... |
... | @@ -19,7 +19,7 @@ | ... | @@ -19,7 +19,7 @@ |
19 | notStrictEqual(actual, expected, [message]) | 19 | notStrictEqual(actual, expected, [message]) |
20 | throws(block, [expected], [message]) | 20 | throws(block, [expected], [message]) |
21 | */ | 21 | */ |
22 | var FlvTag = window.videojs.hls.FlvTag; | 22 | var FlvTag = window.videojs.Hls.FlvTag; |
23 | 23 | ||
24 | module('FLV tag'); | 24 | module('FLV tag'); |
25 | 25 | ... | ... |
... | @@ -2,12 +2,12 @@ | ... | @@ -2,12 +2,12 @@ |
2 | module('H264 Stream'); | 2 | module('H264 Stream'); |
3 | 3 | ||
4 | var | 4 | var |
5 | nalUnitTypes = window.videojs.hls.NALUnitType, | 5 | nalUnitTypes = window.videojs.Hls.NALUnitType, |
6 | FlvTag = window.videojs.hls.FlvTag; | 6 | FlvTag = window.videojs.Hls.FlvTag; |
7 | 7 | ||
8 | test('metadata is generated for IDRs after a full NAL unit is written', function() { | 8 | test('metadata is generated for IDRs after a full NAL unit is written', function() { |
9 | var | 9 | var |
10 | h264Stream = new videojs.hls.H264Stream(), | 10 | h264Stream = new videojs.Hls.H264Stream(), |
11 | accessUnitDelimiter = new Uint8Array([ | 11 | accessUnitDelimiter = new Uint8Array([ |
12 | 0x00, | 12 | 0x00, |
13 | 0x00, | 13 | 0x00, |
... | @@ -62,7 +62,7 @@ test('metadata is generated for IDRs after a full NAL unit is written', function | ... | @@ -62,7 +62,7 @@ test('metadata is generated for IDRs after a full NAL unit is written', function |
62 | 62 | ||
63 | test('starting PTS values can be negative', function() { | 63 | test('starting PTS values can be negative', function() { |
64 | var | 64 | var |
65 | h264Stream = new videojs.hls.H264Stream(), | 65 | h264Stream = new videojs.Hls.H264Stream(), |
66 | accessUnitDelimiter = new Uint8Array([ | 66 | accessUnitDelimiter = new Uint8Array([ |
67 | 0x00, | 67 | 0x00, |
68 | 0x00, | 68 | 0x00, | ... | ... |
... | @@ -127,7 +127,7 @@ | ... | @@ -127,7 +127,7 @@ |
127 | original.addEventListener('change', function() { | 127 | original.addEventListener('change', function() { |
128 | var reader = new FileReader(); | 128 | var reader = new FileReader(); |
129 | reader.addEventListener('loadend', function() { | 129 | reader.addEventListener('loadend', function() { |
130 | var parser = new videojs.hls.SegmentParser(), | 130 | var parser = new videojs.Hls.SegmentParser(), |
131 | tags = [parser.getFlvHeader()], | 131 | tags = [parser.getFlvHeader()], |
132 | tag, | 132 | tag, |
133 | hex, | 133 | hex, |
... | @@ -164,7 +164,7 @@ | ... | @@ -164,7 +164,7 @@ |
164 | } | 164 | } |
165 | 165 | ||
166 | hex = '<pre>' | 166 | hex = '<pre>' |
167 | hex += videojs.hls.utils.hexDump(data); | 167 | hex += videojs.Hls.utils.hexDump(data); |
168 | hex += '</pre>' | 168 | hex += '</pre>' |
169 | 169 | ||
170 | vjsOutput.innerHTML = hex; | 170 | vjsOutput.innerHTML = hex; |
... | @@ -201,7 +201,7 @@ | ... | @@ -201,7 +201,7 @@ |
201 | } | 201 | } |
202 | 202 | ||
203 | // output the hex dump | 203 | // output the hex dump |
204 | hex += videojs.hls.utils.hexDump(bytes); | 204 | hex += videojs.Hls.utils.hexDump(bytes); |
205 | hex += '</pre>'; | 205 | hex += '</pre>'; |
206 | workingOutput.innerHTML = hex; | 206 | workingOutput.innerHTML = hex; |
207 | }); | 207 | }); | ... | ... |
... | @@ -36,26 +36,26 @@ | ... | @@ -36,26 +36,26 @@ |
36 | 36 | ||
37 | test('throws if the playlist url is empty or undefined', function() { | 37 | test('throws if the playlist url is empty or undefined', function() { |
38 | throws(function() { | 38 | throws(function() { |
39 | videojs.hls.PlaylistLoader(); | 39 | videojs.Hls.PlaylistLoader(); |
40 | }, 'requires an argument'); | 40 | }, 'requires an argument'); |
41 | throws(function() { | 41 | throws(function() { |
42 | videojs.hls.PlaylistLoader(''); | 42 | videojs.Hls.PlaylistLoader(''); |
43 | }, 'does not accept the empty string'); | 43 | }, 'does not accept the empty string'); |
44 | }); | 44 | }); |
45 | 45 | ||
46 | test('starts without any metadata', function() { | 46 | test('starts without any metadata', function() { |
47 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 47 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
48 | strictEqual(loader.state, 'HAVE_NOTHING', 'no metadata has loaded yet'); | 48 | strictEqual(loader.state, 'HAVE_NOTHING', 'no metadata has loaded yet'); |
49 | }); | 49 | }); |
50 | 50 | ||
51 | test('requests the initial playlist immediately', function() { | 51 | test('requests the initial playlist immediately', function() { |
52 | new videojs.hls.PlaylistLoader('master.m3u8'); | 52 | new videojs.Hls.PlaylistLoader('master.m3u8'); |
53 | strictEqual(requests.length, 1, 'made a request'); | 53 | strictEqual(requests.length, 1, 'made a request'); |
54 | strictEqual(requests[0].url, 'master.m3u8', 'requested the initial playlist'); | 54 | strictEqual(requests[0].url, 'master.m3u8', 'requested the initial playlist'); |
55 | }); | 55 | }); |
56 | 56 | ||
57 | test('moves to HAVE_MASTER after loading a master playlist', function() { | 57 | test('moves to HAVE_MASTER after loading a master playlist', function() { |
58 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 58 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
59 | requests.pop().respond(200, null, | 59 | requests.pop().respond(200, null, |
60 | '#EXTM3U\n' + | 60 | '#EXTM3U\n' + |
61 | '#EXT-X-STREAM-INF:\n' + | 61 | '#EXT-X-STREAM-INF:\n' + |
... | @@ -67,7 +67,7 @@ | ... | @@ -67,7 +67,7 @@ |
67 | test('jumps to HAVE_METADATA when initialized with a media playlist', function() { | 67 | test('jumps to HAVE_METADATA when initialized with a media playlist', function() { |
68 | var | 68 | var |
69 | loadedmetadatas = 0, | 69 | loadedmetadatas = 0, |
70 | loader = new videojs.hls.PlaylistLoader('media.m3u8'); | 70 | loader = new videojs.Hls.PlaylistLoader('media.m3u8'); |
71 | loader.on('loadedmetadata', function() { | 71 | loader.on('loadedmetadata', function() { |
72 | loadedmetadatas++; | 72 | loadedmetadatas++; |
73 | }); | 73 | }); |
... | @@ -85,7 +85,7 @@ | ... | @@ -85,7 +85,7 @@ |
85 | }); | 85 | }); |
86 | 86 | ||
87 | test('jumps to HAVE_METADATA when initialized with a live media playlist', function() { | 87 | test('jumps to HAVE_METADATA when initialized with a live media playlist', function() { |
88 | var loader = new videojs.hls.PlaylistLoader('media.m3u8'); | 88 | var loader = new videojs.Hls.PlaylistLoader('media.m3u8'); |
89 | requests.pop().respond(200, null, | 89 | requests.pop().respond(200, null, |
90 | '#EXTM3U\n' + | 90 | '#EXTM3U\n' + |
91 | '#EXTINF:10,\n' + | 91 | '#EXTINF:10,\n' + |
... | @@ -99,7 +99,7 @@ | ... | @@ -99,7 +99,7 @@ |
99 | var | 99 | var |
100 | loadedPlaylist = 0, | 100 | loadedPlaylist = 0, |
101 | loadedMetadata = 0, | 101 | loadedMetadata = 0, |
102 | loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 102 | loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
103 | loader.on('loadedplaylist', function() { | 103 | loader.on('loadedplaylist', function() { |
104 | loadedPlaylist++; | 104 | loadedPlaylist++; |
105 | }); | 105 | }); |
... | @@ -131,7 +131,7 @@ | ... | @@ -131,7 +131,7 @@ |
131 | }); | 131 | }); |
132 | 132 | ||
133 | test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() { | 133 | test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() { |
134 | var loader = new videojs.hls.PlaylistLoader('live.m3u8'); | 134 | var loader = new videojs.Hls.PlaylistLoader('live.m3u8'); |
135 | requests.pop().respond(200, null, | 135 | requests.pop().respond(200, null, |
136 | '#EXTM3U\n' + | 136 | '#EXTM3U\n' + |
137 | '#EXTINF:10,\n' + | 137 | '#EXTINF:10,\n' + |
... | @@ -145,7 +145,7 @@ | ... | @@ -145,7 +145,7 @@ |
145 | }); | 145 | }); |
146 | 146 | ||
147 | test('returns to HAVE_METADATA after refreshing the playlist', function() { | 147 | test('returns to HAVE_METADATA after refreshing the playlist', function() { |
148 | var loader = new videojs.hls.PlaylistLoader('live.m3u8'); | 148 | var loader = new videojs.Hls.PlaylistLoader('live.m3u8'); |
149 | requests.pop().respond(200, null, | 149 | requests.pop().respond(200, null, |
150 | '#EXTM3U\n' + | 150 | '#EXTM3U\n' + |
151 | '#EXTINF:10,\n' + | 151 | '#EXTINF:10,\n' + |
... | @@ -161,7 +161,7 @@ | ... | @@ -161,7 +161,7 @@ |
161 | test('emits an error when an initial playlist request fails', function() { | 161 | test('emits an error when an initial playlist request fails', function() { |
162 | var | 162 | var |
163 | errors = [], | 163 | errors = [], |
164 | loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 164 | loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
165 | 165 | ||
166 | loader.on('error', function() { | 166 | loader.on('error', function() { |
167 | errors.push(loader.error); | 167 | errors.push(loader.error); |
... | @@ -175,7 +175,7 @@ | ... | @@ -175,7 +175,7 @@ |
175 | test('errors when an initial media playlist request fails', function() { | 175 | test('errors when an initial media playlist request fails', function() { |
176 | var | 176 | var |
177 | errors = [], | 177 | errors = [], |
178 | loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 178 | loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
179 | 179 | ||
180 | loader.on('error', function() { | 180 | loader.on('error', function() { |
181 | errors.push(loader.error); | 181 | errors.push(loader.error); |
... | @@ -197,7 +197,7 @@ | ... | @@ -197,7 +197,7 @@ |
197 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4 | 197 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4 |
198 | test('halves the refresh timeout if a playlist is unchanged' + | 198 | test('halves the refresh timeout if a playlist is unchanged' + |
199 | 'since the last reload', function() { | 199 | 'since the last reload', function() { |
200 | new videojs.hls.PlaylistLoader('live.m3u8'); | 200 | new videojs.Hls.PlaylistLoader('live.m3u8'); |
201 | requests.pop().respond(200, null, | 201 | requests.pop().respond(200, null, |
202 | '#EXTM3U\n' + | 202 | '#EXTM3U\n' + |
203 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | 203 | '#EXT-X-MEDIA-SEQUENCE:0\n' + |
... | @@ -218,7 +218,7 @@ | ... | @@ -218,7 +218,7 @@ |
218 | }); | 218 | }); |
219 | 219 | ||
220 | test('media-sequence updates are considered a playlist change', function() { | 220 | test('media-sequence updates are considered a playlist change', function() { |
221 | new videojs.hls.PlaylistLoader('live.m3u8'); | 221 | new videojs.Hls.PlaylistLoader('live.m3u8'); |
222 | requests.pop().respond(200, null, | 222 | requests.pop().respond(200, null, |
223 | '#EXTM3U\n' + | 223 | '#EXTM3U\n' + |
224 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | 224 | '#EXT-X-MEDIA-SEQUENCE:0\n' + |
... | @@ -238,7 +238,7 @@ | ... | @@ -238,7 +238,7 @@ |
238 | test('emits an error if a media refresh fails', function() { | 238 | test('emits an error if a media refresh fails', function() { |
239 | var | 239 | var |
240 | errors = 0, | 240 | errors = 0, |
241 | loader = new videojs.hls.PlaylistLoader('live.m3u8'); | 241 | loader = new videojs.Hls.PlaylistLoader('live.m3u8'); |
242 | 242 | ||
243 | loader.on('error', function() { | 243 | loader.on('error', function() { |
244 | errors++; | 244 | errors++; |
... | @@ -256,7 +256,7 @@ | ... | @@ -256,7 +256,7 @@ |
256 | }); | 256 | }); |
257 | 257 | ||
258 | test('switches media playlists when requested', function() { | 258 | test('switches media playlists when requested', function() { |
259 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 259 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
260 | requests.pop().respond(200, null, | 260 | requests.pop().respond(200, null, |
261 | '#EXTM3U\n' + | 261 | '#EXTM3U\n' + |
262 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | 262 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + |
... | @@ -284,7 +284,7 @@ | ... | @@ -284,7 +284,7 @@ |
284 | }); | 284 | }); |
285 | 285 | ||
286 | test('can switch media playlists based on URI', function() { | 286 | test('can switch media playlists based on URI', function() { |
287 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 287 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
288 | requests.pop().respond(200, null, | 288 | requests.pop().respond(200, null, |
289 | '#EXTM3U\n' + | 289 | '#EXTM3U\n' + |
290 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | 290 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + |
... | @@ -312,7 +312,7 @@ | ... | @@ -312,7 +312,7 @@ |
312 | }); | 312 | }); |
313 | 313 | ||
314 | test('aborts in-flight playlist refreshes when switching', function() { | 314 | test('aborts in-flight playlist refreshes when switching', function() { |
315 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 315 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
316 | requests.pop().respond(200, null, | 316 | requests.pop().respond(200, null, |
317 | '#EXTM3U\n' + | 317 | '#EXTM3U\n' + |
318 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | 318 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + |
... | @@ -331,7 +331,7 @@ | ... | @@ -331,7 +331,7 @@ |
331 | }); | 331 | }); |
332 | 332 | ||
333 | test('switching to the active playlist is a no-op', function() { | 333 | test('switching to the active playlist is a no-op', function() { |
334 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 334 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
335 | requests.pop().respond(200, null, | 335 | requests.pop().respond(200, null, |
336 | '#EXTM3U\n' + | 336 | '#EXTM3U\n' + |
337 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | 337 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + |
... | @@ -350,7 +350,7 @@ | ... | @@ -350,7 +350,7 @@ |
350 | }); | 350 | }); |
351 | 351 | ||
352 | test('throws an error if a media switch is initiated too early', function() { | 352 | test('throws an error if a media switch is initiated too early', function() { |
353 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 353 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
354 | 354 | ||
355 | throws(function() { | 355 | throws(function() { |
356 | loader.media('high.m3u8'); | 356 | loader.media('high.m3u8'); |
... | @@ -368,7 +368,7 @@ | ... | @@ -368,7 +368,7 @@ |
368 | }); | 368 | }); |
369 | 369 | ||
370 | test('throws an error if a switch to an unrecognized playlist is requested', function() { | 370 | test('throws an error if a switch to an unrecognized playlist is requested', function() { |
371 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | 371 | var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); |
372 | requests.pop().respond(200, null, | 372 | requests.pop().respond(200, null, |
373 | '#EXTM3U\n' + | 373 | '#EXTM3U\n' + |
374 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + | 374 | '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + |
... | @@ -378,4 +378,31 @@ | ... | @@ -378,4 +378,31 @@ |
378 | loader.media('unrecognized.m3u8'); | 378 | loader.media('unrecognized.m3u8'); |
379 | }, 'throws an error'); | 379 | }, 'throws an error'); |
380 | }); | 380 | }); |
381 | |||
382 | test('dispose cancels the refresh timeout', function() { | ||
383 | var loader = new videojs.Hls.PlaylistLoader('live.m3u8'); | ||
384 | requests.pop().respond(200, null, | ||
385 | '#EXTM3U\n' + | ||
386 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
387 | '#EXTINF:10,\n' + | ||
388 | '0.ts\n'); | ||
389 | loader.dispose(); | ||
390 | // a lot of time passes... | ||
391 | clock.tick(15 * 1000); | ||
392 | |||
393 | strictEqual(requests.length, 0, 'no refresh request was made'); | ||
394 | }); | ||
395 | |||
396 | test('dispose aborts pending refresh requests', function() { | ||
397 | var loader = new videojs.Hls.PlaylistLoader('live.m3u8'); | ||
398 | requests.pop().respond(200, null, | ||
399 | '#EXTM3U\n' + | ||
400 | '#EXT-X-MEDIA-SEQUENCE:0\n' + | ||
401 | '#EXTINF:10,\n' + | ||
402 | '0.ts\n'); | ||
403 | clock.tick(10 * 1000); | ||
404 | |||
405 | loader.dispose(); | ||
406 | ok(requests[0].aborted, 'refresh request aborted'); | ||
407 | }); | ||
381 | })(window); | 408 | })(window); | ... | ... |
... | @@ -39,7 +39,7 @@ | ... | @@ -39,7 +39,7 @@ |
39 | 39 | ||
40 | module('segment parser', { | 40 | module('segment parser', { |
41 | setup: function() { | 41 | setup: function() { |
42 | parser = new window.videojs.hls.SegmentParser(); | 42 | parser = new window.videojs.Hls.SegmentParser(); |
43 | } | 43 | } |
44 | }); | 44 | }); |
45 | 45 | ||
... | @@ -168,11 +168,11 @@ | ... | @@ -168,11 +168,11 @@ |
168 | result = result.concat(makePsi(settings)); | 168 | result = result.concat(makePsi(settings)); |
169 | 169 | ||
170 | // ensure the resulting packet is the correct size | 170 | // ensure the resulting packet is the correct size |
171 | result.length = window.videojs.hls.SegmentParser.MP2T_PACKET_LENGTH; | 171 | result.length = window.videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH; |
172 | return result; | 172 | return result; |
173 | }, | 173 | }, |
174 | h264Type = window.videojs.hls.SegmentParser.STREAM_TYPES.h264, | 174 | h264Type = window.videojs.Hls.SegmentParser.STREAM_TYPES.h264, |
175 | adtsType = window.videojs.hls.SegmentParser.STREAM_TYPES.adts; | 175 | adtsType = window.videojs.Hls.SegmentParser.STREAM_TYPES.adts; |
176 | 176 | ||
177 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | 177 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ |
178 | programs: { | 178 | programs: { | ... | ... |
1 | (function(window, videojs, undefined) { | 1 | (function(window, videojs, undefined) { |
2 | 'use strict'; | ||
2 | /* | 3 | /* |
3 | ======== A Handy Little QUnit Reference ======== | 4 | ======== A Handy Little QUnit Reference ======== |
4 | http://api.qunitjs.com/ | 5 | http://api.qunitjs.com/ |
... | @@ -22,15 +23,37 @@ | ... | @@ -22,15 +23,37 @@ |
22 | 23 | ||
23 | var | 24 | var |
24 | player, | 25 | player, |
25 | oldFlashSupported, | 26 | oldMediaSourceOpen, |
26 | oldSegmentParser, | 27 | oldSegmentParser, |
27 | oldSetTimeout, | 28 | oldSetTimeout, |
28 | oldSourceBuffer, | 29 | oldSourceBuffer, |
29 | oldSupportsNativeHls, | 30 | oldFlashSupported, |
30 | xhrUrls, | ||
31 | requests, | 31 | requests, |
32 | xhr, | 32 | xhr, |
33 | 33 | ||
34 | createPlayer = function(options) { | ||
35 | var tech, video, player; | ||
36 | video = document.createElement('video'); | ||
37 | document.querySelector('#qunit-fixture').appendChild(video); | ||
38 | player = videojs(video, { | ||
39 | flash: { | ||
40 | swf: '' | ||
41 | }, | ||
42 | techOrder: ['hls'], | ||
43 | hls: options || {} | ||
44 | }); | ||
45 | |||
46 | player.buffered = function() { | ||
47 | return videojs.createTimeRange(0, 0); | ||
48 | }; | ||
49 | |||
50 | tech = player.el().querySelector('.vjs-tech'); | ||
51 | tech.vjs_getProperty = function() {}; | ||
52 | tech.vjs_src = function() {}; | ||
53 | videojs.Flash.onReady(tech.id); | ||
54 | |||
55 | return player; | ||
56 | }, | ||
34 | standardXHRResponse = function(request) { | 57 | standardXHRResponse = function(request) { |
35 | if (!request.url) { | 58 | if (!request.url) { |
36 | return; | 59 | return; |
... | @@ -82,37 +105,23 @@ var | ... | @@ -82,37 +105,23 @@ var |
82 | 105 | ||
83 | module('HLS', { | 106 | module('HLS', { |
84 | setup: function() { | 107 | setup: function() { |
108 | oldMediaSourceOpen = videojs.MediaSource.open; | ||
109 | videojs.MediaSource.open = function() {}; | ||
85 | 110 | ||
86 | // mock out Flash features for phantomjs | 111 | // mock out Flash features for phantomjs |
87 | oldFlashSupported = videojs.Flash.isSupported; | 112 | oldFlashSupported = videojs.Flash.isSupported; |
88 | videojs.Flash.isSupported = function() { | 113 | videojs.Flash.isSupported = function() { |
89 | return true; | 114 | return true; |
90 | }; | 115 | }; |
116 | |||
91 | oldSourceBuffer = window.videojs.SourceBuffer; | 117 | oldSourceBuffer = window.videojs.SourceBuffer; |
92 | window.videojs.SourceBuffer = function() { | 118 | window.videojs.SourceBuffer = function() { |
93 | this.appendBuffer = function() {}; | 119 | this.appendBuffer = function() {}; |
94 | this.abort = function() {}; | 120 | this.abort = function() {}; |
95 | }; | 121 | }; |
96 | 122 | ||
97 | // force native HLS to be ignored | ||
98 | oldSupportsNativeHls = videojs.hls.supportsNativeHls; | ||
99 | videojs.hls.supportsNativeHls = false; | ||
100 | |||
101 | // create the test player | ||
102 | var video = document.createElement('video'); | ||
103 | document.querySelector('#qunit-fixture').appendChild(video); | ||
104 | player = videojs(video, { | ||
105 | flash: { | ||
106 | swf: '../node_modules/video.js/dist/video-js/video-js.swf' | ||
107 | }, | ||
108 | techOrder: ['flash'] | ||
109 | }); | ||
110 | player.buffered = function() { | ||
111 | return videojs.createTimeRange(0, 0); | ||
112 | }; | ||
113 | |||
114 | // store functionality that some tests need to mock | 123 | // store functionality that some tests need to mock |
115 | oldSegmentParser = videojs.hls.SegmentParser; | 124 | oldSegmentParser = videojs.Hls.SegmentParser; |
116 | oldSetTimeout = window.setTimeout; | 125 | oldSetTimeout = window.setTimeout; |
117 | 126 | ||
118 | // fake XHRs | 127 | // fake XHRs |
... | @@ -121,13 +130,16 @@ module('HLS', { | ... | @@ -121,13 +130,16 @@ module('HLS', { |
121 | xhr.onCreate = function(xhr) { | 130 | xhr.onCreate = function(xhr) { |
122 | requests.push(xhr); | 131 | requests.push(xhr); |
123 | }; | 132 | }; |
124 | xhrUrls = []; | 133 | |
134 | // create the test player | ||
135 | player = createPlayer(); | ||
125 | }, | 136 | }, |
126 | 137 | ||
127 | teardown: function() { | 138 | teardown: function() { |
139 | player.dispose(); | ||
128 | videojs.Flash.isSupported = oldFlashSupported; | 140 | videojs.Flash.isSupported = oldFlashSupported; |
129 | videojs.hls.supportsNativeHls = oldSupportsNativeHls; | 141 | videojs.MediaSource.open = oldMediaSourceOpen; |
130 | videojs.hls.SegmentParser = oldSegmentParser; | 142 | videojs.Hls.SegmentParser = oldSegmentParser; |
131 | videojs.SourceBuffer = oldSourceBuffer; | 143 | videojs.SourceBuffer = oldSourceBuffer; |
132 | window.setTimeout = oldSetTimeout; | 144 | window.setTimeout = oldSetTimeout; |
133 | xhr.restore(); | 145 | xhr.restore(); |
... | @@ -140,8 +152,11 @@ test('starts playing if autoplay is specified', function() { | ... | @@ -140,8 +152,11 @@ test('starts playing if autoplay is specified', function() { |
140 | plays++; | 152 | plays++; |
141 | }; | 153 | }; |
142 | player.options().autoplay = true; | 154 | player.options().autoplay = true; |
143 | player.hls('manifest/playlist.m3u8'); | 155 | player.src({ |
144 | videojs.mediaSources[player.currentSrc()].trigger({ | 156 | src: 'manifest/playlist.m3u8', |
157 | type: 'application/vnd.apple.mpegurl' | ||
158 | }); | ||
159 | player.hls.mediaSource.trigger({ | ||
145 | type: 'sourceopen' | 160 | type: 'sourceopen' |
146 | }); | 161 | }); |
147 | 162 | ||
... | @@ -155,9 +170,12 @@ test('creates a PlaylistLoader on init', function() { | ... | @@ -155,9 +170,12 @@ test('creates a PlaylistLoader on init', function() { |
155 | loadedmetadata = true; | 170 | loadedmetadata = true; |
156 | }); | 171 | }); |
157 | 172 | ||
158 | player.hls('manifest/playlist.m3u8'); | 173 | ok(!player.hls.playlists, 'waits for set src to create the loader'); |
159 | ok(!player.hls.playlists, 'waits for sourceopen to create the loader'); | 174 | player.src({ |
160 | videojs.mediaSources[player.currentSrc()].trigger({ | 175 | src:'manifest/playlist.m3u8', |
176 | type: 'application/vnd.apple.mpegurl' | ||
177 | }); | ||
178 | player.hls.mediaSource.trigger({ | ||
161 | type: 'sourceopen' | 179 | type: 'sourceopen' |
162 | }); | 180 | }); |
163 | standardXHRResponse(requests[0]); | 181 | standardXHRResponse(requests[0]); |
... | @@ -178,8 +196,11 @@ test('sets the duration if one is available on the playlist', function() { | ... | @@ -178,8 +196,11 @@ test('sets the duration if one is available on the playlist', function() { |
178 | } | 196 | } |
179 | calls++; | 197 | calls++; |
180 | }; | 198 | }; |
181 | player.hls('manifest/media.m3u8'); | 199 | player.src({ |
182 | videojs.mediaSources[player.currentSrc()].trigger({ | 200 | src: 'manifest/media.m3u8', |
201 | type: 'application/vnd.apple.mpegurl' | ||
202 | }); | ||
203 | player.hls.mediaSource.trigger({ | ||
183 | type: 'sourceopen' | 204 | type: 'sourceopen' |
184 | }); | 205 | }); |
185 | 206 | ||
... | @@ -197,8 +218,11 @@ test('calculates the duration if needed', function() { | ... | @@ -197,8 +218,11 @@ test('calculates the duration if needed', function() { |
197 | } | 218 | } |
198 | durations.push(duration); | 219 | durations.push(duration); |
199 | }; | 220 | }; |
200 | player.hls('http://example.com/manifest/missingExtinf.m3u8'); | 221 | player.src({ |
201 | videojs.mediaSources[player.currentSrc()].trigger({ | 222 | src: 'http://example.com/manifest/missingExtinf.m3u8', |
223 | type: 'application/vnd.apple.mpegurl' | ||
224 | }); | ||
225 | player.hls.mediaSource.trigger({ | ||
202 | type: 'sourceopen' | 226 | type: 'sourceopen' |
203 | }); | 227 | }); |
204 | 228 | ||
... | @@ -210,11 +234,14 @@ test('calculates the duration if needed', function() { | ... | @@ -210,11 +234,14 @@ test('calculates the duration if needed', function() { |
210 | }); | 234 | }); |
211 | 235 | ||
212 | test('starts downloading a segment on loadedmetadata', function() { | 236 | test('starts downloading a segment on loadedmetadata', function() { |
213 | player.hls('manifest/media.m3u8'); | 237 | player.src({ |
238 | src: 'manifest/media.m3u8', | ||
239 | type: 'application/vnd.apple.mpegurl' | ||
240 | }); | ||
214 | player.buffered = function() { | 241 | player.buffered = function() { |
215 | return videojs.createTimeRange(0, 0); | 242 | return videojs.createTimeRange(0, 0); |
216 | }; | 243 | }; |
217 | videojs.mediaSources[player.currentSrc()].trigger({ | 244 | player.hls.mediaSource.trigger({ |
218 | type: 'sourceopen' | 245 | type: 'sourceopen' |
219 | }); | 246 | }); |
220 | 247 | ||
... | @@ -228,8 +255,11 @@ test('starts downloading a segment on loadedmetadata', function() { | ... | @@ -228,8 +255,11 @@ test('starts downloading a segment on loadedmetadata', function() { |
228 | }); | 255 | }); |
229 | 256 | ||
230 | test('recognizes absolute URIs and requests them unmodified', function() { | 257 | test('recognizes absolute URIs and requests them unmodified', function() { |
231 | player.hls('manifest/absoluteUris.m3u8'); | 258 | player.src({ |
232 | videojs.mediaSources[player.currentSrc()].trigger({ | 259 | src: 'manifest/absoluteUris.m3u8', |
260 | type: 'application/vnd.apple.mpegurl' | ||
261 | }); | ||
262 | player.hls.mediaSource.trigger({ | ||
233 | type: 'sourceopen' | 263 | type: 'sourceopen' |
234 | }); | 264 | }); |
235 | 265 | ||
... | @@ -241,8 +271,11 @@ test('recognizes absolute URIs and requests them unmodified', function() { | ... | @@ -241,8 +271,11 @@ test('recognizes absolute URIs and requests them unmodified', function() { |
241 | }); | 271 | }); |
242 | 272 | ||
243 | test('recognizes domain-relative URLs', function() { | 273 | test('recognizes domain-relative URLs', function() { |
244 | player.hls('manifest/domainUris.m3u8'); | 274 | player.src({ |
245 | videojs.mediaSources[player.currentSrc()].trigger({ | 275 | src: 'manifest/domainUris.m3u8', |
276 | type: 'application/vnd.apple.mpegurl' | ||
277 | }); | ||
278 | player.hls.mediaSource.trigger({ | ||
246 | type: 'sourceopen' | 279 | type: 'sourceopen' |
247 | }); | 280 | }); |
248 | 281 | ||
... | @@ -253,14 +286,31 @@ test('recognizes domain-relative URLs', function() { | ... | @@ -253,14 +286,31 @@ test('recognizes domain-relative URLs', function() { |
253 | 'the first segment is requested'); | 286 | 'the first segment is requested'); |
254 | }); | 287 | }); |
255 | 288 | ||
256 | test('re-initializes the plugin for each source', function() { | 289 | test('re-initializes the tech for each source', function() { |
257 | var firstInit, secondInit; | 290 | var firstPlaylists, secondPlaylists, firstMSE, secondMSE; |
258 | player.hls('manifest/master.m3u8'); | 291 | |
259 | firstInit = player.hls; | 292 | player.src({ |
260 | player.hls('manifest/master.m3u8'); | 293 | src: 'manifest/master.m3u8', |
261 | secondInit = player.hls; | 294 | type: 'application/vnd.apple.mpegurl' |
295 | }); | ||
296 | player.hls.mediaSource.trigger({ | ||
297 | type: 'sourceopen' | ||
298 | }); | ||
299 | firstPlaylists = player.hls.playlists; | ||
300 | firstMSE = player.hls.mediaSource; | ||
301 | |||
302 | player.src({ | ||
303 | src: 'manifest/master.m3u8', | ||
304 | type: 'application/vnd.apple.mpegurl' | ||
305 | }); | ||
306 | player.hls.mediaSource.trigger({ | ||
307 | type: 'sourceopen' | ||
308 | }); | ||
309 | secondPlaylists = player.hls.playlists; | ||
310 | secondMSE = player.hls.mediaSource; | ||
262 | 311 | ||
263 | notStrictEqual(firstInit, secondInit, 'the plugin object is replaced'); | 312 | notStrictEqual(firstPlaylists, secondPlaylists, 'the playlist object is not reused'); |
313 | notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused'); | ||
264 | }); | 314 | }); |
265 | 315 | ||
266 | test('triggers an error when a master playlist request errors', function() { | 316 | test('triggers an error when a master playlist request errors', function() { |
... | @@ -268,8 +318,11 @@ test('triggers an error when a master playlist request errors', function() { | ... | @@ -268,8 +318,11 @@ test('triggers an error when a master playlist request errors', function() { |
268 | player.on('error', function() { | 318 | player.on('error', function() { |
269 | error = player.hls.error; | 319 | error = player.hls.error; |
270 | }); | 320 | }); |
271 | player.hls('manifest/master.m3u8'); | 321 | player.src({ |
272 | videojs.mediaSources[player.currentSrc()].trigger({ | 322 | src: 'manifest/master.m3u8', |
323 | type: 'application/vnd.apple.mpegurl' | ||
324 | }); | ||
325 | player.hls.mediaSource.trigger({ | ||
273 | type: 'sourceopen' | 326 | type: 'sourceopen' |
274 | }); | 327 | }); |
275 | requests.pop().respond(500); | 328 | requests.pop().respond(500); |
... | @@ -279,8 +332,11 @@ test('triggers an error when a master playlist request errors', function() { | ... | @@ -279,8 +332,11 @@ test('triggers an error when a master playlist request errors', function() { |
279 | }); | 332 | }); |
280 | 333 | ||
281 | test('downloads media playlists after loading the master', function() { | 334 | test('downloads media playlists after loading the master', function() { |
282 | player.hls('manifest/master.m3u8'); | 335 | player.src({ |
283 | videojs.mediaSources[player.currentSrc()].trigger({ | 336 | src: 'manifest/master.m3u8', |
337 | type: 'application/vnd.apple.mpegurl' | ||
338 | }); | ||
339 | player.hls.mediaSource.trigger({ | ||
284 | type: 'sourceopen' | 340 | type: 'sourceopen' |
285 | }); | 341 | }); |
286 | 342 | ||
... | @@ -309,8 +365,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read | ... | @@ -309,8 +365,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read |
309 | }; | 365 | }; |
310 | this.send = function() {}; | 366 | this.send = function() {}; |
311 | }; | 367 | }; |
312 | player.hls('manifest/media.m3u8'); | 368 | player.src({ |
313 | videojs.mediaSources[player.currentSrc()].trigger({ | 369 | src: 'manifest/media.m3u8', |
370 | type: 'application/vnd.apple.mpegurl' | ||
371 | }); | ||
372 | player.hls.mediaSource.trigger({ | ||
314 | type: 'sourceopen' | 373 | type: 'sourceopen' |
315 | }); | 374 | }); |
316 | player.trigger('timeupdate'); | 375 | player.trigger('timeupdate'); |
... | @@ -320,8 +379,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read | ... | @@ -320,8 +379,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read |
320 | }); | 379 | }); |
321 | 380 | ||
322 | test('calculates the bandwidth after downloading a segment', function() { | 381 | test('calculates the bandwidth after downloading a segment', function() { |
323 | player.hls('manifest/media.m3u8'); | 382 | player.src({ |
324 | videojs.mediaSources[player.currentSrc()].trigger({ | 383 | src: 'manifest/media.m3u8', |
384 | type: 'application/vnd.apple.mpegurl' | ||
385 | }); | ||
386 | player.hls.mediaSource.trigger({ | ||
325 | type: 'sourceopen' | 387 | type: 'sourceopen' |
326 | }); | 388 | }); |
327 | 389 | ||
... | @@ -337,12 +399,15 @@ test('calculates the bandwidth after downloading a segment', function() { | ... | @@ -337,12 +399,15 @@ test('calculates the bandwidth after downloading a segment', function() { |
337 | 399 | ||
338 | test('selects a playlist after segment downloads', function() { | 400 | test('selects a playlist after segment downloads', function() { |
339 | var calls = 0; | 401 | var calls = 0; |
340 | player.hls('manifest/master.m3u8'); | 402 | player.src({ |
403 | src: 'manifest/master.m3u8', | ||
404 | type: 'application/vnd.apple.mpegurl' | ||
405 | }); | ||
341 | player.hls.selectPlaylist = function() { | 406 | player.hls.selectPlaylist = function() { |
342 | calls++; | 407 | calls++; |
343 | return player.hls.playlists.master.playlists[0]; | 408 | return player.hls.playlists.master.playlists[0]; |
344 | }; | 409 | }; |
345 | videojs.mediaSources[player.currentSrc()].trigger({ | 410 | player.hls.mediaSource.trigger({ |
346 | type: 'sourceopen' | 411 | type: 'sourceopen' |
347 | }); | 412 | }); |
348 | 413 | ||
... | @@ -367,8 +432,11 @@ test('selects a playlist after segment downloads', function() { | ... | @@ -367,8 +432,11 @@ test('selects a playlist after segment downloads', function() { |
367 | test('moves to the next segment if there is a network error', function() { | 432 | test('moves to the next segment if there is a network error', function() { |
368 | var mediaIndex; | 433 | var mediaIndex; |
369 | 434 | ||
370 | player.hls('manifest/master.m3u8'); | 435 | player.src({ |
371 | videojs.mediaSources[player.currentSrc()].trigger({ | 436 | src: 'manifest/master.m3u8', |
437 | type: 'application/vnd.apple.mpegurl' | ||
438 | }); | ||
439 | player.hls.mediaSource.trigger({ | ||
372 | type: 'sourceopen' | 440 | type: 'sourceopen' |
373 | }); | 441 | }); |
374 | 442 | ||
... | @@ -386,7 +454,10 @@ test('updates the duration after switching playlists', function() { | ... | @@ -386,7 +454,10 @@ test('updates the duration after switching playlists', function() { |
386 | var | 454 | var |
387 | calls = 0, | 455 | calls = 0, |
388 | selectedPlaylist = false; | 456 | selectedPlaylist = false; |
389 | player.hls('manifest/master.m3u8'); | 457 | player.src({ |
458 | src: 'manifest/master.m3u8', | ||
459 | type: 'application/vnd.apple.mpegurl' | ||
460 | }); | ||
390 | player.hls.selectPlaylist = function() { | 461 | player.hls.selectPlaylist = function() { |
391 | selectedPlaylist = true; | 462 | selectedPlaylist = true; |
392 | return player.hls.playlists.master.playlists[1]; | 463 | return player.hls.playlists.master.playlists[1]; |
... | @@ -400,7 +471,7 @@ test('updates the duration after switching playlists', function() { | ... | @@ -400,7 +471,7 @@ test('updates the duration after switching playlists', function() { |
400 | calls++; | 471 | calls++; |
401 | } | 472 | } |
402 | }; | 473 | }; |
403 | videojs.mediaSources[player.currentSrc()].trigger({ | 474 | player.hls.mediaSource.trigger({ |
404 | type: 'sourceopen' | 475 | type: 'sourceopen' |
405 | }); | 476 | }); |
406 | 477 | ||
... | @@ -418,8 +489,11 @@ test('downloads additional playlists if required', function() { | ... | @@ -418,8 +489,11 @@ test('downloads additional playlists if required', function() { |
418 | playlist = { | 489 | playlist = { |
419 | uri: 'media3.m3u8' | 490 | uri: 'media3.m3u8' |
420 | }; | 491 | }; |
421 | player.hls('manifest/master.m3u8'); | 492 | player.src({ |
422 | videojs.mediaSources[player.currentSrc()].trigger({ | 493 | src: 'manifest/master.m3u8', |
494 | type: 'application/vnd.apple.mpegurl' | ||
495 | }); | ||
496 | player.hls.mediaSource.trigger({ | ||
423 | type: 'sourceopen' | 497 | type: 'sourceopen' |
424 | }); | 498 | }); |
425 | 499 | ||
... | @@ -456,8 +530,11 @@ test('downloads additional playlists if required', function() { | ... | @@ -456,8 +530,11 @@ test('downloads additional playlists if required', function() { |
456 | 530 | ||
457 | test('selects a playlist below the current bandwidth', function() { | 531 | test('selects a playlist below the current bandwidth', function() { |
458 | var playlist; | 532 | var playlist; |
459 | player.hls('manifest/master.m3u8'); | 533 | player.src({ |
460 | videojs.mediaSources[player.currentSrc()].trigger({ | 534 | src: 'manifest/master.m3u8', |
535 | type: 'application/vnd.apple.mpegurl' | ||
536 | }); | ||
537 | player.hls.mediaSource.trigger({ | ||
461 | type: 'sourceopen' | 538 | type: 'sourceopen' |
462 | }); | 539 | }); |
463 | 540 | ||
... | @@ -478,8 +555,11 @@ test('selects a playlist below the current bandwidth', function() { | ... | @@ -478,8 +555,11 @@ test('selects a playlist below the current bandwidth', function() { |
478 | 555 | ||
479 | test('raises the minimum bitrate for a stream proportionially', function() { | 556 | test('raises the minimum bitrate for a stream proportionially', function() { |
480 | var playlist; | 557 | var playlist; |
481 | player.hls('manifest/master.m3u8'); | 558 | player.src({ |
482 | videojs.mediaSources[player.currentSrc()].trigger({ | 559 | src: 'manifest/master.m3u8', |
560 | type: 'application/vnd.apple.mpegurl' | ||
561 | }); | ||
562 | player.hls.mediaSource.trigger({ | ||
483 | type: 'sourceopen' | 563 | type: 'sourceopen' |
484 | }); | 564 | }); |
485 | 565 | ||
... | @@ -500,8 +580,11 @@ test('raises the minimum bitrate for a stream proportionially', function() { | ... | @@ -500,8 +580,11 @@ test('raises the minimum bitrate for a stream proportionially', function() { |
500 | 580 | ||
501 | test('uses the lowest bitrate if no other is suitable', function() { | 581 | test('uses the lowest bitrate if no other is suitable', function() { |
502 | var playlist; | 582 | var playlist; |
503 | player.hls('manifest/master.m3u8'); | 583 | player.src({ |
504 | videojs.mediaSources[player.currentSrc()].trigger({ | 584 | src: 'manifest/master.m3u8', |
585 | type: 'application/vnd.apple.mpegurl' | ||
586 | }); | ||
587 | player.hls.mediaSource.trigger({ | ||
505 | type: 'sourceopen' | 588 | type: 'sourceopen' |
506 | }); | 589 | }); |
507 | 590 | ||
... | @@ -520,9 +603,12 @@ test('uses the lowest bitrate if no other is suitable', function() { | ... | @@ -520,9 +603,12 @@ test('uses the lowest bitrate if no other is suitable', function() { |
520 | test('selects the correct rendition by player dimensions', function() { | 603 | test('selects the correct rendition by player dimensions', function() { |
521 | var playlist; | 604 | var playlist; |
522 | 605 | ||
523 | player.hls('manifest/master.m3u8'); | 606 | player.src({ |
607 | src: 'manifest/master.m3u8', | ||
608 | type: 'application/vnd.apple.mpegurl' | ||
609 | }); | ||
524 | 610 | ||
525 | videojs.mediaSources[player.currentSrc()].trigger({ | 611 | player.hls.mediaSource.trigger({ |
526 | type: 'sourceopen' | 612 | type: 'sourceopen' |
527 | }); | 613 | }); |
528 | 614 | ||
... | @@ -550,14 +636,17 @@ test('selects the correct rendition by player dimensions', function() { | ... | @@ -550,14 +636,17 @@ test('selects the correct rendition by player dimensions', function() { |
550 | 636 | ||
551 | 637 | ||
552 | test('does not download the next segment if the buffer is full', function() { | 638 | test('does not download the next segment if the buffer is full', function() { |
553 | player.hls('manifest/media.m3u8'); | 639 | player.src({ |
640 | src: 'manifest/media.m3u8', | ||
641 | type: 'application/vnd.apple.mpegurl' | ||
642 | }); | ||
554 | player.currentTime = function() { | 643 | player.currentTime = function() { |
555 | return 15; | 644 | return 15; |
556 | }; | 645 | }; |
557 | player.buffered = function() { | 646 | player.buffered = function() { |
558 | return videojs.createTimeRange(0, 20); | 647 | return videojs.createTimeRange(0, 20); |
559 | }; | 648 | }; |
560 | videojs.mediaSources[player.currentSrc()].trigger({ | 649 | player.hls.mediaSource.trigger({ |
561 | type: 'sourceopen' | 650 | type: 'sourceopen' |
562 | }); | 651 | }); |
563 | 652 | ||
... | @@ -569,8 +658,11 @@ test('does not download the next segment if the buffer is full', function() { | ... | @@ -569,8 +658,11 @@ test('does not download the next segment if the buffer is full', function() { |
569 | }); | 658 | }); |
570 | 659 | ||
571 | test('downloads the next segment if the buffer is getting low', function() { | 660 | test('downloads the next segment if the buffer is getting low', function() { |
572 | player.hls('manifest/media.m3u8'); | 661 | player.src({ |
573 | videojs.mediaSources[player.currentSrc()].trigger({ | 662 | src: 'manifest/media.m3u8', |
663 | type: 'application/vnd.apple.mpegurl' | ||
664 | }); | ||
665 | player.hls.mediaSource.trigger({ | ||
574 | type: 'sourceopen' | 666 | type: 'sourceopen' |
575 | }); | 667 | }); |
576 | 668 | ||
... | @@ -597,8 +689,11 @@ test('downloads the next segment if the buffer is getting low', function() { | ... | @@ -597,8 +689,11 @@ test('downloads the next segment if the buffer is getting low', function() { |
597 | }); | 689 | }); |
598 | 690 | ||
599 | test('stops downloading segments at the end of the playlist', function() { | 691 | test('stops downloading segments at the end of the playlist', function() { |
600 | player.hls('manifest/media.m3u8'); | 692 | player.src({ |
601 | videojs.mediaSources[player.currentSrc()].trigger({ | 693 | src: 'manifest/media.m3u8', |
694 | type: 'application/vnd.apple.mpegurl' | ||
695 | }); | ||
696 | player.hls.mediaSource.trigger({ | ||
602 | type: 'sourceopen' | 697 | type: 'sourceopen' |
603 | }); | 698 | }); |
604 | standardXHRResponse(requests[0]); | 699 | standardXHRResponse(requests[0]); |
... | @@ -606,13 +701,16 @@ test('stops downloading segments at the end of the playlist', function() { | ... | @@ -606,13 +701,16 @@ test('stops downloading segments at the end of the playlist', function() { |
606 | player.hls.mediaIndex = 4; | 701 | player.hls.mediaIndex = 4; |
607 | player.trigger('timeupdate'); | 702 | player.trigger('timeupdate'); |
608 | 703 | ||
609 | strictEqual(xhrUrls.length, 0, 'no request is made'); | 704 | strictEqual(requests.length, 0, 'no request is made'); |
610 | }); | 705 | }); |
611 | 706 | ||
612 | test('only makes one segment request at a time', function() { | 707 | test('only makes one segment request at a time', function() { |
613 | var openedXhrs = 0; | 708 | var openedXhrs = 0; |
614 | player.hls('manifest/media.m3u8'); | 709 | player.src({ |
615 | videojs.mediaSources[player.currentSrc()].trigger({ | 710 | src: 'manifest/media.m3u8', |
711 | type: 'application/vnd.apple.mpegurl' | ||
712 | }); | ||
713 | player.hls.mediaSource.trigger({ | ||
616 | type: 'sourceopen' | 714 | type: 'sourceopen' |
617 | }); | 715 | }); |
618 | xhr.restore(); | 716 | xhr.restore(); |
... | @@ -634,68 +732,12 @@ test('only makes one segment request at a time', function() { | ... | @@ -634,68 +732,12 @@ test('only makes one segment request at a time', function() { |
634 | xhr = sinon.useFakeXMLHttpRequest(); | 732 | xhr = sinon.useFakeXMLHttpRequest(); |
635 | }); | 733 | }); |
636 | 734 | ||
637 | test('uses the src attribute if no options are provided and it ends in ".m3u8"', function() { | ||
638 | var url = 'http://example.com/services/mobile/streaming/index/master.m3u8?videoId=1824650741001'; | ||
639 | player.el().querySelector('.vjs-tech').src = url; | ||
640 | player.hls(); | ||
641 | videojs.mediaSources[player.currentSrc()].trigger({ | ||
642 | type: 'sourceopen' | ||
643 | }); | ||
644 | |||
645 | strictEqual(requests[0].url, url, 'currentSrc is used'); | ||
646 | }); | ||
647 | |||
648 | test('ignores src attribute if it doesn\'t have the "m3u8" extension', function() { | ||
649 | var tech = player.el().querySelector('.vjs-tech'); | ||
650 | tech.src = 'basdfasdfasdfliel//.m3u9'; | ||
651 | player.hls(); | ||
652 | ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); | ||
653 | strictEqual(requests.length, 0, 'no request is made'); | ||
654 | |||
655 | tech.src = ''; | ||
656 | player.hls(); | ||
657 | ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); | ||
658 | strictEqual(requests.length, 0, 'no request is made'); | ||
659 | |||
660 | tech.src = 'http://example.com/movie.mp4?q=why.m3u8'; | ||
661 | player.hls(); | ||
662 | ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); | ||
663 | strictEqual(requests.length, 0, 'no request is made'); | ||
664 | |||
665 | tech.src = 'http://example.m3u8/movie.mp4'; | ||
666 | player.hls(); | ||
667 | ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); | ||
668 | strictEqual(requests.length, 0, 'no request is made'); | ||
669 | |||
670 | tech.src = '//example.com/movie.mp4#http://tricky.com/master.m3u8'; | ||
671 | player.hls(); | ||
672 | ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); | ||
673 | strictEqual(requests.length, 0, 'no request is made'); | ||
674 | }); | ||
675 | |||
676 | test('activates if the first playable source is HLS', function() { | ||
677 | var video; | ||
678 | document.querySelector('#qunit-fixture').innerHTML = | ||
679 | '<video controls>' + | ||
680 | '<source type="slartibartfast$%" src="movie.slarti">' + | ||
681 | '<source type="application/x-mpegURL" src="movie.m3u8">' + | ||
682 | '<source type="video/mp4" src="movie.mp4">' + | ||
683 | '</video>'; | ||
684 | video = document.querySelector('#qunit-fixture video'); | ||
685 | player = videojs(video, { | ||
686 | flash: { | ||
687 | swf: '../node_modules/video.js/dist/video-js/video-js.swf' | ||
688 | }, | ||
689 | techOrder: ['flash'] | ||
690 | }); | ||
691 | player.hls(); | ||
692 | |||
693 | ok(player.currentSrc() in videojs.mediaSources, 'media source created'); | ||
694 | }); | ||
695 | |||
696 | test('cancels outstanding XHRs when seeking', function() { | 735 | test('cancels outstanding XHRs when seeking', function() { |
697 | player.hls('manifest/media.m3u8'); | 736 | player.src({ |
698 | videojs.mediaSources[player.currentSrc()].trigger({ | 737 | src: 'manifest/media.m3u8', |
738 | type: 'application/vnd.apple.mpegurl' | ||
739 | }); | ||
740 | player.hls.mediaSource.trigger({ | ||
699 | type: 'sourceopen' | 741 | type: 'sourceopen' |
700 | }); | 742 | }); |
701 | standardXHRResponse(requests[0]); | 743 | standardXHRResponse(requests[0]); |
... | @@ -721,7 +763,7 @@ test('cancels outstanding XHRs when seeking', function() { | ... | @@ -721,7 +763,7 @@ test('cancels outstanding XHRs when seeking', function() { |
721 | test('flushes the parser after each segment', function() { | 763 | test('flushes the parser after each segment', function() { |
722 | var flushes = 0; | 764 | var flushes = 0; |
723 | // mock out the segment parser | 765 | // mock out the segment parser |
724 | videojs.hls.SegmentParser = function() { | 766 | videojs.Hls.SegmentParser = function() { |
725 | this.getFlvHeader = function() { | 767 | this.getFlvHeader = function() { |
726 | return []; | 768 | return []; |
727 | }; | 769 | }; |
... | @@ -732,8 +774,11 @@ test('flushes the parser after each segment', function() { | ... | @@ -732,8 +774,11 @@ test('flushes the parser after each segment', function() { |
732 | this.tagsAvailable = function() {}; | 774 | this.tagsAvailable = function() {}; |
733 | }; | 775 | }; |
734 | 776 | ||
735 | player.hls('manifest/media.m3u8'); | 777 | player.src({ |
736 | videojs.mediaSources[player.currentSrc()].trigger({ | 778 | src: 'manifest/media.m3u8', |
779 | type: 'application/vnd.apple.mpegurl' | ||
780 | }); | ||
781 | player.hls.mediaSource.trigger({ | ||
737 | type: 'sourceopen' | 782 | type: 'sourceopen' |
738 | }); | 783 | }); |
739 | 784 | ||
... | @@ -749,7 +794,7 @@ test('drops tags before the target timestamp when seeking', function() { | ... | @@ -749,7 +794,7 @@ test('drops tags before the target timestamp when seeking', function() { |
749 | bytes = []; | 794 | bytes = []; |
750 | 795 | ||
751 | // mock out the parser and source buffer | 796 | // mock out the parser and source buffer |
752 | videojs.hls.SegmentParser = mockSegmentParser(tags); | 797 | videojs.Hls.SegmentParser = mockSegmentParser(tags); |
753 | window.videojs.SourceBuffer = function() { | 798 | window.videojs.SourceBuffer = function() { |
754 | this.appendBuffer = function(chunk) { | 799 | this.appendBuffer = function(chunk) { |
755 | bytes.push(chunk); | 800 | bytes.push(chunk); |
... | @@ -764,8 +809,11 @@ test('drops tags before the target timestamp when seeking', function() { | ... | @@ -764,8 +809,11 @@ test('drops tags before the target timestamp when seeking', function() { |
764 | // push a tag into the buffer | 809 | // push a tag into the buffer |
765 | tags.push({ pts: 0, bytes: 0 }); | 810 | tags.push({ pts: 0, bytes: 0 }); |
766 | 811 | ||
767 | player.hls('manifest/media.m3u8'); | 812 | player.src({ |
768 | videojs.mediaSources[player.currentSrc()].trigger({ | 813 | src: 'manifest/media.m3u8', |
814 | type: 'application/vnd.apple.mpegurl' | ||
815 | }); | ||
816 | player.hls.mediaSource.trigger({ | ||
769 | type: 'sourceopen' | 817 | type: 'sourceopen' |
770 | }); | 818 | }); |
771 | standardXHRResponse(requests[0]); | 819 | standardXHRResponse(requests[0]); |
... | @@ -803,7 +851,7 @@ test('clears pending buffer updates when seeking', function() { | ... | @@ -803,7 +851,7 @@ test('clears pending buffer updates when seeking', function() { |
803 | tags = [{ pts: 0, bytes: 0 }]; | 851 | tags = [{ pts: 0, bytes: 0 }]; |
804 | 852 | ||
805 | // mock out the parser and source buffer | 853 | // mock out the parser and source buffer |
806 | videojs.hls.SegmentParser = mockSegmentParser(tags); | 854 | videojs.Hls.SegmentParser = mockSegmentParser(tags); |
807 | window.videojs.SourceBuffer = function() { | 855 | window.videojs.SourceBuffer = function() { |
808 | this.appendBuffer = function(chunk) { | 856 | this.appendBuffer = function(chunk) { |
809 | bytes.push(chunk); | 857 | bytes.push(chunk); |
... | @@ -818,8 +866,11 @@ test('clears pending buffer updates when seeking', function() { | ... | @@ -818,8 +866,11 @@ test('clears pending buffer updates when seeking', function() { |
818 | }; | 866 | }; |
819 | 867 | ||
820 | // queue up a tag to be pushed into the buffer (but don't push it yet!) | 868 | // queue up a tag to be pushed into the buffer (but don't push it yet!) |
821 | player.hls('manifest/media.m3u8'); | 869 | player.src({ |
822 | videojs.mediaSources[player.currentSrc()].trigger({ | 870 | src: 'manifest/media.m3u8', |
871 | type: 'application/vnd.apple.mpegurl' | ||
872 | }); | ||
873 | player.hls.mediaSource.trigger({ | ||
823 | type: 'sourceopen' | 874 | type: 'sourceopen' |
824 | }); | 875 | }); |
825 | 876 | ||
... | @@ -846,8 +897,11 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { | ... | @@ -846,8 +897,11 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { |
846 | player.on('error', function() { | 897 | player.on('error', function() { |
847 | errorTriggered = true; | 898 | errorTriggered = true; |
848 | }); | 899 | }); |
849 | player.hls('manifest/media.m3u8'); | 900 | player.src({ |
850 | videojs.mediaSources[player.currentSrc()].trigger({ | 901 | src: 'manifest/media.m3u8', |
902 | type: 'application/vnd.apple.mpegurl' | ||
903 | }); | ||
904 | player.hls.mediaSource.trigger({ | ||
851 | type: 'sourceopen' | 905 | type: 'sourceopen' |
852 | }); | 906 | }); |
853 | requests.pop().respond(404); | 907 | requests.pop().respond(404); |
... | @@ -862,9 +916,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { | ... | @@ -862,9 +916,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { |
862 | }); | 916 | }); |
863 | 917 | ||
864 | test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { | 918 | test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { |
865 | player.hls('manifest/media.m3u8'); | 919 | player.src({ |
920 | src: 'manifest/media.m3u8', | ||
921 | type: 'application/vnd.apple.mpegurl' | ||
922 | }); | ||
866 | 923 | ||
867 | videojs.mediaSources[player.currentSrc()].trigger({ | 924 | player.hls.mediaSource.trigger({ |
868 | type: 'sourceopen' | 925 | type: 'sourceopen' |
869 | }); | 926 | }); |
870 | 927 | ||
... | @@ -875,9 +932,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { | ... | @@ -875,9 +932,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { |
875 | }); | 932 | }); |
876 | 933 | ||
877 | test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { | 934 | test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { |
878 | player.hls('manifest/media.m3u8'); | 935 | player.src({ |
936 | src: 'manifest/media.m3u8', | ||
937 | type: 'application/vnd.apple.mpegurl' | ||
938 | }); | ||
879 | 939 | ||
880 | videojs.mediaSources[player.currentSrc()].trigger({ | 940 | player.hls.mediaSource.trigger({ |
881 | type: 'sourceopen' | 941 | type: 'sourceopen' |
882 | }); | 942 | }); |
883 | 943 | ||
... | @@ -887,17 +947,12 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { | ... | @@ -887,17 +947,12 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { |
887 | equal(4, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_ABORTED'); | 947 | equal(4, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_ABORTED'); |
888 | }); | 948 | }); |
889 | 949 | ||
890 | test('has no effect if native HLS is available', function() { | ||
891 | videojs.hls.supportsNativeHls = true; | ||
892 | player.hls('http://example.com/manifest/master.m3u8'); | ||
893 | |||
894 | ok(!(player.currentSrc() in videojs.mediaSources), | ||
895 | 'no media source was opened'); | ||
896 | }); | ||
897 | |||
898 | test('duration is Infinity for live playlists', function() { | 950 | test('duration is Infinity for live playlists', function() { |
899 | player.hls('http://example.com/manifest/missingEndlist.m3u8'); | 951 | player.src({ |
900 | videojs.mediaSources[player.currentSrc()].trigger({ | 952 | src: 'http://example.com/manifest/missingEndlist.m3u8', |
953 | type: 'application/vnd.apple.mpegurl' | ||
954 | }); | ||
955 | player.hls.mediaSource.trigger({ | ||
901 | type: 'sourceopen' | 956 | type: 'sourceopen' |
902 | }); | 957 | }); |
903 | 958 | ||
... | @@ -912,8 +967,11 @@ test('does not reload playlists with an endlist tag', function() { | ... | @@ -912,8 +967,11 @@ test('does not reload playlists with an endlist tag', function() { |
912 | window.setTimeout = function(callback, timeout) { | 967 | window.setTimeout = function(callback, timeout) { |
913 | callbacks.push({ callback: callback, timeout: timeout }); | 968 | callbacks.push({ callback: callback, timeout: timeout }); |
914 | }; | 969 | }; |
915 | player.hls('manifest/media.m3u8'); | 970 | player.src({ |
916 | videojs.mediaSources[player.currentSrc()].trigger({ | 971 | src: 'manifest/media.m3u8', |
972 | type: 'application/vnd.apple.mpegurl' | ||
973 | }); | ||
974 | player.hls.mediaSource.trigger({ | ||
917 | type: 'sourceopen' | 975 | type: 'sourceopen' |
918 | }); | 976 | }); |
919 | 977 | ||
... | @@ -921,8 +979,11 @@ test('does not reload playlists with an endlist tag', function() { | ... | @@ -921,8 +979,11 @@ test('does not reload playlists with an endlist tag', function() { |
921 | }); | 979 | }); |
922 | 980 | ||
923 | test('updates the media index when a playlist reloads', function() { | 981 | test('updates the media index when a playlist reloads', function() { |
924 | player.hls('http://example.com/live-updating.m3u8'); | 982 | player.src({ |
925 | videojs.mediaSources[player.currentSrc()].trigger({ | 983 | src: 'http://example.com/live-updating.m3u8', |
984 | type: 'application/vnd.apple.mpegurl' | ||
985 | }); | ||
986 | player.hls.mediaSource.trigger({ | ||
926 | type: 'sourceopen' | 987 | type: 'sourceopen' |
927 | }); | 988 | }); |
928 | 989 | ||
... | @@ -964,8 +1025,11 @@ test('mediaIndex is zero before the first segment loads', function() { | ... | @@ -964,8 +1025,11 @@ test('mediaIndex is zero before the first segment loads', function() { |
964 | this.open = function() {}; | 1025 | this.open = function() {}; |
965 | this.send = function() {}; | 1026 | this.send = function() {}; |
966 | }; | 1027 | }; |
967 | player.hls('http://example.com/first-seg-load.m3u8'); | 1028 | player.src({ |
968 | videojs.mediaSources[player.currentSrc()].trigger({ | 1029 | src: 'http://example.com/first-seg-load.m3u8', |
1030 | type: 'application/vnd.apple.mpegurl' | ||
1031 | }); | ||
1032 | player.hls.mediaSource.trigger({ | ||
969 | type: 'sourceopen' | 1033 | type: 'sourceopen' |
970 | }); | 1034 | }); |
971 | 1035 | ||
... | @@ -973,8 +1037,11 @@ test('mediaIndex is zero before the first segment loads', function() { | ... | @@ -973,8 +1037,11 @@ test('mediaIndex is zero before the first segment loads', function() { |
973 | }); | 1037 | }); |
974 | 1038 | ||
975 | test('reloads out-of-date live playlists when switching variants', function() { | 1039 | test('reloads out-of-date live playlists when switching variants', function() { |
976 | player.hls('http://example.com/master.m3u8'); | 1040 | player.src({ |
977 | videojs.mediaSources[player.currentSrc()].trigger({ | 1041 | src: 'http://example.com/master.m3u8', |
1042 | type: 'application/vnd.apple.mpegurl' | ||
1043 | }); | ||
1044 | player.hls.mediaSource.trigger({ | ||
978 | type: 'sourceopen' | 1045 | type: 'sourceopen' |
979 | }); | 1046 | }); |
980 | 1047 | ||
... | @@ -1014,29 +1081,40 @@ test('does not reload master playlists', function() { | ... | @@ -1014,29 +1081,40 @@ test('does not reload master playlists', function() { |
1014 | callbacks.push(callback); | 1081 | callbacks.push(callback); |
1015 | }; | 1082 | }; |
1016 | 1083 | ||
1017 | player.hls('http://example.com/master.m3u8'); | 1084 | player.src({ |
1018 | videojs.mediaSources[player.currentSrc()].trigger({ | 1085 | src: 'http://example.com/master.m3u8', |
1086 | type: 'application/vnd.apple.mpegurl' | ||
1087 | }); | ||
1088 | player.hls.mediaSource.trigger({ | ||
1019 | type: 'sourceopen' | 1089 | type: 'sourceopen' |
1020 | }); | 1090 | }); |
1021 | 1091 | ||
1022 | strictEqual(callbacks.length, 0, 'no reload scheduled'); | 1092 | strictEqual(callbacks.length, |
1093 | 0, 'no reload scheduled'); | ||
1023 | }); | 1094 | }); |
1024 | 1095 | ||
1025 | test('if withCredentials option is used, withCredentials is set on the XHR object', function() { | 1096 | test('if withCredentials option is used, withCredentials is set on the XHR object', function() { |
1026 | player.hls({ | 1097 | player.dispose(); |
1027 | url: 'http://example.com/media.m3u8', | 1098 | player = createPlayer({ |
1028 | withCredentials: true | 1099 | withCredentials: true |
1029 | }); | 1100 | }); |
1030 | videojs.mediaSources[player.currentSrc()].trigger({ | 1101 | player.src({ |
1102 | src: 'http://example.com/media.m3u8', | ||
1103 | type: 'application/vnd.apple.mpegurl' | ||
1104 | }); | ||
1105 | player.hls.mediaSource.trigger({ | ||
1031 | type: 'sourceopen' | 1106 | type: 'sourceopen' |
1032 | }); | 1107 | }); |
1033 | ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in"); | 1108 | ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in"); |
1034 | }); | 1109 | }); |
1035 | 1110 | ||
1036 | test('does not break if the playlist has no segments', function() { | 1111 | test('does not break if the playlist has no segments', function() { |
1037 | player.hls('manifest/master.m3u8'); | 1112 | player.src({ |
1113 | src: 'manifest/master.m3u8', | ||
1114 | type: 'application/vnd.apple.mpegurl' | ||
1115 | }); | ||
1038 | try { | 1116 | try { |
1039 | videojs.mediaSources[player.currentSrc()].trigger({ | 1117 | player.hls.mediaSource.trigger({ |
1040 | type: 'sourceopen' | 1118 | type: 'sourceopen' |
1041 | }); | 1119 | }); |
1042 | requests[0].respond(200, null, | 1120 | requests[0].respond(200, null, |
... | @@ -1051,4 +1129,22 @@ test('does not break if the playlist has no segments', function() { | ... | @@ -1051,4 +1129,22 @@ test('does not break if the playlist has no segments', function() { |
1051 | strictEqual(requests.length, 1, 'no requests for non-existent segments were queued'); | 1129 | strictEqual(requests.length, 1, 'no requests for non-existent segments were queued'); |
1052 | }); | 1130 | }); |
1053 | 1131 | ||
1132 | test('disposes the playlist loader', function() { | ||
1133 | var disposes = 0, player; | ||
1134 | player = createPlayer(); | ||
1135 | player.src({ | ||
1136 | src: 'manifest/master.m3u8', | ||
1137 | type: 'application/vnd.apple.mpegurl' | ||
1138 | }); | ||
1139 | player.hls.mediaSource.trigger({ | ||
1140 | type: 'sourceopen' | ||
1141 | }); | ||
1142 | player.hls.playlists.dispose = function() { | ||
1143 | disposes++; | ||
1144 | }; | ||
1145 | |||
1146 | player.dispose(); | ||
1147 | strictEqual(disposes, 1, 'disposed playlist loader'); | ||
1148 | }); | ||
1149 | |||
1054 | })(window, window.videojs); | 1150 | })(window, window.videojs); | ... | ... |
-
Please register or sign in to post a comment