c64d4a02 by David LaPalomento

Merge pull request #35 from videojs/withcredentials

Withcredentials
2 parents 45ec53ae 5587efc9
...@@ -13,27 +13,28 @@ ...@@ -13,27 +13,28 @@
13 "test": "grunt test-local" 13 "test": "grunt test-local"
14 }, 14 },
15 "devDependencies": { 15 "devDependencies": {
16 "grunt": "~0.4.1",
17 "grunt-concurrent": "0.4.3",
18 "grunt-contrib-clean": "~0.4.0",
19 "grunt-contrib-concat": "~0.3.0",
20 "grunt-contrib-connect": "~0.6.0",
16 "grunt-contrib-jshint": "~0.6.0", 21 "grunt-contrib-jshint": "~0.6.0",
17 "grunt-contrib-qunit": "~0.2.0", 22 "grunt-contrib-qunit": "~0.2.0",
18 "grunt-contrib-concat": "~0.3.0",
19 "grunt-contrib-uglify": "~0.2.0", 23 "grunt-contrib-uglify": "~0.2.0",
20 "grunt-contrib-watch": "~0.4.0", 24 "grunt-contrib-watch": "~0.4.0",
21 "grunt-contrib-clean": "~0.4.0", 25 "grunt-karma": "~0.6.2",
22 "grunt-contrib-connect": "~0.6.0",
23 "grunt-concurrent": "0.4.3",
24 "grunt-open": "0.2.3", 26 "grunt-open": "0.2.3",
25 "grunt-shell": "0.6.1", 27 "grunt-shell": "0.6.1",
26 "grunt": "~0.4.1",
27 "grunt-karma": "~0.6.2",
28 "karma": "~0.10.0", 28 "karma": "~0.10.0",
29 "karma-sauce-launcher": "~0.1.8",
30 "karma-chrome-launcher": "~0.1.2", 29 "karma-chrome-launcher": "~0.1.2",
31 "karma-firefox-launcher": "~0.1.3", 30 "karma-firefox-launcher": "~0.1.3",
32 "karma-ie-launcher": "~0.1.1", 31 "karma-ie-launcher": "~0.1.1",
33 "karma-opera-launcher": "~0.1.0", 32 "karma-opera-launcher": "~0.1.0",
34 "karma-phantomjs-launcher": "~0.1.1", 33 "karma-phantomjs-launcher": "~0.1.1",
35 "karma-safari-launcher": "~0.1.1",
36 "karma-qunit": "~0.1.1", 34 "karma-qunit": "~0.1.1",
35 "karma-safari-launcher": "~0.1.1",
36 "karma-sauce-launcher": "~0.1.8",
37 "sinon": "^1.9.1",
37 "video.js": "^4.5" 38 "video.js": "^4.5"
38 }, 39 },
39 "peerDependencies": { 40 "peerDependencies": {
......
...@@ -31,6 +31,9 @@ videojs.hls = { ...@@ -31,6 +31,9 @@ videojs.hls = {
31 }; 31 };
32 32
33 var 33 var
34
35 settings,
36
34 // the desired length of video to maintain in the buffer, in seconds 37 // the desired length of video to maintain in the buffer, in seconds
35 goalBufferLength = 5, 38 goalBufferLength = 5,
36 39
...@@ -109,12 +112,26 @@ var ...@@ -109,12 +112,26 @@ var
109 method: 'GET' 112 method: 'GET'
110 }, 113 },
111 request; 114 request;
115
116 if (typeof callback !== 'function') {
117 callback = function() {};
118 }
119
112 if (typeof url === 'object') { 120 if (typeof url === 'object') {
113 options = videojs.util.mergeOptions(options, url); 121 options = videojs.util.mergeOptions(options, url);
114 url = options.url; 122 url = options.url;
115 } 123 }
124
116 request = new window.XMLHttpRequest(); 125 request = new window.XMLHttpRequest();
117 request.open(options.method, url); 126 request.open(options.method, url);
127
128 if (options.responseType) {
129 request.responseType = options.responseType;
130 }
131 if (settings.withCredentials) {
132 request.withCredentials = true;
133 }
134
118 request.onreadystatechange = function() { 135 request.onreadystatechange = function() {
119 // wait until the request completes 136 // wait until the request completes
120 if (this.readyState !== 4) { 137 if (this.readyState !== 4) {
...@@ -286,6 +303,8 @@ var ...@@ -286,6 +303,8 @@ var
286 return; 303 return;
287 } 304 }
288 305
306 settings = videojs.util.mergeOptions({}, options);
307
289 srcUrl = (function() { 308 srcUrl = (function() {
290 var 309 var
291 extname, 310 extname,
...@@ -299,7 +318,7 @@ var ...@@ -299,7 +318,7 @@ var
299 // use the URL specified in options if one was provided 318 // use the URL specified in options if one was provided
300 if (typeof options === 'string') { 319 if (typeof options === 'string') {
301 return options; 320 return options;
302 } else if (options) { 321 } else if (options && options.url) {
303 return options.url; 322 return options.url;
304 } 323 }
305 324
...@@ -627,24 +646,20 @@ var ...@@ -627,24 +646,20 @@ var
627 segment.uri); 646 segment.uri);
628 } 647 }
629 648
630 // request the next segment 649 startTime = +new Date();
631 segmentXhr = new window.XMLHttpRequest();
632 segmentXhr.open('GET', segmentUri);
633 segmentXhr.responseType = 'arraybuffer';
634 segmentXhr.onreadystatechange = function() {
635 // wait until the request completes
636 if (this.readyState !== 4) {
637 return;
638 }
639 650
651 // request the next segment
652 segmentXhr = xhr({
653 url: segmentUri,
654 responseType: 'arraybuffer'
655 }, function(error, url) {
640 // the segment request is no longer outstanding 656 // the segment request is no longer outstanding
641 segmentXhr = null; 657 segmentXhr = null;
642 658
643 // trigger an error if the request was not successful 659 if (error) {
644 if (this.status >= 400) {
645 player.hls.error = { 660 player.hls.error = {
646 status: this.status, 661 status: this.status,
647 message: 'HLS segment request error at URL: ' + segmentUri, 662 message: 'HLS segment request error at URL: ' + url,
648 code: (this.status >= 500) ? 4 : 2 663 code: (this.status >= 500) ? 4 : 2
649 }; 664 };
650 665
...@@ -694,9 +709,7 @@ var ...@@ -694,9 +709,7 @@ var
694 // figure out what stream the next segment should be downloaded from 709 // figure out what stream the next segment should be downloaded from
695 // with the updated bandwidth information 710 // with the updated bandwidth information
696 updateCurrentPlaylist(); 711 updateCurrentPlaylist();
697 }; 712 });
698 startTime = +new Date();
699 segmentXhr.send(null);
700 }; 713 };
701 714
702 // load the MediaSource into the player 715 // load the MediaSource into the player
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
28 "strictEqual", 28 "strictEqual",
29 "notStrictEqual", 29 "notStrictEqual",
30 "throws", 30 "throws",
31 "sinon",
31 "process" 32 "process"
32 ] 33 ]
33 } 34 }
......
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title>video.js HLS Plugin Test Suite</title> 5 <title>video.js HLS Plugin Test Suite</title>
6 <!-- Load sinon server for fakeXHR -->
7 <script src="../node_modules/sinon/lib/sinon.js"></script>
8 <script src="../node_modules/sinon/lib/sinon/util/event.js"></script>
9 <script src="../node_modules/sinon/lib/sinon/util/xhr_ie.js"></script>
10 <script src="../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js"></script>
11
6 <!-- Load local QUnit. --> 12 <!-- Load local QUnit. -->
7 <link rel="stylesheet" href="../libs/qunit/qunit.css" media="screen"> 13 <link rel="stylesheet" href="../libs/qunit/qunit.css" media="screen">
8 <script src="../libs/qunit/qunit.js"></script> 14 <script src="../libs/qunit/qunit.js"></script>
......
...@@ -23,12 +23,40 @@ ...@@ -23,12 +23,40 @@
23 var 23 var
24 player, 24 player,
25 oldFlashSupported, 25 oldFlashSupported,
26 oldXhr,
27 oldSegmentParser, 26 oldSegmentParser,
28 oldSetTimeout, 27 oldSetTimeout,
29 oldSourceBuffer, 28 oldSourceBuffer,
30 oldSupportsNativeHls, 29 oldSupportsNativeHls,
31 xhrUrls, 30 xhrUrls,
31 requests,
32 xhr,
33
34 standardXHRResponse = function(request) {
35 if (!request.url) {
36 return;
37 }
38
39 var contentType = "application/json",
40 // contents off the global object
41 manifestName = (/(?:.*\/)?(.*)\.m3u8/).exec(request.url);
42
43 if (manifestName) {
44 manifestName = manifestName[1];
45 } else {
46 manifestName = request.url;
47 }
48
49 if (/\.m3u8?/.test(request.url)) {
50 contentType = 'application/vnd.apple.mpegurl';
51 } else if (/\.ts/.test(request.url)) {
52 contentType = 'video/MP2T';
53 }
54
55 request.response = new Uint8Array([1]).buffer;
56 request.respond(200,
57 {'Content-Type': contentType},
58 window.manifests[manifestName]);
59 },
32 60
33 mockSegmentParser = function(tags) { 61 mockSegmentParser = function(tags) {
34 if (tags === undefined) { 62 if (tags === undefined) {
...@@ -88,35 +116,21 @@ module('HLS', { ...@@ -88,35 +116,21 @@ module('HLS', {
88 oldSetTimeout = window.setTimeout; 116 oldSetTimeout = window.setTimeout;
89 117
90 // make XHRs synchronous 118 // make XHRs synchronous
91 oldXhr = window.XMLHttpRequest; 119 xhr = sinon.useFakeXMLHttpRequest();
92 window.XMLHttpRequest = function() { 120 requests = [];
93 this.open = function(method, url) { 121 xhr.onCreate = function(xhr) {
94 xhrUrls.push(url); 122 requests.push(xhr);
95 };
96 this.send = function() {
97 // if the request URL looks like one of the test manifests, grab the
98 // contents off the global object
99 var manifestName = (/(?:.*\/)?(.*)\.m3u8/).exec(xhrUrls.slice(-1)[0]);
100 if (manifestName) {
101 manifestName = manifestName[1];
102 }
103 this.responseText = window.manifests[manifestName || xhrUrls.slice(-1)[0]];
104 this.response = new Uint8Array([1]).buffer;
105
106 this.readyState = 4;
107 this.onreadystatechange();
108 };
109 this.abort = function() {};
110 }; 123 };
111 xhrUrls = []; 124 xhrUrls = [];
112 }, 125 },
126
113 teardown: function() { 127 teardown: function() {
114 videojs.Flash.isSupported = oldFlashSupported; 128 videojs.Flash.isSupported = oldFlashSupported;
115 videojs.hls.supportsNativeHls = oldSupportsNativeHls; 129 videojs.hls.supportsNativeHls = oldSupportsNativeHls;
116 videojs.hls.SegmentParser = oldSegmentParser; 130 videojs.hls.SegmentParser = oldSegmentParser;
117 videojs.SourceBuffer = oldSourceBuffer; 131 videojs.SourceBuffer = oldSourceBuffer;
118 window.setTimeout = oldSetTimeout; 132 window.setTimeout = oldSetTimeout;
119 window.XMLHttpRequest = oldXhr; 133 xhr.restore();
120 } 134 }
121 }); 135 });
122 136
...@@ -131,6 +145,7 @@ test('starts playing if autoplay is specified', function() { ...@@ -131,6 +145,7 @@ test('starts playing if autoplay is specified', function() {
131 type: 'sourceopen' 145 type: 'sourceopen'
132 }); 146 });
133 147
148 standardXHRResponse(requests[0]);
134 strictEqual(1, plays, 'play was called'); 149 strictEqual(1, plays, 'play was called');
135 }); 150 });
136 151
...@@ -148,6 +163,7 @@ test('loads the specified manifest URL on init', function() { ...@@ -148,6 +163,7 @@ test('loads the specified manifest URL on init', function() {
148 videojs.mediaSources[player.currentSrc()].trigger({ 163 videojs.mediaSources[player.currentSrc()].trigger({
149 type: 'sourceopen' 164 type: 'sourceopen'
150 }); 165 });
166 standardXHRResponse(requests[0]);
151 ok(loadedmanifest, 'loadedmanifest fires'); 167 ok(loadedmanifest, 'loadedmanifest fires');
152 ok(loadedmetadata, 'loadedmetadata fires'); 168 ok(loadedmetadata, 'loadedmetadata fires');
153 ok(player.hls.master, 'a master is inferred'); 169 ok(player.hls.master, 'a master is inferred');
...@@ -172,6 +188,8 @@ test('sets the duration if one is available on the playlist', function() { ...@@ -172,6 +188,8 @@ test('sets the duration if one is available on the playlist', function() {
172 type: 'sourceopen' 188 type: 'sourceopen'
173 }); 189 });
174 190
191 standardXHRResponse(requests[0]);
192 standardXHRResponse(requests[1]);
175 strictEqual(calls, 2, 'duration is set'); 193 strictEqual(calls, 2, 'duration is set');
176 }); 194 });
177 195
...@@ -188,6 +206,8 @@ test('calculates the duration if needed', function() { ...@@ -188,6 +206,8 @@ test('calculates the duration if needed', function() {
188 type: 'sourceopen' 206 type: 'sourceopen'
189 }); 207 });
190 208
209 standardXHRResponse(requests[0]);
210 standardXHRResponse(requests[1]);
191 strictEqual(durations.length, 2, 'duration is set'); 211 strictEqual(durations.length, 2, 'duration is set');
192 strictEqual(durations[0], 212 strictEqual(durations[0],
193 player.hls.media.segments.length * 10, 213 player.hls.media.segments.length * 10,
...@@ -203,7 +223,9 @@ test('starts downloading a segment on loadedmetadata', function() { ...@@ -203,7 +223,9 @@ test('starts downloading a segment on loadedmetadata', function() {
203 type: 'sourceopen' 223 type: 'sourceopen'
204 }); 224 });
205 225
206 strictEqual(xhrUrls[1], 226 standardXHRResponse(requests[0]);
227 standardXHRResponse(requests[1]);
228 strictEqual(requests[1].url,
207 window.location.origin + 229 window.location.origin +
208 window.location.pathname.split('/').slice(0, -1).join('/') + 230 window.location.pathname.split('/').slice(0, -1).join('/') +
209 '/manifest/00001.ts', 231 '/manifest/00001.ts',
...@@ -216,7 +238,9 @@ test('recognizes absolute URIs and requests them unmodified', function() { ...@@ -216,7 +238,9 @@ test('recognizes absolute URIs and requests them unmodified', function() {
216 type: 'sourceopen' 238 type: 'sourceopen'
217 }); 239 });
218 240
219 strictEqual(xhrUrls[1], 241 standardXHRResponse(requests[0]);
242 standardXHRResponse(requests[1]);
243 strictEqual(requests[1].url,
220 'http://example.com/00001.ts', 244 'http://example.com/00001.ts',
221 'the first segment is requested'); 245 'the first segment is requested');
222 }); 246 });
...@@ -227,7 +251,9 @@ test('recognizes domain-relative URLs', function() { ...@@ -227,7 +251,9 @@ test('recognizes domain-relative URLs', function() {
227 type: 'sourceopen' 251 type: 'sourceopen'
228 }); 252 });
229 253
230 strictEqual(xhrUrls[1], 254 standardXHRResponse(requests[0]);
255 standardXHRResponse(requests[1]);
256 strictEqual(requests[1].url,
231 window.location.origin + '/00001.ts', 257 window.location.origin + '/00001.ts',
232 'the first segment is requested'); 258 'the first segment is requested');
233 }); 259 });
...@@ -273,13 +299,17 @@ test('downloads media playlists after loading the master', function() { ...@@ -273,13 +299,17 @@ test('downloads media playlists after loading the master', function() {
273 type: 'sourceopen' 299 type: 'sourceopen'
274 }); 300 });
275 301
276 strictEqual(xhrUrls[0], 'manifest/master.m3u8', 'master playlist requested'); 302 standardXHRResponse(requests[0]);
277 strictEqual(xhrUrls[1], 303 standardXHRResponse(requests[1]);
304 standardXHRResponse(requests[2]);
305
306 strictEqual(requests[0].url, 'manifest/master.m3u8', 'master playlist requested');
307 strictEqual(requests[1].url,
278 window.location.origin + 308 window.location.origin +
279 window.location.pathname.split('/').slice(0, -1).join('/') + 309 window.location.pathname.split('/').slice(0, -1).join('/') +
280 '/manifest/media.m3u8', 310 '/manifest/media.m3u8',
281 'media playlist requested'); 311 'media playlist requested');
282 strictEqual(xhrUrls[2], 312 strictEqual(requests[2].url,
283 window.location.origin + 313 window.location.origin +
284 window.location.pathname.split('/').slice(0, -1).join('/') + 314 window.location.pathname.split('/').slice(0, -1).join('/') +
285 '/manifest/00001.ts', 315 '/manifest/00001.ts',
...@@ -310,6 +340,9 @@ test('calculates the bandwidth after downloading a segment', function() { ...@@ -310,6 +340,9 @@ test('calculates the bandwidth after downloading a segment', function() {
310 type: 'sourceopen' 340 type: 'sourceopen'
311 }); 341 });
312 342
343 standardXHRResponse(requests[0]);
344 standardXHRResponse(requests[1]);
345
313 ok(player.hls.bandwidth, 'bandwidth is calculated'); 346 ok(player.hls.bandwidth, 'bandwidth is calculated');
314 ok(player.hls.bandwidth > 0, 347 ok(player.hls.bandwidth > 0,
315 'bandwidth is positive: ' + player.hls.bandwidth); 348 'bandwidth is positive: ' + player.hls.bandwidth);
...@@ -328,6 +361,10 @@ test('selects a playlist after segment downloads', function() { ...@@ -328,6 +361,10 @@ test('selects a playlist after segment downloads', function() {
328 type: 'sourceopen' 361 type: 'sourceopen'
329 }); 362 });
330 363
364 standardXHRResponse(requests[0]);
365 standardXHRResponse(requests[1]);
366 standardXHRResponse(requests[2]);
367
331 strictEqual(calls, 1, 'selects after the initial segment'); 368 strictEqual(calls, 1, 'selects after the initial segment');
332 player.currentTime = function() { 369 player.currentTime = function() {
333 return 1; 370 return 1;
...@@ -336,28 +373,26 @@ test('selects a playlist after segment downloads', function() { ...@@ -336,28 +373,26 @@ test('selects a playlist after segment downloads', function() {
336 return videojs.createTimeRange(0, 2); 373 return videojs.createTimeRange(0, 2);
337 }; 374 };
338 player.trigger('timeupdate'); 375 player.trigger('timeupdate');
376
377 standardXHRResponse(requests[3]);
339 strictEqual(calls, 2, 'selects after additional segments'); 378 strictEqual(calls, 2, 'selects after additional segments');
340 }); 379 });
341 380
342 test('moves to the next segment if there is a network error', function() { 381 test('moves to the next segment if there is a network error', function() {
343 var mediaIndex; 382 var mediaIndex;
383
344 player.hls('manifest/master.m3u8'); 384 player.hls('manifest/master.m3u8');
345 videojs.mediaSources[player.currentSrc()].trigger({ 385 videojs.mediaSources[player.currentSrc()].trigger({
346 type: 'sourceopen' 386 type: 'sourceopen'
347 }); 387 });
348 388
349 // fail the next segment request 389 standardXHRResponse(requests[0]);
350 window.XMLHttpRequest = function() { 390 standardXHRResponse(requests[1]);
351 this.open = function() {}; 391
352 this.send = function() {
353 this.readyState = 4;
354 this.status = 400;
355 this.onreadystatechange();
356 };
357 };
358 mediaIndex = player.hls.mediaIndex; 392 mediaIndex = player.hls.mediaIndex;
359 player.trigger('timeupdate'); 393 player.trigger('timeupdate');
360 394
395 requests[2].respond(400);
361 strictEqual(mediaIndex + 1, player.hls.mediaIndex, 'media index is incremented'); 396 strictEqual(mediaIndex + 1, player.hls.mediaIndex, 'media index is incremented');
362 }); 397 });
363 398
...@@ -383,6 +418,10 @@ test('updates the duration after switching playlists', function() { ...@@ -383,6 +418,10 @@ test('updates the duration after switching playlists', function() {
383 type: 'sourceopen' 418 type: 'sourceopen'
384 }); 419 });
385 420
421 standardXHRResponse(requests[0]);
422 standardXHRResponse(requests[1]);
423 standardXHRResponse(requests[2]);
424 standardXHRResponse(requests[3]);
386 ok(selectedPlaylist, 'selected playlist'); 425 ok(selectedPlaylist, 'selected playlist');
387 strictEqual(calls, 1, 'updates the duration'); 426 strictEqual(calls, 1, 'updates the duration');
388 }); 427 });
...@@ -398,6 +437,8 @@ test('downloads additional playlists if required', function() { ...@@ -398,6 +437,8 @@ test('downloads additional playlists if required', function() {
398 type: 'sourceopen' 437 type: 'sourceopen'
399 }); 438 });
400 439
440 standardXHRResponse(requests[0]);
441 standardXHRResponse(requests[1]);
401 // before an m3u8 is downloaded, no segments are available 442 // before an m3u8 is downloaded, no segments are available
402 player.hls.selectPlaylist = function() { 443 player.hls.selectPlaylist = function() {
403 if (!called) { 444 if (!called) {
...@@ -407,13 +448,15 @@ test('downloads additional playlists if required', function() { ...@@ -407,13 +448,15 @@ test('downloads additional playlists if required', function() {
407 playlist.segments = [1, 1, 1]; 448 playlist.segments = [1, 1, 1];
408 return playlist; 449 return playlist;
409 }; 450 };
410 xhrUrls = [];
411 451
412 // the playlist selection is revisited after a new segment is downloaded 452 // the playlist selection is revisited after a new segment is downloaded
413 player.trigger('timeupdate'); 453 player.trigger('timeupdate');
414 454
415 strictEqual(2, xhrUrls.length, 'requests were made'); 455 standardXHRResponse(requests[2]);
416 strictEqual(xhrUrls[1], 456 standardXHRResponse(requests[3]);
457
458 strictEqual(4, requests.length, 'requests were made');
459 strictEqual(requests[3].url,
417 window.location.origin + 460 window.location.origin +
418 window.location.pathname.split('/').slice(0, -1).join('/') + 461 window.location.pathname.split('/').slice(0, -1).join('/') +
419 '/manifest/' + 462 '/manifest/' +
...@@ -430,6 +473,8 @@ test('selects a playlist below the current bandwidth', function() { ...@@ -430,6 +473,8 @@ test('selects a playlist below the current bandwidth', function() {
430 type: 'sourceopen' 473 type: 'sourceopen'
431 }); 474 });
432 475
476 standardXHRResponse(requests[0]);
477
433 // the default playlist has a really high bitrate 478 // the default playlist has a really high bitrate
434 player.hls.master.playlists[0].attributes.BANDWIDTH = 9e10; 479 player.hls.master.playlists[0].attributes.BANDWIDTH = 9e10;
435 // playlist 1 has a very low bitrate 480 // playlist 1 has a very low bitrate
...@@ -450,6 +495,8 @@ test('raises the minimum bitrate for a stream proportionially', function() { ...@@ -450,6 +495,8 @@ test('raises the minimum bitrate for a stream proportionially', function() {
450 type: 'sourceopen' 495 type: 'sourceopen'
451 }); 496 });
452 497
498 standardXHRResponse(requests[0]);
499
453 // the default playlist's bandwidth + 10% is equal to the current bandwidth 500 // the default playlist's bandwidth + 10% is equal to the current bandwidth
454 player.hls.master.playlists[0].attributes.BANDWIDTH = 10; 501 player.hls.master.playlists[0].attributes.BANDWIDTH = 10;
455 player.hls.bandwidth = 11; 502 player.hls.bandwidth = 11;
...@@ -470,6 +517,8 @@ test('uses the lowest bitrate if no other is suitable', function() { ...@@ -470,6 +517,8 @@ test('uses the lowest bitrate if no other is suitable', function() {
470 type: 'sourceopen' 517 type: 'sourceopen'
471 }); 518 });
472 519
520 standardXHRResponse(requests[0]);
521
473 // the lowest bitrate playlist is much greater than 1b/s 522 // the lowest bitrate playlist is much greater than 1b/s
474 player.hls.bandwidth = 1; 523 player.hls.bandwidth = 1;
475 playlist = player.hls.selectPlaylist(); 524 playlist = player.hls.selectPlaylist();
...@@ -489,6 +538,8 @@ test('selects the correct rendition by player dimensions', function() { ...@@ -489,6 +538,8 @@ test('selects the correct rendition by player dimensions', function() {
489 type: 'sourceopen' 538 type: 'sourceopen'
490 }); 539 });
491 540
541 standardXHRResponse(requests[0]);
542
492 player.width(640); 543 player.width(640);
493 player.height(360); 544 player.height(360);
494 player.hls.bandwidth = 3000000; 545 player.hls.bandwidth = 3000000;
...@@ -521,9 +572,12 @@ test('does not download the next segment if the buffer is full', function() { ...@@ -521,9 +572,12 @@ test('does not download the next segment if the buffer is full', function() {
521 videojs.mediaSources[player.currentSrc()].trigger({ 572 videojs.mediaSources[player.currentSrc()].trigger({
522 type: 'sourceopen' 573 type: 'sourceopen'
523 }); 574 });
575
576 standardXHRResponse(requests[0]);
577
524 player.trigger('timeupdate'); 578 player.trigger('timeupdate');
525 579
526 strictEqual(xhrUrls.length, 1, 'no segment request was made'); 580 strictEqual(requests.length, 1, 'no segment request was made');
527 }); 581 });
528 582
529 test('downloads the next segment if the buffer is getting low', function() { 583 test('downloads the next segment if the buffer is getting low', function() {
...@@ -531,7 +585,11 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -531,7 +585,11 @@ test('downloads the next segment if the buffer is getting low', function() {
531 videojs.mediaSources[player.currentSrc()].trigger({ 585 videojs.mediaSources[player.currentSrc()].trigger({
532 type: 'sourceopen' 586 type: 'sourceopen'
533 }); 587 });
534 strictEqual(xhrUrls.length, 2, 'did not make a request'); 588
589 standardXHRResponse(requests[0]);
590 standardXHRResponse(requests[1]);
591
592 strictEqual(requests.length, 2, 'did not make a request');
535 player.currentTime = function() { 593 player.currentTime = function() {
536 return 15; 594 return 15;
537 }; 595 };
...@@ -540,8 +598,10 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -540,8 +598,10 @@ test('downloads the next segment if the buffer is getting low', function() {
540 }; 598 };
541 player.trigger('timeupdate'); 599 player.trigger('timeupdate');
542 600
543 strictEqual(xhrUrls.length, 3, 'made a request'); 601 standardXHRResponse(requests[2]);
544 strictEqual(xhrUrls[2], 602
603 strictEqual(requests.length, 3, 'made a request');
604 strictEqual(requests[2].url,
545 window.location.origin + 605 window.location.origin +
546 window.location.pathname.split('/').slice(0, -1).join('/') + 606 window.location.pathname.split('/').slice(0, -1).join('/') +
547 '/manifest/00002.ts', 607 '/manifest/00002.ts',
...@@ -553,7 +613,8 @@ test('stops downloading segments at the end of the playlist', function() { ...@@ -553,7 +613,8 @@ test('stops downloading segments at the end of the playlist', function() {
553 videojs.mediaSources[player.currentSrc()].trigger({ 613 videojs.mediaSources[player.currentSrc()].trigger({
554 type: 'sourceopen' 614 type: 'sourceopen'
555 }); 615 });
556 xhrUrls = []; 616 standardXHRResponse(requests[0]);
617 requests = [];
557 player.hls.mediaIndex = 4; 618 player.hls.mediaIndex = 4;
558 player.trigger('timeupdate'); 619 player.trigger('timeupdate');
559 620
...@@ -566,6 +627,8 @@ test('only makes one segment request at a time', function() { ...@@ -566,6 +627,8 @@ test('only makes one segment request at a time', function() {
566 videojs.mediaSources[player.currentSrc()].trigger({ 627 videojs.mediaSources[player.currentSrc()].trigger({
567 type: 'sourceopen' 628 type: 'sourceopen'
568 }); 629 });
630 xhr.restore();
631 var oldXHR = window.XMLHttpRequest;
569 // mock out a long-running XHR 632 // mock out a long-running XHR
570 window.XMLHttpRequest = function() { 633 window.XMLHttpRequest = function() {
571 this.send = function() {}; 634 this.send = function() {};
...@@ -573,11 +636,14 @@ test('only makes one segment request at a time', function() { ...@@ -573,11 +636,14 @@ test('only makes one segment request at a time', function() {
573 openedXhrs++; 636 openedXhrs++;
574 }; 637 };
575 }; 638 };
639 standardXHRResponse(requests[0]);
576 player.trigger('timeupdate'); 640 player.trigger('timeupdate');
577 641
578 strictEqual(1, openedXhrs, 'one XHR is made'); 642 strictEqual(1, openedXhrs, 'one XHR is made');
579 player.trigger('timeupdate'); 643 player.trigger('timeupdate');
580 strictEqual(1, openedXhrs, 'only one XHR is made'); 644 strictEqual(1, openedXhrs, 'only one XHR is made');
645 window.XMLHttpRequest = oldXHR;
646 xhr = sinon.useFakeXMLHttpRequest();
581 }); 647 });
582 648
583 test('uses the src attribute if no options are provided and it ends in ".m3u8"', function() { 649 test('uses the src attribute if no options are provided and it ends in ".m3u8"', function() {
...@@ -588,7 +654,7 @@ test('uses the src attribute if no options are provided and it ends in ".m3u8"', ...@@ -588,7 +654,7 @@ test('uses the src attribute if no options are provided and it ends in ".m3u8"',
588 type: 'sourceopen' 654 type: 'sourceopen'
589 }); 655 });
590 656
591 strictEqual(url, xhrUrls[0], 'currentSrc is used'); 657 strictEqual(requests[0].url, url, 'currentSrc is used');
592 }); 658 });
593 659
594 test('ignores src attribute if it doesn\'t have the "m3u8" extension', function() { 660 test('ignores src attribute if it doesn\'t have the "m3u8" extension', function() {
...@@ -596,27 +662,27 @@ test('ignores src attribute if it doesn\'t have the "m3u8" extension', function( ...@@ -596,27 +662,27 @@ test('ignores src attribute if it doesn\'t have the "m3u8" extension', function(
596 tech.src = 'basdfasdfasdfliel//.m3u9'; 662 tech.src = 'basdfasdfasdfliel//.m3u9';
597 player.hls(); 663 player.hls();
598 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); 664 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
599 strictEqual(xhrUrls.length, 0, 'no request is made'); 665 strictEqual(requests.length, 0, 'no request is made');
600 666
601 tech.src = ''; 667 tech.src = '';
602 player.hls(); 668 player.hls();
603 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); 669 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
604 strictEqual(xhrUrls.length, 0, 'no request is made'); 670 strictEqual(requests.length, 0, 'no request is made');
605 671
606 tech.src = 'http://example.com/movie.mp4?q=why.m3u8'; 672 tech.src = 'http://example.com/movie.mp4?q=why.m3u8';
607 player.hls(); 673 player.hls();
608 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); 674 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
609 strictEqual(xhrUrls.length, 0, 'no request is made'); 675 strictEqual(requests.length, 0, 'no request is made');
610 676
611 tech.src = 'http://example.m3u8/movie.mp4'; 677 tech.src = 'http://example.m3u8/movie.mp4';
612 player.hls(); 678 player.hls();
613 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); 679 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
614 strictEqual(xhrUrls.length, 0, 'no request is made'); 680 strictEqual(requests.length, 0, 'no request is made');
615 681
616 tech.src = '//example.com/movie.mp4#http://tricky.com/master.m3u8'; 682 tech.src = '//example.com/movie.mp4#http://tricky.com/master.m3u8';
617 player.hls(); 683 player.hls();
618 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created'); 684 ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
619 strictEqual(xhrUrls.length, 0, 'no request is made'); 685 strictEqual(requests.length, 0, 'no request is made');
620 }); 686 });
621 687
622 test('activates if the first playable source is HLS', function() { 688 test('activates if the first playable source is HLS', function() {
...@@ -640,13 +706,11 @@ test('activates if the first playable source is HLS', function() { ...@@ -640,13 +706,11 @@ test('activates if the first playable source is HLS', function() {
640 }); 706 });
641 707
642 test('cancels outstanding XHRs when seeking', function() { 708 test('cancels outstanding XHRs when seeking', function() {
643 var
644 aborted = false,
645 opened = 0;
646 player.hls('manifest/media.m3u8'); 709 player.hls('manifest/media.m3u8');
647 videojs.mediaSources[player.currentSrc()].trigger({ 710 videojs.mediaSources[player.currentSrc()].trigger({
648 type: 'sourceopen' 711 type: 'sourceopen'
649 }); 712 });
713 standardXHRResponse(requests[0]);
650 player.hls.media = { 714 player.hls.media = {
651 segments: [{ 715 segments: [{
652 uri: '0.ts', 716 uri: '0.ts',
...@@ -657,27 +721,13 @@ test('cancels outstanding XHRs when seeking', function() { ...@@ -657,27 +721,13 @@ test('cancels outstanding XHRs when seeking', function() {
657 }] 721 }]
658 }; 722 };
659 723
660 // XHR requests will never complete
661 window.XMLHttpRequest = function() {
662 this.open = function() {
663 opened++;
664 };
665 this.send = function() {};
666 this.abort = function() {
667 aborted = true;
668 this.readyState = 4;
669 this.status = 0;
670 this.onreadystatechange();
671 };
672 };
673 // trigger a segment download request 724 // trigger a segment download request
674 player.trigger('timeupdate'); 725 player.trigger('timeupdate');
675 opened = 0;
676 // attempt to seek while the download is in progress 726 // attempt to seek while the download is in progress
677 player.trigger('seeking'); 727 player.trigger('seeking');
678 728
679 ok(aborted, 'XHR aborted'); 729 ok(requests[1].aborted, 'XHR aborted');
680 strictEqual(1, opened, 'opened new XHR'); 730 strictEqual(requests.length, 3, 'opened new XHR');
681 }); 731 });
682 732
683 test('flushes the parser after each segment', function() { 733 test('flushes the parser after each segment', function() {
...@@ -699,12 +749,13 @@ test('flushes the parser after each segment', function() { ...@@ -699,12 +749,13 @@ test('flushes the parser after each segment', function() {
699 type: 'sourceopen' 749 type: 'sourceopen'
700 }); 750 });
701 751
702 strictEqual(1, flushes, 'tags are flushed at the end of a segment'); 752 standardXHRResponse(requests[0]);
753 standardXHRResponse(requests[1]);
754 strictEqual(flushes, 1, 'tags are flushed at the end of a segment');
703 }); 755 });
704 756
705 test('drops tags before the target timestamp when seeking', function() { 757 test('drops tags before the target timestamp when seeking', function() {
706 var 758 var i = 10,
707 i = 10,
708 callbacks = [], 759 callbacks = [],
709 tags = [], 760 tags = [],
710 bytes = []; 761 bytes = [];
...@@ -729,6 +780,8 @@ test('drops tags before the target timestamp when seeking', function() { ...@@ -729,6 +780,8 @@ test('drops tags before the target timestamp when seeking', function() {
729 videojs.mediaSources[player.currentSrc()].trigger({ 780 videojs.mediaSources[player.currentSrc()].trigger({
730 type: 'sourceopen' 781 type: 'sourceopen'
731 }); 782 });
783 standardXHRResponse(requests[0]);
784 standardXHRResponse(requests[1]);
732 while (callbacks.length) { 785 while (callbacks.length) {
733 callbacks.shift()(); 786 callbacks.shift()();
734 } 787 }
...@@ -745,6 +798,7 @@ test('drops tags before the target timestamp when seeking', function() { ...@@ -745,6 +798,7 @@ test('drops tags before the target timestamp when seeking', function() {
745 return 7; 798 return 7;
746 }; 799 };
747 player.trigger('seeking'); 800 player.trigger('seeking');
801 standardXHRResponse(requests[2]);
748 802
749 while (callbacks.length) { 803 while (callbacks.length) {
750 callbacks.shift()(); 804 callbacks.shift()();
...@@ -759,6 +813,7 @@ test('clears pending buffer updates when seeking', function() { ...@@ -759,6 +813,7 @@ test('clears pending buffer updates when seeking', function() {
759 callbacks = [], 813 callbacks = [],
760 aborts = 0, 814 aborts = 0,
761 tags = [{ pts: 0, bytes: 0 }]; 815 tags = [{ pts: 0, bytes: 0 }];
816
762 // mock out the parser and source buffer 817 // mock out the parser and source buffer
763 videojs.hls.SegmentParser = mockSegmentParser(tags); 818 videojs.hls.SegmentParser = mockSegmentParser(tags);
764 window.videojs.SourceBuffer = function() { 819 window.videojs.SourceBuffer = function() {
...@@ -780,12 +835,16 @@ test('clears pending buffer updates when seeking', function() { ...@@ -780,12 +835,16 @@ test('clears pending buffer updates when seeking', function() {
780 type: 'sourceopen' 835 type: 'sourceopen'
781 }); 836 });
782 837
838 standardXHRResponse(requests[0]);
839 standardXHRResponse(requests[1]);
840
783 // seek to 7s 841 // seek to 7s
784 tags.push({ pts: 7000, bytes: 7 }); 842 tags.push({ pts: 7000, bytes: 7 });
785 player.currentTime = function() { 843 player.currentTime = function() {
786 return 7; 844 return 7;
787 }; 845 };
788 player.trigger('seeking'); 846 player.trigger('seeking');
847 standardXHRResponse(requests[2]);
789 848
790 while (callbacks.length) { 849 while (callbacks.length) {
791 callbacks.shift()(); 850 callbacks.shift()();
...@@ -826,23 +885,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() { ...@@ -826,23 +885,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
826 test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { 885 test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
827 player.hls('manifest/media.m3u8'); 886 player.hls('manifest/media.m3u8');
828 887
829 player.on('loadedmanifest', function () {
830 window.XMLHttpRequest = function () {
831 this.open = function (method, url) {
832 xhrUrls.push(url);
833 };
834 this.send = function () {
835 this.readyState = 4;
836 this.status = 404;
837 this.onreadystatechange();
838 };
839 };
840 });
841
842 videojs.mediaSources[player.currentSrc()].trigger({ 888 videojs.mediaSources[player.currentSrc()].trigger({
843 type: 'sourceopen' 889 type: 'sourceopen'
844 }); 890 });
845 891
892 standardXHRResponse(requests[0]);
893 requests[1].respond(404);
846 ok(player.hls.error.message, 'an error message is available'); 894 ok(player.hls.error.message, 'an error message is available');
847 equal(2, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_NETWORK'); 895 equal(2, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_NETWORK');
848 }); 896 });
...@@ -850,23 +898,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () { ...@@ -850,23 +898,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
850 test('segment 500 should trigger MEDIA_ERR_ABORTED', function () { 898 test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
851 player.hls('manifest/media.m3u8'); 899 player.hls('manifest/media.m3u8');
852 900
853 player.on('loadedmanifest', function () {
854 window.XMLHttpRequest = function () {
855 this.open = function (method, url) {
856 xhrUrls.push(url);
857 };
858 this.send = function () {
859 this.readyState = 4;
860 this.status = 500;
861 this.onreadystatechange();
862 };
863 };
864 });
865
866 videojs.mediaSources[player.currentSrc()].trigger({ 901 videojs.mediaSources[player.currentSrc()].trigger({
867 type: 'sourceopen' 902 type: 'sourceopen'
868 }); 903 });
869 904
905 standardXHRResponse(requests[0]);
906 requests[1].respond(500);
870 ok(player.hls.error.message, 'an error message is available'); 907 ok(player.hls.error.message, 'an error message is available');
871 equal(4, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_ABORTED'); 908 equal(4, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_ABORTED');
872 }); 909 });
...@@ -889,6 +926,7 @@ test('reloads live playlists', function() { ...@@ -889,6 +926,7 @@ test('reloads live playlists', function() {
889 videojs.mediaSources[player.currentSrc()].trigger({ 926 videojs.mediaSources[player.currentSrc()].trigger({
890 type: 'sourceopen' 927 type: 'sourceopen'
891 }); 928 });
929 standardXHRResponse(requests[0]);
892 930
893 strictEqual(1, callbacks.length, 'refresh was scheduled'); 931 strictEqual(1, callbacks.length, 'refresh was scheduled');
894 strictEqual(player.hls.media.targetDuration * 1000, 932 strictEqual(player.hls.media.targetDuration * 1000,
...@@ -902,7 +940,9 @@ test('duration is Infinity for live playlists', function() { ...@@ -902,7 +940,9 @@ test('duration is Infinity for live playlists', function() {
902 type: 'sourceopen' 940 type: 'sourceopen'
903 }); 941 });
904 942
905 strictEqual(Infinity, player.duration(), 'duration is infinity'); 943 standardXHRResponse(requests[0]);
944
945 strictEqual(player.duration(), Infinity, 'duration is infinity');
906 }); 946 });
907 947
908 test('does not reload playlists with an endlist tag', function() { 948 test('does not reload playlists with an endlist tag', function() {
...@@ -931,19 +971,22 @@ test('reloads a live playlist after half a target duration if it has not ' + ...@@ -931,19 +971,22 @@ test('reloads a live playlist after half a target duration if it has not ' +
931 type: 'sourceopen' 971 type: 'sourceopen'
932 }); 972 });
933 973
974 standardXHRResponse(requests[0]);
975 standardXHRResponse(requests[1]);
934 strictEqual(callbacks.length, 1, 'full-length refresh scheduled'); 976 strictEqual(callbacks.length, 1, 'full-length refresh scheduled');
935 callbacks.pop().callback(); 977 callbacks.pop().callback();
978 standardXHRResponse(requests[2]);
936 979
937 strictEqual(1, callbacks.length, 'half-length refresh was scheduled'); 980 strictEqual(callbacks.length, 1, 'half-length refresh was scheduled');
938 strictEqual(callbacks[0].timeout, 981 strictEqual(callbacks[0].timeout,
939 player.hls.media.targetDuration / 2 * 1000, 982 player.hls.media.targetDuration / 2 * 1000,
940 'waited half a target duration'); 983 'waited half a target duration');
941 }); 984 });
942 985
943 test('merges playlist reloads', function() { 986 test('merges playlist reloads', function() {
944 var 987 var oldPlaylist,
945 oldPlaylist,
946 callback; 988 callback;
989
947 // capture timeouts 990 // capture timeouts
948 window.setTimeout = function(cb) { 991 window.setTimeout = function(cb) {
949 callback = cb; 992 callback = cb;
...@@ -953,9 +996,12 @@ test('merges playlist reloads', function() { ...@@ -953,9 +996,12 @@ test('merges playlist reloads', function() {
953 videojs.mediaSources[player.currentSrc()].trigger({ 996 videojs.mediaSources[player.currentSrc()].trigger({
954 type: 'sourceopen' 997 type: 'sourceopen'
955 }); 998 });
999 standardXHRResponse(requests[0]);
1000 standardXHRResponse(requests[1]);
956 oldPlaylist = player.hls.media; 1001 oldPlaylist = player.hls.media;
957 1002
958 callback(); 1003 callback();
1004 standardXHRResponse(requests[2]);
959 ok(oldPlaylist !== player.hls.media, 'player.hls.media was updated'); 1005 ok(oldPlaylist !== player.hls.media, 'player.hls.media was updated');
960 }); 1006 });
961 1007
...@@ -979,6 +1025,8 @@ test('updates the media index when a playlist reloads', function() { ...@@ -979,6 +1025,8 @@ test('updates the media index when a playlist reloads', function() {
979 type: 'sourceopen' 1025 type: 'sourceopen'
980 }); 1026 });
981 1027
1028 standardXHRResponse(requests[0]);
1029 standardXHRResponse(requests[1]);
982 // play the stream until 2.ts is playing 1030 // play the stream until 2.ts is playing
983 player.hls.mediaIndex = 3; 1031 player.hls.mediaIndex = 3;
984 1032
...@@ -992,6 +1040,7 @@ test('updates the media index when a playlist reloads', function() { ...@@ -992,6 +1040,7 @@ test('updates the media index when a playlist reloads', function() {
992 '#EXTINF:10,\n' + 1040 '#EXTINF:10,\n' +
993 '3.ts\n'; 1041 '3.ts\n';
994 callback(); 1042 callback();
1043 standardXHRResponse(requests[2]);
995 1044
996 strictEqual(player.hls.mediaIndex, 2, 'mediaIndex is updated after the reload'); 1045 strictEqual(player.hls.mediaIndex, 2, 'mediaIndex is updated after the reload');
997 }); 1046 });
...@@ -1064,7 +1113,21 @@ test('does not reload master playlists', function() { ...@@ -1064,7 +1113,21 @@ test('does not reload master playlists', function() {
1064 }); 1113 });
1065 1114
1066 test('only reloads the active media playlist', function() { 1115 test('only reloads the active media playlist', function() {
1067 var callbacks = [], urls = [], responses = []; 1116 var callbacks = [],
1117 i = 0,
1118 filteredRequests = [],
1119 customResponse;
1120
1121 customResponse = function(request) {
1122 request.response = new Uint8Array([1]).buffer;
1123 request.respond(200,
1124 {'Content-Type': 'application/vnd.apple.mpegurl'},
1125 '#EXTM3U\n' +
1126 '#EXT-X-MEDIA-SEQUENCE:1\n' +
1127 '#EXTINF:10,\n' +
1128 '1.ts\n');
1129 };
1130
1068 window.setTimeout = function(callback) { 1131 window.setTimeout = function(callback) {
1069 callbacks.push(callback); 1132 callbacks.push(callback);
1070 }; 1133 };
...@@ -1073,25 +1136,12 @@ test('only reloads the active media playlist', function() { ...@@ -1073,25 +1136,12 @@ test('only reloads the active media playlist', function() {
1073 videojs.mediaSources[player.currentSrc()].trigger({ 1136 videojs.mediaSources[player.currentSrc()].trigger({
1074 type: 'sourceopen' 1137 type: 'sourceopen'
1075 }); 1138 });
1139
1140 standardXHRResponse(requests[0]);
1141 standardXHRResponse(requests[1]);
1142
1076 videojs.mediaSources[player.currentSrc()].endOfStream = function() {}; 1143 videojs.mediaSources[player.currentSrc()].endOfStream = function() {};
1077 1144
1078 window.XMLHttpRequest = function() {
1079 this.open = function(method, url) {
1080 urls.push(url);
1081 };
1082 this.send = function() {
1083 var xhr = this;
1084 responses.push(function() {
1085 xhr.readyState = 4;
1086 xhr.responseText = '#EXTM3U\n' +
1087 '#EXT-X-MEDIA-SEQUENCE:1\n' +
1088 '#EXTINF:10,\n' +
1089 '1.ts\n';
1090 xhr.response = new Uint8Array([1]).buffer;
1091 xhr.onreadystatechange();
1092 });
1093 };
1094 };
1095 player.hls.selectPlaylist = function() { 1145 player.hls.selectPlaylist = function() {
1096 return player.hls.master.playlists[1]; 1146 return player.hls.master.playlists[1];
1097 }; 1147 };
...@@ -1101,43 +1151,57 @@ test('only reloads the active media playlist', function() { ...@@ -1101,43 +1151,57 @@ test('only reloads the active media playlist', function() {
1101 1151
1102 player.trigger('timeupdate'); 1152 player.trigger('timeupdate');
1103 strictEqual(callbacks.length, 1, 'a refresh is scheduled'); 1153 strictEqual(callbacks.length, 1, 'a refresh is scheduled');
1104 strictEqual(responses.length, 1, 'segment requested');
1105 1154
1106 responses.shift()(); // segment response 1155 standardXHRResponse(requests[2]); // segment response
1107 responses.shift()(); // loaded switched.m3u8 1156 customResponse(requests[3]); // loaded witched.m3u8
1108 1157
1109 urls = [];
1110 callbacks.shift()(); // out-of-date refresh of missingEndlist.m3u8 1158 callbacks.shift()(); // out-of-date refresh of missingEndlist.m3u8
1111 callbacks.shift()(); // refresh switched.m3u8 1159 callbacks.shift()(); // refresh switched.m3u8
1112 1160
1113 strictEqual(urls.length, 1, 'one refresh was made'); 1161 for (; i < requests.length; i++) {
1114 strictEqual(urls[0], 1162 if (/switched/.test(requests[i].url)) {
1163 filteredRequests.push(requests[i]);
1164 }
1165 }
1166 strictEqual(filteredRequests.length, 2, 'one refresh was made');
1167 strictEqual(filteredRequests[1].url,
1115 'http://example.com/switched.m3u8', 1168 'http://example.com/switched.m3u8',
1116 'refreshed the active playlist'); 1169 'refreshed the active playlist');
1170
1171 });
1172
1173 test('if withCredentials option is used, withCredentials is set on the XHR object', function() {
1174 player.hls({
1175 url: 'http://example.com/media.m3u8',
1176 withCredentials: true
1177 });
1178 videojs.mediaSources[player.currentSrc()].trigger({
1179 type: 'sourceopen'
1180 });
1181 ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in");
1117 }); 1182 });
1118 1183
1119 test('does not break if the playlist has no segments', function() { 1184 test('does not break if the playlist has no segments', function() {
1120 window.XMLHttpRequest = function () { 1185 var customResponse = function(request) {
1121 this.open = function () {}; 1186 request.response = new Uint8Array([1]).buffer;
1122 this.send = function () { 1187 request.respond(200,
1123 this.readyState = 4; 1188 {'Content-Type': 'application/vnd.apple.mpegurl'},
1124 this.status = 200; 1189 '#EXTM3U\n' +
1125 this.responseText = '#EXTM3U\n' +
1126 '#EXT-X-PLAYLIST-TYPE:VOD\n' + 1190 '#EXT-X-PLAYLIST-TYPE:VOD\n' +
1127 '#EXT-X-TARGETDURATION:10\n'; 1191 '#EXT-X-TARGETDURATION:10\n');
1128 this.onreadystatechange();
1129 };
1130 }; 1192 };
1131 player.hls('manifest/master.m3u8'); 1193 player.hls('manifest/master.m3u8');
1132 try { 1194 try {
1133 videojs.mediaSources[player.currentSrc()].trigger({ 1195 videojs.mediaSources[player.currentSrc()].trigger({
1134 type: 'sourceopen' 1196 type: 'sourceopen'
1135 }); 1197 });
1198 customResponse(requests[0]);
1136 } catch(e) { 1199 } catch(e) {
1137 ok(false, 'an error was thrown'); 1200 ok(false, 'an error was thrown');
1138 throw e; 1201 throw e;
1139 } 1202 }
1140 ok(true, 'no error was thrown'); 1203 ok(true, 'no error was thrown');
1204 strictEqual(requests.length, 1, 'no requests for non-existent segments were queued');
1141 }); 1205 });
1142 1206
1143 })(window, window.videojs); 1207 })(window, window.videojs);
......