a81238c7 by Gary Katsevman

Update HLS:

dispose tech, playlist loader
don't trigger loadedmetadata if there was an error
no need for duration update workarounds, add duration to tech
update all the tests
remove irrelevant tests
1 parent 73420aa6
...@@ -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 }
......
...@@ -153,7 +153,13 @@ var ...@@ -153,7 +153,13 @@ var
153 var 153 var
154 duration = 0, 154 duration = 0,
155 segment, 155 segment,
156 i = (playlist.segments || []).length; 156 i;
157
158 if (!playlist) {
159 return 0;
160 }
161
162 i = (playlist.segments || []).length;
157 163
158 // if present, use the duration specified in the playlist 164 // if present, use the duration specified in the playlist
159 if (playlist.totalDuration) { 165 if (playlist.totalDuration) {
...@@ -205,16 +211,8 @@ var ...@@ -205,16 +211,8 @@ var
205 * Update the player duration 211 * Update the player duration
206 */ 212 */
207 updateDuration = function(playlist) { 213 updateDuration = function(playlist) {
208 var tech;
209 // update the duration 214 // update the duration
210 player.duration(totalDuration(playlist)); 215 player.duration(totalDuration(playlist));
211 // tell the flash tech of the new duration
212 tech = player.el().querySelector('.vjs-tech');
213 if(tech.vjs_setProperty) {
214 tech.vjs_setProperty('duration', player.duration());
215 }
216 // manually fire the duration change
217 player.trigger('durationchange');
218 }; 216 };
219 217
220 /** 218 /**
...@@ -310,7 +308,8 @@ var ...@@ -310,7 +308,8 @@ var
310 } 308 }
311 309
312 // if no segments are available, do nothing 310 // if no segments are available, do nothing
313 if (!player.hls.playlists.media().segments) { 311 if (player.hls.playlists.state === "HAVE_NOTHING" ||
312 !player.hls.playlists.media().segments) {
314 return; 313 return;
315 } 314 }
316 315
...@@ -456,12 +455,12 @@ videojs.Hls = videojs.Flash.extend({ ...@@ -456,12 +455,12 @@ videojs.Hls = videojs.Flash.extend({
456 source = options.source, 455 source = options.source,
457 settings = player.options(); 456 settings = player.options();
458 457
458 player.hls = this;
459 delete options.source; 459 delete options.source;
460 options.swf = settings.flash.swf; 460 options.swf = settings.flash.swf;
461 videojs.Flash.call(this, player, options, ready); 461 videojs.Flash.call(this, player, options, ready);
462 player.hls = {};
463 options.source = source; 462 options.source = source;
464 videojs.Hls.prototype.src.call(player, options.source && options.source.src); 463 videojs.Hls.prototype.src.call(this, options.source && options.source.src);
465 } 464 }
466 }); 465 });
467 466
...@@ -472,7 +471,7 @@ videojs.Hls.prototype.src = function(src) { ...@@ -472,7 +471,7 @@ videojs.Hls.prototype.src = function(src) {
472 source; 471 source;
473 472
474 if (src) { 473 if (src) {
475 mediaSource = new videojs.MediaSource(), 474 mediaSource = new videojs.MediaSource();
476 source = { 475 source = {
477 src: videojs.URL.createObjectURL(mediaSource), 476 src: videojs.URL.createObjectURL(mediaSource),
478 type: "video/flv" 477 type: "video/flv"
...@@ -480,10 +479,25 @@ videojs.Hls.prototype.src = function(src) { ...@@ -480,10 +479,25 @@ videojs.Hls.prototype.src = function(src) {
480 player.hls.mediaSource = mediaSource; 479 player.hls.mediaSource = mediaSource;
481 initSource(player, mediaSource, src); 480 initSource(player, mediaSource, src);
482 this.ready(function() { 481 this.ready(function() {
483 this.el().querySelector('.vjs-tech').vjs_src(source.src); 482 this.el().vjs_src(source.src);
484 }); 483 });
485 } 484 }
486 } 485 };
486
487 videojs.Hls.prototype.duration = function() {
488 var playlists = this.player().hls.playlists;
489 if (playlists) {
490 return totalDuration(playlists.media());
491 }
492 return 0;
493 };
494
495 videojs.Hls.prototype.dispose = function() {
496 if (this.player().hls.playlists) {
497 this.player().hls.playlists.dispose();
498 }
499 videojs.Flash.prototype.dispose.call(this);
500 };
487 501
488 //for (var prop in videojs.Flash) { 502 //for (var prop in videojs.Flash) {
489 //videojs.Hls[prop] = videojs.Flash[prop]; 503 //videojs.Hls[prop] = videojs.Flash[prop];
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
15 <script src="../libs/qunit/qunit.js"></script> 15 <script src="../libs/qunit/qunit.js"></script>
16 16
17 <!-- video.js --> 17 <!-- video.js -->
18 <script src="../node_modules/video.js/dist/video-js/video.dev.js"></script> 18 <script src="../node_modules/video.js/dist/video-js/video.js"></script>
19 <script src="../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> 19 <script src="../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
20 20
21 <!-- HLS plugin --> 21 <!-- HLS plugin -->
......
...@@ -22,12 +22,11 @@ ...@@ -22,12 +22,11 @@
22 22
23 var 23 var
24 player, 24 player,
25 oldFlashSupported, 25 oldMediaSourceOpen,
26 oldSegmentParser, 26 oldSegmentParser,
27 oldSetTimeout, 27 oldSetTimeout,
28 oldSourceBuffer, 28 oldSourceBuffer,
29 oldSupportsNativeHls, 29 oldFlashSupported,
30 xhrUrls,
31 requests, 30 requests,
32 xhr, 31 xhr,
33 32
...@@ -82,37 +81,37 @@ var ...@@ -82,37 +81,37 @@ var
82 81
83 module('HLS', { 82 module('HLS', {
84 setup: function() { 83 setup: function() {
84 oldMediaSourceOpen = videojs.MediaSource.open;
85 videojs.MediaSource.open = function() {};
85 86
86 // mock out Flash features for phantomjs 87 // mock out Flash features for phantomjs
87 oldFlashSupported = videojs.Flash.isSupported; 88 oldFlashSupported = videojs.Flash.isSupported;
88 videojs.Flash.isSupported = function() { 89 videojs.Flash.isSupported = function() {
89 return true; 90 return true;
90 }; 91 };
92
91 oldSourceBuffer = window.videojs.SourceBuffer; 93 oldSourceBuffer = window.videojs.SourceBuffer;
92 window.videojs.SourceBuffer = function() { 94 window.videojs.SourceBuffer = function() {
93 this.appendBuffer = function() {}; 95 this.appendBuffer = function() {};
94 this.abort = function() {}; 96 this.abort = function() {};
95 }; 97 };
96 98
97 // force native HLS to be ignored
98 oldSupportsNativeHls = videojs.hls.supportsNativeHls;
99 videojs.hls.supportsNativeHls = false;
100
101 // create the test player 99 // create the test player
102 var video = document.createElement('video'); 100 var video = document.createElement('video');
103 document.querySelector('#qunit-fixture').appendChild(video); 101 document.querySelector('#qunit-fixture').appendChild(video);
104 player = videojs(video, { 102 player = videojs(video, {
105 flash: { 103 flash: {
106 swf: '../node_modules/video.js/dist/video-js/video-js.swf' 104 swf: ''//'../node_modules/video.js/dist/video-js/video-js.swf'
107 }, 105 },
108 techOrder: ['flash'] 106 techOrder: ['hls']
109 }); 107 });
108
110 player.buffered = function() { 109 player.buffered = function() {
111 return videojs.createTimeRange(0, 0); 110 return videojs.createTimeRange(0, 0);
112 }; 111 };
113 112
114 // store functionality that some tests need to mock 113 // store functionality that some tests need to mock
115 oldSegmentParser = videojs.hls.SegmentParser; 114 oldSegmentParser = videojs.Hls.SegmentParser;
116 oldSetTimeout = window.setTimeout; 115 oldSetTimeout = window.setTimeout;
117 116
118 // fake XHRs 117 // fake XHRs
...@@ -121,13 +120,18 @@ module('HLS', { ...@@ -121,13 +120,18 @@ module('HLS', {
121 xhr.onCreate = function(xhr) { 120 xhr.onCreate = function(xhr) {
122 requests.push(xhr); 121 requests.push(xhr);
123 }; 122 };
124 xhrUrls = []; 123
124 var tech = player.el().querySelector('.vjs-tech');
125 tech.vjs_getProperty = function() {};
126 tech.vjs_src = function() {};
127 videojs.Flash.onReady(tech.id);
125 }, 128 },
126 129
127 teardown: function() { 130 teardown: function() {
131 player.dispose();
128 videojs.Flash.isSupported = oldFlashSupported; 132 videojs.Flash.isSupported = oldFlashSupported;
129 videojs.hls.supportsNativeHls = oldSupportsNativeHls; 133 videojs.MediaSource.open = oldMediaSourceOpen;
130 videojs.hls.SegmentParser = oldSegmentParser; 134 videojs.Hls.SegmentParser = oldSegmentParser;
131 videojs.SourceBuffer = oldSourceBuffer; 135 videojs.SourceBuffer = oldSourceBuffer;
132 window.setTimeout = oldSetTimeout; 136 window.setTimeout = oldSetTimeout;
133 xhr.restore(); 137 xhr.restore();
...@@ -140,8 +144,8 @@ test('starts playing if autoplay is specified', function() { ...@@ -140,8 +144,8 @@ test('starts playing if autoplay is specified', function() {
140 plays++; 144 plays++;
141 }; 145 };
142 player.options().autoplay = true; 146 player.options().autoplay = true;
143 player.hls('manifest/playlist.m3u8'); 147 player.src({src: 'manifest/playlist.m3u8', type: 'application/vnd.apple.mpegurl'});
144 videojs.mediaSources[player.currentSrc()].trigger({ 148 player.hls.mediaSource.trigger({
145 type: 'sourceopen' 149 type: 'sourceopen'
146 }); 150 });
147 151
...@@ -155,9 +159,9 @@ test('creates a PlaylistLoader on init', function() { ...@@ -155,9 +159,9 @@ test('creates a PlaylistLoader on init', function() {
155 loadedmetadata = true; 159 loadedmetadata = true;
156 }); 160 });
157 161
158 player.hls('manifest/playlist.m3u8'); 162 ok(!player.hls.playlists, 'waits for set src to create the loader');
159 ok(!player.hls.playlists, 'waits for sourceopen to create the loader'); 163 player.src({src:'manifest/playlist.m3u8', type: 'application/vnd.apple.mpegurl'});
160 videojs.mediaSources[player.currentSrc()].trigger({ 164 player.hls.mediaSource.trigger({
161 type: 'sourceopen' 165 type: 'sourceopen'
162 }); 166 });
163 standardXHRResponse(requests[0]); 167 standardXHRResponse(requests[0]);
...@@ -178,8 +182,8 @@ test('sets the duration if one is available on the playlist', function() { ...@@ -178,8 +182,8 @@ test('sets the duration if one is available on the playlist', function() {
178 } 182 }
179 calls++; 183 calls++;
180 }; 184 };
181 player.hls('manifest/media.m3u8'); 185 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
182 videojs.mediaSources[player.currentSrc()].trigger({ 186 player.hls.mediaSource.trigger({
183 type: 'sourceopen' 187 type: 'sourceopen'
184 }); 188 });
185 189
...@@ -197,8 +201,8 @@ test('calculates the duration if needed', function() { ...@@ -197,8 +201,8 @@ test('calculates the duration if needed', function() {
197 } 201 }
198 durations.push(duration); 202 durations.push(duration);
199 }; 203 };
200 player.hls('http://example.com/manifest/missingExtinf.m3u8'); 204 player.src({src: 'http://example.com/manifest/missingExtinf.m3u8', type: 'application/vnd.apple.mpegurl'});
201 videojs.mediaSources[player.currentSrc()].trigger({ 205 player.hls.mediaSource.trigger({
202 type: 'sourceopen' 206 type: 'sourceopen'
203 }); 207 });
204 208
...@@ -210,11 +214,11 @@ test('calculates the duration if needed', function() { ...@@ -210,11 +214,11 @@ test('calculates the duration if needed', function() {
210 }); 214 });
211 215
212 test('starts downloading a segment on loadedmetadata', function() { 216 test('starts downloading a segment on loadedmetadata', function() {
213 player.hls('manifest/media.m3u8'); 217 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
214 player.buffered = function() { 218 player.buffered = function() {
215 return videojs.createTimeRange(0, 0); 219 return videojs.createTimeRange(0, 0);
216 }; 220 };
217 videojs.mediaSources[player.currentSrc()].trigger({ 221 player.hls.mediaSource.trigger({
218 type: 'sourceopen' 222 type: 'sourceopen'
219 }); 223 });
220 224
...@@ -228,8 +232,8 @@ test('starts downloading a segment on loadedmetadata', function() { ...@@ -228,8 +232,8 @@ test('starts downloading a segment on loadedmetadata', function() {
228 }); 232 });
229 233
230 test('recognizes absolute URIs and requests them unmodified', function() { 234 test('recognizes absolute URIs and requests them unmodified', function() {
231 player.hls('manifest/absoluteUris.m3u8'); 235 player.src({src: 'manifest/absoluteUris.m3u8', type: 'application/vnd.apple.mpegurl'});
232 videojs.mediaSources[player.currentSrc()].trigger({ 236 player.hls.mediaSource.trigger({
233 type: 'sourceopen' 237 type: 'sourceopen'
234 }); 238 });
235 239
...@@ -241,8 +245,8 @@ test('recognizes absolute URIs and requests them unmodified', function() { ...@@ -241,8 +245,8 @@ test('recognizes absolute URIs and requests them unmodified', function() {
241 }); 245 });
242 246
243 test('recognizes domain-relative URLs', function() { 247 test('recognizes domain-relative URLs', function() {
244 player.hls('manifest/domainUris.m3u8'); 248 player.src({src: 'manifest/domainUris.m3u8', type: 'application/vnd.apple.mpegurl'});
245 videojs.mediaSources[player.currentSrc()].trigger({ 249 player.hls.mediaSource.trigger({
246 type: 'sourceopen' 250 type: 'sourceopen'
247 }); 251 });
248 252
...@@ -253,14 +257,25 @@ test('recognizes domain-relative URLs', function() { ...@@ -253,14 +257,25 @@ test('recognizes domain-relative URLs', function() {
253 'the first segment is requested'); 257 'the first segment is requested');
254 }); 258 });
255 259
256 test('re-initializes the plugin for each source', function() { 260 test('re-initializes the tech for each source', function() {
257 var firstInit, secondInit; 261 var firstPlaylists, secondPlaylists, firstMSE, secondMSE;
258 player.hls('manifest/master.m3u8'); 262
259 firstInit = player.hls; 263 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
260 player.hls('manifest/master.m3u8'); 264 player.hls.mediaSource.trigger({
261 secondInit = player.hls; 265 type: 'sourceopen'
266 });
267 firstPlaylists = player.hls.playlists;
268 firstMSE = player.hls.mediaSource;
269
270 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
271 player.hls.mediaSource.trigger({
272 type: 'sourceopen'
273 });
274 secondPlaylists = player.hls.playlists;
275 secondMSE = player.hls.mediaSource;
262 276
263 notStrictEqual(firstInit, secondInit, 'the plugin object is replaced'); 277 notStrictEqual(firstPlaylists, secondPlaylists, 'the playlist object is not reused');
278 notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused');
264 }); 279 });
265 280
266 test('triggers an error when a master playlist request errors', function() { 281 test('triggers an error when a master playlist request errors', function() {
...@@ -268,8 +283,8 @@ test('triggers an error when a master playlist request errors', function() { ...@@ -268,8 +283,8 @@ test('triggers an error when a master playlist request errors', function() {
268 player.on('error', function() { 283 player.on('error', function() {
269 error = player.hls.error; 284 error = player.hls.error;
270 }); 285 });
271 player.hls('manifest/master.m3u8'); 286 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
272 videojs.mediaSources[player.currentSrc()].trigger({ 287 player.hls.mediaSource.trigger({
273 type: 'sourceopen' 288 type: 'sourceopen'
274 }); 289 });
275 requests.pop().respond(500); 290 requests.pop().respond(500);
...@@ -279,8 +294,8 @@ test('triggers an error when a master playlist request errors', function() { ...@@ -279,8 +294,8 @@ test('triggers an error when a master playlist request errors', function() {
279 }); 294 });
280 295
281 test('downloads media playlists after loading the master', function() { 296 test('downloads media playlists after loading the master', function() {
282 player.hls('manifest/master.m3u8'); 297 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
283 videojs.mediaSources[player.currentSrc()].trigger({ 298 player.hls.mediaSource.trigger({
284 type: 'sourceopen' 299 type: 'sourceopen'
285 }); 300 });
286 301
...@@ -309,8 +324,8 @@ test('timeupdates do not check to fill the buffer until a media playlist is read ...@@ -309,8 +324,8 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
309 }; 324 };
310 this.send = function() {}; 325 this.send = function() {};
311 }; 326 };
312 player.hls('manifest/media.m3u8'); 327 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
313 videojs.mediaSources[player.currentSrc()].trigger({ 328 player.hls.mediaSource.trigger({
314 type: 'sourceopen' 329 type: 'sourceopen'
315 }); 330 });
316 player.trigger('timeupdate'); 331 player.trigger('timeupdate');
...@@ -320,8 +335,8 @@ test('timeupdates do not check to fill the buffer until a media playlist is read ...@@ -320,8 +335,8 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
320 }); 335 });
321 336
322 test('calculates the bandwidth after downloading a segment', function() { 337 test('calculates the bandwidth after downloading a segment', function() {
323 player.hls('manifest/media.m3u8'); 338 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
324 videojs.mediaSources[player.currentSrc()].trigger({ 339 player.hls.mediaSource.trigger({
325 type: 'sourceopen' 340 type: 'sourceopen'
326 }); 341 });
327 342
...@@ -337,12 +352,12 @@ test('calculates the bandwidth after downloading a segment', function() { ...@@ -337,12 +352,12 @@ test('calculates the bandwidth after downloading a segment', function() {
337 352
338 test('selects a playlist after segment downloads', function() { 353 test('selects a playlist after segment downloads', function() {
339 var calls = 0; 354 var calls = 0;
340 player.hls('manifest/master.m3u8'); 355 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
341 player.hls.selectPlaylist = function() { 356 player.hls.selectPlaylist = function() {
342 calls++; 357 calls++;
343 return player.hls.playlists.master.playlists[0]; 358 return player.hls.playlists.master.playlists[0];
344 }; 359 };
345 videojs.mediaSources[player.currentSrc()].trigger({ 360 player.hls.mediaSource.trigger({
346 type: 'sourceopen' 361 type: 'sourceopen'
347 }); 362 });
348 363
...@@ -367,8 +382,8 @@ test('selects a playlist after segment downloads', function() { ...@@ -367,8 +382,8 @@ test('selects a playlist after segment downloads', function() {
367 test('moves to the next segment if there is a network error', function() { 382 test('moves to the next segment if there is a network error', function() {
368 var mediaIndex; 383 var mediaIndex;
369 384
370 player.hls('manifest/master.m3u8'); 385 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
371 videojs.mediaSources[player.currentSrc()].trigger({ 386 player.hls.mediaSource.trigger({
372 type: 'sourceopen' 387 type: 'sourceopen'
373 }); 388 });
374 389
...@@ -386,7 +401,7 @@ test('updates the duration after switching playlists', function() { ...@@ -386,7 +401,7 @@ test('updates the duration after switching playlists', function() {
386 var 401 var
387 calls = 0, 402 calls = 0,
388 selectedPlaylist = false; 403 selectedPlaylist = false;
389 player.hls('manifest/master.m3u8'); 404 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
390 player.hls.selectPlaylist = function() { 405 player.hls.selectPlaylist = function() {
391 selectedPlaylist = true; 406 selectedPlaylist = true;
392 return player.hls.playlists.master.playlists[1]; 407 return player.hls.playlists.master.playlists[1];
...@@ -400,7 +415,7 @@ test('updates the duration after switching playlists', function() { ...@@ -400,7 +415,7 @@ test('updates the duration after switching playlists', function() {
400 calls++; 415 calls++;
401 } 416 }
402 }; 417 };
403 videojs.mediaSources[player.currentSrc()].trigger({ 418 player.hls.mediaSource.trigger({
404 type: 'sourceopen' 419 type: 'sourceopen'
405 }); 420 });
406 421
...@@ -418,8 +433,8 @@ test('downloads additional playlists if required', function() { ...@@ -418,8 +433,8 @@ test('downloads additional playlists if required', function() {
418 playlist = { 433 playlist = {
419 uri: 'media3.m3u8' 434 uri: 'media3.m3u8'
420 }; 435 };
421 player.hls('manifest/master.m3u8'); 436 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
422 videojs.mediaSources[player.currentSrc()].trigger({ 437 player.hls.mediaSource.trigger({
423 type: 'sourceopen' 438 type: 'sourceopen'
424 }); 439 });
425 440
...@@ -456,8 +471,8 @@ test('downloads additional playlists if required', function() { ...@@ -456,8 +471,8 @@ test('downloads additional playlists if required', function() {
456 471
457 test('selects a playlist below the current bandwidth', function() { 472 test('selects a playlist below the current bandwidth', function() {
458 var playlist; 473 var playlist;
459 player.hls('manifest/master.m3u8'); 474 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
460 videojs.mediaSources[player.currentSrc()].trigger({ 475 player.hls.mediaSource.trigger({
461 type: 'sourceopen' 476 type: 'sourceopen'
462 }); 477 });
463 478
...@@ -478,8 +493,8 @@ test('selects a playlist below the current bandwidth', function() { ...@@ -478,8 +493,8 @@ test('selects a playlist below the current bandwidth', function() {
478 493
479 test('raises the minimum bitrate for a stream proportionially', function() { 494 test('raises the minimum bitrate for a stream proportionially', function() {
480 var playlist; 495 var playlist;
481 player.hls('manifest/master.m3u8'); 496 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
482 videojs.mediaSources[player.currentSrc()].trigger({ 497 player.hls.mediaSource.trigger({
483 type: 'sourceopen' 498 type: 'sourceopen'
484 }); 499 });
485 500
...@@ -500,8 +515,8 @@ test('raises the minimum bitrate for a stream proportionially', function() { ...@@ -500,8 +515,8 @@ test('raises the minimum bitrate for a stream proportionially', function() {
500 515
501 test('uses the lowest bitrate if no other is suitable', function() { 516 test('uses the lowest bitrate if no other is suitable', function() {
502 var playlist; 517 var playlist;
503 player.hls('manifest/master.m3u8'); 518 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
504 videojs.mediaSources[player.currentSrc()].trigger({ 519 player.hls.mediaSource.trigger({
505 type: 'sourceopen' 520 type: 'sourceopen'
506 }); 521 });
507 522
...@@ -520,9 +535,9 @@ test('uses the lowest bitrate if no other is suitable', function() { ...@@ -520,9 +535,9 @@ test('uses the lowest bitrate if no other is suitable', function() {
520 test('selects the correct rendition by player dimensions', function() { 535 test('selects the correct rendition by player dimensions', function() {
521 var playlist; 536 var playlist;
522 537
523 player.hls('manifest/master.m3u8'); 538 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
524 539
525 videojs.mediaSources[player.currentSrc()].trigger({ 540 player.hls.mediaSource.trigger({
526 type: 'sourceopen' 541 type: 'sourceopen'
527 }); 542 });
528 543
...@@ -550,14 +565,14 @@ test('selects the correct rendition by player dimensions', function() { ...@@ -550,14 +565,14 @@ test('selects the correct rendition by player dimensions', function() {
550 565
551 566
552 test('does not download the next segment if the buffer is full', function() { 567 test('does not download the next segment if the buffer is full', function() {
553 player.hls('manifest/media.m3u8'); 568 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
554 player.currentTime = function() { 569 player.currentTime = function() {
555 return 15; 570 return 15;
556 }; 571 };
557 player.buffered = function() { 572 player.buffered = function() {
558 return videojs.createTimeRange(0, 20); 573 return videojs.createTimeRange(0, 20);
559 }; 574 };
560 videojs.mediaSources[player.currentSrc()].trigger({ 575 player.hls.mediaSource.trigger({
561 type: 'sourceopen' 576 type: 'sourceopen'
562 }); 577 });
563 578
...@@ -569,8 +584,8 @@ test('does not download the next segment if the buffer is full', function() { ...@@ -569,8 +584,8 @@ test('does not download the next segment if the buffer is full', function() {
569 }); 584 });
570 585
571 test('downloads the next segment if the buffer is getting low', function() { 586 test('downloads the next segment if the buffer is getting low', function() {
572 player.hls('manifest/media.m3u8'); 587 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
573 videojs.mediaSources[player.currentSrc()].trigger({ 588 player.hls.mediaSource.trigger({
574 type: 'sourceopen' 589 type: 'sourceopen'
575 }); 590 });
576 591
...@@ -597,8 +612,8 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -597,8 +612,8 @@ test('downloads the next segment if the buffer is getting low', function() {
597 }); 612 });
598 613
599 test('stops downloading segments at the end of the playlist', function() { 614 test('stops downloading segments at the end of the playlist', function() {
600 player.hls('manifest/media.m3u8'); 615 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
601 videojs.mediaSources[player.currentSrc()].trigger({ 616 player.hls.mediaSource.trigger({
602 type: 'sourceopen' 617 type: 'sourceopen'
603 }); 618 });
604 standardXHRResponse(requests[0]); 619 standardXHRResponse(requests[0]);
...@@ -606,13 +621,13 @@ test('stops downloading segments at the end of the playlist', function() { ...@@ -606,13 +621,13 @@ test('stops downloading segments at the end of the playlist', function() {
606 player.hls.mediaIndex = 4; 621 player.hls.mediaIndex = 4;
607 player.trigger('timeupdate'); 622 player.trigger('timeupdate');
608 623
609 strictEqual(xhrUrls.length, 0, 'no request is made'); 624 strictEqual(requests.length, 0, 'no request is made');
610 }); 625 });
611 626
612 test('only makes one segment request at a time', function() { 627 test('only makes one segment request at a time', function() {
613 var openedXhrs = 0; 628 var openedXhrs = 0;
614 player.hls('manifest/media.m3u8'); 629 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
615 videojs.mediaSources[player.currentSrc()].trigger({ 630 player.hls.mediaSource.trigger({
616 type: 'sourceopen' 631 type: 'sourceopen'
617 }); 632 });
618 xhr.restore(); 633 xhr.restore();
...@@ -634,68 +649,9 @@ test('only makes one segment request at a time', function() { ...@@ -634,68 +649,9 @@ test('only makes one segment request at a time', function() {
634 xhr = sinon.useFakeXMLHttpRequest(); 649 xhr = sinon.useFakeXMLHttpRequest();
635 }); 650 });
636 651
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() { 652 test('cancels outstanding XHRs when seeking', function() {
697 player.hls('manifest/media.m3u8'); 653 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
698 videojs.mediaSources[player.currentSrc()].trigger({ 654 player.hls.mediaSource.trigger({
699 type: 'sourceopen' 655 type: 'sourceopen'
700 }); 656 });
701 standardXHRResponse(requests[0]); 657 standardXHRResponse(requests[0]);
...@@ -721,7 +677,7 @@ test('cancels outstanding XHRs when seeking', function() { ...@@ -721,7 +677,7 @@ test('cancels outstanding XHRs when seeking', function() {
721 test('flushes the parser after each segment', function() { 677 test('flushes the parser after each segment', function() {
722 var flushes = 0; 678 var flushes = 0;
723 // mock out the segment parser 679 // mock out the segment parser
724 videojs.hls.SegmentParser = function() { 680 videojs.Hls.SegmentParser = function() {
725 this.getFlvHeader = function() { 681 this.getFlvHeader = function() {
726 return []; 682 return [];
727 }; 683 };
...@@ -732,8 +688,8 @@ test('flushes the parser after each segment', function() { ...@@ -732,8 +688,8 @@ test('flushes the parser after each segment', function() {
732 this.tagsAvailable = function() {}; 688 this.tagsAvailable = function() {};
733 }; 689 };
734 690
735 player.hls('manifest/media.m3u8'); 691 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
736 videojs.mediaSources[player.currentSrc()].trigger({ 692 player.hls.mediaSource.trigger({
737 type: 'sourceopen' 693 type: 'sourceopen'
738 }); 694 });
739 695
...@@ -749,7 +705,7 @@ test('drops tags before the target timestamp when seeking', function() { ...@@ -749,7 +705,7 @@ test('drops tags before the target timestamp when seeking', function() {
749 bytes = []; 705 bytes = [];
750 706
751 // mock out the parser and source buffer 707 // mock out the parser and source buffer
752 videojs.hls.SegmentParser = mockSegmentParser(tags); 708 videojs.Hls.SegmentParser = mockSegmentParser(tags);
753 window.videojs.SourceBuffer = function() { 709 window.videojs.SourceBuffer = function() {
754 this.appendBuffer = function(chunk) { 710 this.appendBuffer = function(chunk) {
755 bytes.push(chunk); 711 bytes.push(chunk);
...@@ -764,8 +720,8 @@ test('drops tags before the target timestamp when seeking', function() { ...@@ -764,8 +720,8 @@ test('drops tags before the target timestamp when seeking', function() {
764 // push a tag into the buffer 720 // push a tag into the buffer
765 tags.push({ pts: 0, bytes: 0 }); 721 tags.push({ pts: 0, bytes: 0 });
766 722
767 player.hls('manifest/media.m3u8'); 723 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
768 videojs.mediaSources[player.currentSrc()].trigger({ 724 player.hls.mediaSource.trigger({
769 type: 'sourceopen' 725 type: 'sourceopen'
770 }); 726 });
771 standardXHRResponse(requests[0]); 727 standardXHRResponse(requests[0]);
...@@ -803,7 +759,7 @@ test('clears pending buffer updates when seeking', function() { ...@@ -803,7 +759,7 @@ test('clears pending buffer updates when seeking', function() {
803 tags = [{ pts: 0, bytes: 0 }]; 759 tags = [{ pts: 0, bytes: 0 }];
804 760
805 // mock out the parser and source buffer 761 // mock out the parser and source buffer
806 videojs.hls.SegmentParser = mockSegmentParser(tags); 762 videojs.Hls.SegmentParser = mockSegmentParser(tags);
807 window.videojs.SourceBuffer = function() { 763 window.videojs.SourceBuffer = function() {
808 this.appendBuffer = function(chunk) { 764 this.appendBuffer = function(chunk) {
809 bytes.push(chunk); 765 bytes.push(chunk);
...@@ -818,8 +774,8 @@ test('clears pending buffer updates when seeking', function() { ...@@ -818,8 +774,8 @@ test('clears pending buffer updates when seeking', function() {
818 }; 774 };
819 775
820 // queue up a tag to be pushed into the buffer (but don't push it yet!) 776 // queue up a tag to be pushed into the buffer (but don't push it yet!)
821 player.hls('manifest/media.m3u8'); 777 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
822 videojs.mediaSources[player.currentSrc()].trigger({ 778 player.hls.mediaSource.trigger({
823 type: 'sourceopen' 779 type: 'sourceopen'
824 }); 780 });
825 781
...@@ -846,8 +802,8 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { ...@@ -846,8 +802,8 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
846 player.on('error', function() { 802 player.on('error', function() {
847 errorTriggered = true; 803 errorTriggered = true;
848 }); 804 });
849 player.hls('manifest/media.m3u8'); 805 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
850 videojs.mediaSources[player.currentSrc()].trigger({ 806 player.hls.mediaSource.trigger({
851 type: 'sourceopen' 807 type: 'sourceopen'
852 }); 808 });
853 requests.pop().respond(404); 809 requests.pop().respond(404);
...@@ -862,9 +818,9 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { ...@@ -862,9 +818,9 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
862 }); 818 });
863 819
864 test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { 820 test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
865 player.hls('manifest/media.m3u8'); 821 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
866 822
867 videojs.mediaSources[player.currentSrc()].trigger({ 823 player.hls.mediaSource.trigger({
868 type: 'sourceopen' 824 type: 'sourceopen'
869 }); 825 });
870 826
...@@ -875,9 +831,9 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { ...@@ -875,9 +831,9 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
875 }); 831 });
876 832
877 test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { 833 test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
878 player.hls('manifest/media.m3u8'); 834 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
879 835
880 videojs.mediaSources[player.currentSrc()].trigger({ 836 player.hls.mediaSource.trigger({
881 type: 'sourceopen' 837 type: 'sourceopen'
882 }); 838 });
883 839
...@@ -888,16 +844,16 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { ...@@ -888,16 +844,16 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
888 }); 844 });
889 845
890 test('has no effect if native HLS is available', function() { 846 test('has no effect if native HLS is available', function() {
891 videojs.hls.supportsNativeHls = true; 847 videojs.Hls.supportsNativeHls = true;
892 player.hls('http://example.com/manifest/master.m3u8'); 848 player.src({src: 'http://example.com/manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
893 849
894 ok(!(player.currentSrc() in videojs.mediaSources), 850 ok(!(player.currentSrc() in videojs.mediaSources),
895 'no media source was opened'); 851 'no media source was opened');
896 }); 852 });
897 853
898 test('duration is Infinity for live playlists', function() { 854 test('duration is Infinity for live playlists', function() {
899 player.hls('http://example.com/manifest/missingEndlist.m3u8'); 855 player.src({src: 'http://example.com/manifest/missingEndlist.m3u8', type: 'application/vnd.apple.mpegurl'});
900 videojs.mediaSources[player.currentSrc()].trigger({ 856 player.hls.mediaSource.trigger({
901 type: 'sourceopen' 857 type: 'sourceopen'
902 }); 858 });
903 859
...@@ -912,8 +868,8 @@ test('does not reload playlists with an endlist tag', function() { ...@@ -912,8 +868,8 @@ test('does not reload playlists with an endlist tag', function() {
912 window.setTimeout = function(callback, timeout) { 868 window.setTimeout = function(callback, timeout) {
913 callbacks.push({ callback: callback, timeout: timeout }); 869 callbacks.push({ callback: callback, timeout: timeout });
914 }; 870 };
915 player.hls('manifest/media.m3u8'); 871 player.src({src: 'manifest/media.m3u8', type: 'application/vnd.apple.mpegurl'});
916 videojs.mediaSources[player.currentSrc()].trigger({ 872 player.hls.mediaSource.trigger({
917 type: 'sourceopen' 873 type: 'sourceopen'
918 }); 874 });
919 875
...@@ -921,8 +877,8 @@ test('does not reload playlists with an endlist tag', function() { ...@@ -921,8 +877,8 @@ test('does not reload playlists with an endlist tag', function() {
921 }); 877 });
922 878
923 test('updates the media index when a playlist reloads', function() { 879 test('updates the media index when a playlist reloads', function() {
924 player.hls('http://example.com/live-updating.m3u8'); 880 player.src({src: 'http://example.com/live-updating.m3u8', type: 'application/vnd.apple.mpegurl'});
925 videojs.mediaSources[player.currentSrc()].trigger({ 881 player.hls.mediaSource.trigger({
926 type: 'sourceopen' 882 type: 'sourceopen'
927 }); 883 });
928 884
...@@ -964,8 +920,8 @@ test('mediaIndex is zero before the first segment loads', function() { ...@@ -964,8 +920,8 @@ test('mediaIndex is zero before the first segment loads', function() {
964 this.open = function() {}; 920 this.open = function() {};
965 this.send = function() {}; 921 this.send = function() {};
966 }; 922 };
967 player.hls('http://example.com/first-seg-load.m3u8'); 923 player.src({src: 'http://example.com/first-seg-load.m3u8', type: 'application/vnd.apple.mpegurl'});
968 videojs.mediaSources[player.currentSrc()].trigger({ 924 player.hls.mediaSource.trigger({
969 type: 'sourceopen' 925 type: 'sourceopen'
970 }); 926 });
971 927
...@@ -973,8 +929,8 @@ test('mediaIndex is zero before the first segment loads', function() { ...@@ -973,8 +929,8 @@ test('mediaIndex is zero before the first segment loads', function() {
973 }); 929 });
974 930
975 test('reloads out-of-date live playlists when switching variants', function() { 931 test('reloads out-of-date live playlists when switching variants', function() {
976 player.hls('http://example.com/master.m3u8'); 932 player.src({src: 'http://example.com/master.m3u8', type: 'application/vnd.apple.mpegurl'});
977 videojs.mediaSources[player.currentSrc()].trigger({ 933 player.hls.mediaSource.trigger({
978 type: 'sourceopen' 934 type: 'sourceopen'
979 }); 935 });
980 936
...@@ -1014,8 +970,8 @@ test('does not reload master playlists', function() { ...@@ -1014,8 +970,8 @@ test('does not reload master playlists', function() {
1014 callbacks.push(callback); 970 callbacks.push(callback);
1015 }; 971 };
1016 972
1017 player.hls('http://example.com/master.m3u8'); 973 player.src({src: 'http://example.com/master.m3u8', type: 'application/vnd.apple.mpegurl'});
1018 videojs.mediaSources[player.currentSrc()].trigger({ 974 player.hls.mediaSource.trigger({
1019 type: 'sourceopen' 975 type: 'sourceopen'
1020 }); 976 });
1021 977
...@@ -1023,20 +979,33 @@ test('does not reload master playlists', function() { ...@@ -1023,20 +979,33 @@ test('does not reload master playlists', function() {
1023 }); 979 });
1024 980
1025 test('if withCredentials option is used, withCredentials is set on the XHR object', function() { 981 test('if withCredentials option is used, withCredentials is set on the XHR object', function() {
1026 player.hls({ 982 stop();
1027 url: 'http://example.com/media.m3u8', 983 player.dispose();
1028 withCredentials: true 984 var video = document.createElement('video');
1029 }); 985
1030 videojs.mediaSources[player.currentSrc()].trigger({ 986 document.querySelector('#qunit-fixture').appendChild(video);
1031 type: 'sourceopen' 987 player = videojs(video, {
988 flash: {
989 swf: '../node_modules/video.js/dist/video-js/video-js.swf'
990 },
991 hls: {
992 withCredentials: true
993 },
994 techOrder: ['hls']
995 }, function() {
996 player.src({src: 'http://example.com/media.m3u8', type: 'application/vnd.apple.mpegurl'});
997 player.hls.mediaSource.trigger({
998 type: 'sourceopen'
999 });
1000 ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in");
1001 start();
1032 }); 1002 });
1033 ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in");
1034 }); 1003 });
1035 1004
1036 test('does not break if the playlist has no segments', function() { 1005 test('does not break if the playlist has no segments', function() {
1037 player.hls('manifest/master.m3u8'); 1006 player.src({src: 'manifest/master.m3u8', type: 'application/vnd.apple.mpegurl'});
1038 try { 1007 try {
1039 videojs.mediaSources[player.currentSrc()].trigger({ 1008 player.hls.mediaSource.trigger({
1040 type: 'sourceopen' 1009 type: 'sourceopen'
1041 }); 1010 });
1042 requests[0].respond(200, null, 1011 requests[0].respond(200, null,
......