Merge pull request #35 from videojs/withcredentials
Withcredentials
Showing
5 changed files
with
244 additions
and
159 deletions
... | @@ -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 | ... | ... |
... | @@ -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); | ... | ... |
-
Please register or sign in to post a comment