94e9c811 by David LaPalomento

Merge pull request #75 from videojs/feature/switching

Adaptive-switching simulator
2 parents 95f99307 869d642e
...@@ -66,7 +66,8 @@ module.exports = function(grunt) { ...@@ -66,7 +66,8 @@ module.exports = function(grunt) {
66 '!test/tsSegment.js', 66 '!test/tsSegment.js',
67 '!test/fixtures/*.js', 67 '!test/fixtures/*.js',
68 '!test/manifest/**', 68 '!test/manifest/**',
69 '!test/muxer/**'] 69 '!test/muxer/**',
70 '!test/switcher/**']
70 } 71 }
71 }, 72 },
72 connect: { 73 connect: {
......
...@@ -147,10 +147,17 @@ ...@@ -147,10 +147,17 @@
147 return; 147 return;
148 } 148 }
149 149
150
151
150 loader.state = 'SWITCHING_MEDIA'; 152 loader.state = 'SWITCHING_MEDIA';
151 153
152 // abort any outstanding playlist refreshes 154 // there is already an outstanding playlist request
153 if (request) { 155 if (request) {
156 if (resolveUrl(loader.master.uri, playlist.uri) === request.url) {
157 // requesting to switch to the same playlist multiple times
158 // has no effect after the first
159 return;
160 }
154 request.abort(); 161 request.abort();
155 request = null; 162 request = null;
156 } 163 }
......
...@@ -563,6 +563,7 @@ xhr = videojs.Hls.xhr = function(url, callback) { ...@@ -563,6 +563,7 @@ xhr = videojs.Hls.xhr = function(url, callback) {
563 563
564 request = new window.XMLHttpRequest(); 564 request = new window.XMLHttpRequest();
565 request.open(options.method, url); 565 request.open(options.method, url);
566 request.url = url;
566 567
567 if (options.responseType) { 568 if (options.responseType) {
568 request.responseType = options.responseType; 569 request.responseType = options.responseType;
......
...@@ -349,6 +349,27 @@ ...@@ -349,6 +349,27 @@
349 strictEqual(requests.length, 0, 'no requests is sent'); 349 strictEqual(requests.length, 0, 'no requests is sent');
350 }); 350 });
351 351
352 test('does not abort requests when the same playlist is re-requested', function() {
353 var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
354 requests.pop().respond(200, null,
355 '#EXTM3U\n' +
356 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
357 'low.m3u8\n' +
358 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
359 'high.m3u8\n');
360 requests.pop().respond(200, null,
361 '#EXTM3U\n' +
362 '#EXT-X-MEDIA-SEQUENCE:0\n' +
363 '#EXTINF:10,\n' +
364 'low-0.ts\n' +
365 '#EXT-X-ENDLIST\n');
366 loader.media('high.m3u8');
367 loader.media('high.m3u8');
368
369 strictEqual(requests.length, 1, 'made only one request');
370 ok(!requests[0].aborted, 'request not aborted');
371 });
372
352 test('throws an error if a media switch is initiated too early', function() { 373 test('throws an error if a media switch is initiated too early', function() {
353 var loader = new videojs.Hls.PlaylistLoader('master.m3u8'); 374 var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
354 375
......
1 /* ==========================================================================
2 HTML5 Boilerplate styles - h5bp.com (generated via initializr.com)
3 ========================================================================== */
4
5 html,
6 button,
7 input,
8 select,
9 textarea {
10 color: #222;
11 }
12
13 body {
14 font-size: 1em;
15 line-height: 1.4;
16 }
17
18 ::-moz-selection {
19 background: #b3d4fc;
20 text-shadow: none;
21 }
22
23 ::selection {
24 background: #b3d4fc;
25 text-shadow: none;
26 }
27
28 hr {
29 display: block;
30 height: 1px;
31 border: 0;
32 border-top: 1px solid #ccc;
33 margin: 1em 0;
34 padding: 0;
35 }
36
37 img {
38 vertical-align: middle;
39 }
40
41 fieldset {
42 border: 0;
43 margin: 0;
44 padding: 0;
45 }
46
47 textarea {
48 resize: vertical;
49 }
50
51 .chromeframe {
52 margin: 0.2em 0;
53 background: #ccc;
54 color: #000;
55 padding: 0.2em 0;
56 }
57
58
59 /* ===== Initializr Styles ==================================================
60 Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template
61 ========================================================================== */
62
63 body {
64 font: 16px/26px Helvetica, Helvetica Neue, Arial;
65 }
66
67 .wrapper {
68 width: 90%;
69 margin: 0 5%;
70 }
71
72 /* ===================
73 ALL: Orange Theme
74 =================== */
75
76 .header-container {
77 border-bottom: 20px solid #e44d26;
78 }
79
80 .footer-container,
81 .main aside {
82 border-top: 20px solid #e44d26;
83 }
84
85 .header-container,
86 .footer-container,
87 .main aside {
88 background: #f16529;
89 }
90
91 .title {
92 color: white;
93 }
94
95 /* ==============
96 MOBILE: Menu
97 ============== */
98
99 nav ul {
100 margin: 0;
101 padding: 0;
102 }
103
104 nav a {
105 display: block;
106 margin-bottom: 10px;
107 padding: 15px 0;
108
109 text-align: center;
110 text-decoration: none;
111 font-weight: bold;
112
113 color: white;
114 background: #e44d26;
115 }
116
117 nav a:hover,
118 nav a:visited {
119 color: white;
120 }
121
122 nav a:hover {
123 text-decoration: underline;
124 }
125
126 /* ==============
127 MOBILE: Main
128 ============== */
129
130 .main {
131 padding: 30px 0;
132 }
133
134 .main article h1 {
135 font-size: 2em;
136 }
137
138 .main aside {
139 color: white;
140 padding: 0px 5% 10px;
141 }
142
143 .footer-container footer {
144 color: white;
145 padding: 20px 0;
146 }
147
148 /* ===============
149 ALL: IE Fixes
150 =============== */
151
152 .ie7 .title {
153 padding-top: 20px;
154 }
155
156 /* ==========================================================================
157 Author's custom styles
158 ========================================================================== */
159
160 section {
161 clear: both;
162 }
163
164 form label {
165 display: block;
166 }
167
168 .result-wrapper {
169 float: left;
170 margin: 10px 0;
171 min-width: 422px;
172 width: 50%;
173 }
174
175 .result {
176 border: thin solid #aaa;
177 border-radius: 5px;
178 font-size: 10px;
179 line-height: 15px;
180 margin: 0 5px;
181 padding: 0 10px;
182 }
183
184 /* ==========================================================================
185 Media Queries
186 ========================================================================== */
187
188 @media only screen and (min-width: 480px) {
189
190 /* ====================
191 INTERMEDIATE: Menu
192 ==================== */
193
194 nav a {
195 float: left;
196 width: 27%;
197 margin: 0 1.7%;
198 padding: 25px 2%;
199 margin-bottom: 0;
200 }
201
202 nav li:first-child a {
203 margin-left: 0;
204 }
205
206 nav li:last-child a {
207 margin-right: 0;
208 }
209
210 /* ========================
211 INTERMEDIATE: IE Fixes
212 ======================== */
213
214 nav ul li {
215 display: inline;
216 }
217
218 .oldie nav a {
219 margin: 0 0.7%;
220 }
221 }
222
223 @media only screen and (min-width: 768px) {
224
225 /* ====================
226 WIDE: CSS3 Effects
227 ==================== */
228
229 .header-container,
230 .main aside {
231 -webkit-box-shadow: 0 5px 10px #aaa;
232 -moz-box-shadow: 0 5px 10px #aaa;
233 box-shadow: 0 5px 10px #aaa;
234 }
235
236 /* ============
237 WIDE: Menu
238 ============ */
239
240 .title {
241 float: left;
242 }
243
244 nav {
245 float: right;
246 width: 38%;
247 }
248
249 /* ============
250 WIDE: Main
251 ============ */
252
253 .main article {
254 float: left;
255 width: 100%;
256 }
257 }
258
259 @media only screen and (min-width: 1140px) {
260
261 /* ===============
262 Maximal Width
263 =============== */
264
265 .wrapper {
266 width: 1026px; /* 1140px - 10% for margins */
267 margin: 0 auto;
268 }
269 }
270
271 /* ==========================================================================
272 Helper classes
273 ========================================================================== */
274
275 .ir {
276 background-color: transparent;
277 border: 0;
278 overflow: hidden;
279 *text-indent: -9999px;
280 }
281
282 .ir:before {
283 content: "";
284 display: block;
285 width: 0;
286 height: 150%;
287 }
288
289 .hidden {
290 display: none !important;
291 visibility: hidden;
292 }
293
294 .visuallyhidden {
295 border: 0;
296 clip: rect(0 0 0 0);
297 height: 1px;
298 margin: -1px;
299 overflow: hidden;
300 padding: 0;
301 position: absolute;
302 width: 1px;
303 }
304
305 .visuallyhidden.focusable:active,
306 .visuallyhidden.focusable:focus {
307 clip: auto;
308 height: auto;
309 margin: 0;
310 overflow: visible;
311 position: static;
312 width: auto;
313 }
314
315 .invisible {
316 visibility: hidden;
317 }
318
319 .clearfix:before,
320 .clearfix:after {
321 content: " ";
322 display: table;
323 }
324
325 .clearfix:after {
326 clear: both;
327 }
328
329 .clearfix {
330 *zoom: 1;
331 }
332
333 /* ==========================================================================
334 Print styles
335 ========================================================================== */
336
337 @media print {
338 * {
339 background: transparent !important;
340 color: #000 !important; /* Black prints faster: h5bp.com/s */
341 box-shadow: none !important;
342 text-shadow: none !important;
343 }
344
345 a,
346 a:visited {
347 text-decoration: underline;
348 }
349
350 a[href]:after {
351 content: " (" attr(href) ")";
352 }
353
354 abbr[title]:after {
355 content: " (" attr(title) ")";
356 }
357
358 /*
359 * Don't show links for images, or javascript/internal links
360 */
361
362 .ir a:after,
363 a[href^="javascript:"]:after,
364 a[href^="#"]:after {
365 content: "";
366 }
367
368 pre,
369 blockquote {
370 border: 1px solid #999;
371 page-break-inside: avoid;
372 }
373
374 thead {
375 display: table-header-group; /* h5bp.com/t */
376 }
377
378 tr,
379 img {
380 page-break-inside: avoid;
381 }
382
383 img {
384 max-width: 100% !important;
385 }
386
387 @page {
388 margin: 0.5cm;
389 }
390
391 p,
392 h2,
393 h3 {
394 orphans: 3;
395 widows: 3;
396 }
397
398 h2,
399 h3 {
400 page-break-after: avoid;
401 }
402 }
403
404 /* ==========================================================================
405 Simulation Chart
406 ========================================================================== */
407
408 .axis path,
409 .axis line {
410 fill: none;
411 stroke: #000;
412 shape-rendering: crispEdges;
413 }
414
415 .x.axis path {
416 display: none;
417 }
418
419 .line {
420 fill: none;
421 stroke-width: 1.5px;
422 }
423 .line.bandwidth {
424 stroke: steelblue;
425 }
426 .line.buffered {
427 stroke: #2ca02c;
428 opacity: 0.25;
429 }
430 .line.bitrate {
431 stroke: #999;
432 opacity: 0.5;
433 stroke-dasharray: 5, 5;
434 }
435
436 .timeline {
437 color: #888;
438 height: 500px;
439 margin: 0 auto;
440 text-align: center;
441 width: 960px;
442 }
443
444 /* ==========================================================================
445 Simulation Parameters
446 ========================================================================== */
447
448 .network-timeline label {
449 display: inline;
450 }
451 .network-timeline .time {
452 width: 50px;
453 }
454 .network-timeline .bandwidth {
455 width: 140px;
456 }
...\ No newline at end of file ...\ No newline at end of file
1 /*! normalize.css v1.1.2 | MIT License | git.io/normalize */
2
3 /* ==========================================================================
4 HTML5 display definitions
5 ========================================================================== */
6
7 /**
8 * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
9 */
10
11 article,
12 aside,
13 details,
14 figcaption,
15 figure,
16 footer,
17 header,
18 hgroup,
19 main,
20 nav,
21 section,
22 summary {
23 display: block;
24 }
25
26 /**
27 * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
28 */
29
30 audio,
31 canvas,
32 video {
33 display: inline-block;
34 *display: inline;
35 *zoom: 1;
36 }
37
38 /**
39 * Prevent modern browsers from displaying `audio` without controls.
40 * Remove excess height in iOS 5 devices.
41 */
42
43 audio:not([controls]) {
44 display: none;
45 height: 0;
46 }
47
48 /**
49 * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
50 * Known issue: no IE 6 support.
51 */
52
53 [hidden] {
54 display: none;
55 }
56
57 /* ==========================================================================
58 Base
59 ========================================================================== */
60
61 /**
62 * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
63 * `em` units.
64 * 2. Prevent iOS text size adjust after orientation change, without disabling
65 * user zoom.
66 */
67
68 html {
69 font-size: 100%; /* 1 */
70 -ms-text-size-adjust: 100%; /* 2 */
71 -webkit-text-size-adjust: 100%; /* 2 */
72 }
73
74 /**
75 * Address `font-family` inconsistency between `textarea` and other form
76 * elements.
77 */
78
79 html,
80 button,
81 input,
82 select,
83 textarea {
84 font-family: sans-serif;
85 }
86
87 /**
88 * Address margins handled incorrectly in IE 6/7.
89 */
90
91 body {
92 margin: 0;
93 }
94
95 /* ==========================================================================
96 Links
97 ========================================================================== */
98
99 /**
100 * Address `outline` inconsistency between Chrome and other browsers.
101 */
102
103 a:focus {
104 outline: thin dotted;
105 }
106
107 /**
108 * Improve readability when focused and also mouse hovered in all browsers.
109 */
110
111 a:active,
112 a:hover {
113 outline: 0;
114 }
115
116 /* ==========================================================================
117 Typography
118 ========================================================================== */
119
120 /**
121 * Address font sizes and margins set differently in IE 6/7.
122 * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
123 * and Chrome.
124 */
125
126 h1 {
127 font-size: 2em;
128 margin: 0.67em 0;
129 }
130
131 h2 {
132 font-size: 1.5em;
133 margin: 0.83em 0;
134 }
135
136 h3 {
137 font-size: 1.17em;
138 margin: 1em 0;
139 }
140
141 h4 {
142 font-size: 1em;
143 margin: 1.33em 0;
144 }
145
146 h5 {
147 font-size: 0.83em;
148 margin: 1.67em 0;
149 }
150
151 h6 {
152 font-size: 0.67em;
153 margin: 2.33em 0;
154 }
155
156 /**
157 * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
158 */
159
160 abbr[title] {
161 border-bottom: 1px dotted;
162 }
163
164 /**
165 * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
166 */
167
168 b,
169 strong {
170 font-weight: bold;
171 }
172
173 blockquote {
174 margin: 1em 40px;
175 }
176
177 /**
178 * Address styling not present in Safari 5 and Chrome.
179 */
180
181 dfn {
182 font-style: italic;
183 }
184
185 /**
186 * Address differences between Firefox and other browsers.
187 * Known issue: no IE 6/7 normalization.
188 */
189
190 hr {
191 -moz-box-sizing: content-box;
192 box-sizing: content-box;
193 height: 0;
194 }
195
196 /**
197 * Address styling not present in IE 6/7/8/9.
198 */
199
200 mark {
201 background: #ff0;
202 color: #000;
203 }
204
205 /**
206 * Address margins set differently in IE 6/7.
207 */
208
209 p,
210 pre {
211 margin: 1em 0;
212 }
213
214 /**
215 * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
216 */
217
218 code,
219 kbd,
220 pre,
221 samp {
222 font-family: monospace, serif;
223 _font-family: 'courier new', monospace;
224 font-size: 1em;
225 }
226
227 /**
228 * Improve readability of pre-formatted text in all browsers.
229 */
230
231 pre {
232 white-space: pre;
233 white-space: pre-wrap;
234 word-wrap: break-word;
235 }
236
237 /**
238 * Address CSS quotes not supported in IE 6/7.
239 */
240
241 q {
242 quotes: none;
243 }
244
245 /**
246 * Address `quotes` property not supported in Safari 4.
247 */
248
249 q:before,
250 q:after {
251 content: '';
252 content: none;
253 }
254
255 /**
256 * Address inconsistent and variable font size in all browsers.
257 */
258
259 small {
260 font-size: 80%;
261 }
262
263 /**
264 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
265 */
266
267 sub,
268 sup {
269 font-size: 75%;
270 line-height: 0;
271 position: relative;
272 vertical-align: baseline;
273 }
274
275 sup {
276 top: -0.5em;
277 }
278
279 sub {
280 bottom: -0.25em;
281 }
282
283 /* ==========================================================================
284 Lists
285 ========================================================================== */
286
287 /**
288 * Address margins set differently in IE 6/7.
289 */
290
291 dl,
292 menu,
293 ol,
294 ul {
295 margin: 1em 0;
296 }
297
298 dd {
299 margin: 0 0 0 40px;
300 }
301
302 /**
303 * Address paddings set differently in IE 6/7.
304 */
305
306 menu,
307 ol,
308 ul {
309 padding: 0 0 0 40px;
310 }
311
312 /**
313 * Correct list images handled incorrectly in IE 7.
314 */
315
316 nav ul,
317 nav ol {
318 list-style: none;
319 list-style-image: none;
320 }
321
322 /* ==========================================================================
323 Embedded content
324 ========================================================================== */
325
326 /**
327 * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
328 * 2. Improve image quality when scaled in IE 7.
329 */
330
331 img {
332 border: 0; /* 1 */
333 -ms-interpolation-mode: bicubic; /* 2 */
334 }
335
336 /**
337 * Correct overflow displayed oddly in IE 9.
338 */
339
340 svg:not(:root) {
341 overflow: hidden;
342 }
343
344 /* ==========================================================================
345 Figures
346 ========================================================================== */
347
348 /**
349 * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
350 */
351
352 figure {
353 margin: 0;
354 }
355
356 /* ==========================================================================
357 Forms
358 ========================================================================== */
359
360 /**
361 * Correct margin displayed oddly in IE 6/7.
362 */
363
364 form {
365 margin: 0;
366 }
367
368 /**
369 * Define consistent border, margin, and padding.
370 */
371
372 fieldset {
373 border: 1px solid #c0c0c0;
374 margin: 0 2px;
375 padding: 0.35em 0.625em 0.75em;
376 }
377
378 /**
379 * 1. Correct color not being inherited in IE 6/7/8/9.
380 * 2. Correct text not wrapping in Firefox 3.
381 * 3. Correct alignment displayed oddly in IE 6/7.
382 */
383
384 legend {
385 border: 0; /* 1 */
386 padding: 0;
387 white-space: normal; /* 2 */
388 *margin-left: -7px; /* 3 */
389 }
390
391 /**
392 * 1. Correct font size not being inherited in all browsers.
393 * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
394 * and Chrome.
395 * 3. Improve appearance and consistency in all browsers.
396 */
397
398 button,
399 input,
400 select,
401 textarea {
402 font-size: 100%; /* 1 */
403 margin: 0; /* 2 */
404 vertical-align: baseline; /* 3 */
405 *vertical-align: middle; /* 3 */
406 }
407
408 /**
409 * Address Firefox 3+ setting `line-height` on `input` using `!important` in
410 * the UA stylesheet.
411 */
412
413 button,
414 input {
415 line-height: normal;
416 }
417
418 /**
419 * Address inconsistent `text-transform` inheritance for `button` and `select`.
420 * All other form control elements do not inherit `text-transform` values.
421 * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
422 * Correct `select` style inheritance in Firefox 4+ and Opera.
423 */
424
425 button,
426 select {
427 text-transform: none;
428 }
429
430 /**
431 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
432 * and `video` controls.
433 * 2. Correct inability to style clickable `input` types in iOS.
434 * 3. Improve usability and consistency of cursor style between image-type
435 * `input` and others.
436 * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
437 * Known issue: inner spacing remains in IE 6.
438 */
439
440 button,
441 html input[type="button"], /* 1 */
442 input[type="reset"],
443 input[type="submit"] {
444 -webkit-appearance: button; /* 2 */
445 cursor: pointer; /* 3 */
446 *overflow: visible; /* 4 */
447 }
448
449 /**
450 * Re-set default cursor for disabled elements.
451 */
452
453 button[disabled],
454 html input[disabled] {
455 cursor: default;
456 }
457
458 /**
459 * 1. Address box sizing set to content-box in IE 8/9.
460 * 2. Remove excess padding in IE 8/9.
461 * 3. Remove excess padding in IE 7.
462 * Known issue: excess padding remains in IE 6.
463 */
464
465 input[type="checkbox"],
466 input[type="radio"] {
467 box-sizing: border-box; /* 1 */
468 padding: 0; /* 2 */
469 *height: 13px; /* 3 */
470 *width: 13px; /* 3 */
471 }
472
473 /**
474 * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
475 * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
476 * (include `-moz` to future-proof).
477 */
478
479 input[type="search"] {
480 -webkit-appearance: textfield; /* 1 */
481 -moz-box-sizing: content-box;
482 -webkit-box-sizing: content-box; /* 2 */
483 box-sizing: content-box;
484 }
485
486 /**
487 * Remove inner padding and search cancel button in Safari 5 and Chrome
488 * on OS X.
489 */
490
491 input[type="search"]::-webkit-search-cancel-button,
492 input[type="search"]::-webkit-search-decoration {
493 -webkit-appearance: none;
494 }
495
496 /**
497 * Remove inner padding and border in Firefox 3+.
498 */
499
500 button::-moz-focus-inner,
501 input::-moz-focus-inner {
502 border: 0;
503 padding: 0;
504 }
505
506 /**
507 * 1. Remove default vertical scrollbar in IE 6/7/8/9.
508 * 2. Improve readability and alignment in all browsers.
509 */
510
511 textarea {
512 overflow: auto; /* 1 */
513 vertical-align: top; /* 2 */
514 }
515
516 /* ==========================================================================
517 Tables
518 ========================================================================== */
519
520 /**
521 * Remove most spacing between table cells.
522 */
523
524 table {
525 border-collapse: collapse;
526 border-spacing: 0;
527 }
1 /*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}
...\ No newline at end of file ...\ No newline at end of file
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title>Playlist Switching Simulator</title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15 <link rel="stylesheet" href="../../node_modules/video.js/dist/video-js/video-js.css">
16
17 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
18 </head>
19 <body>
20 <!--[if lt IE 7]>
21 <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
22 <![endif]-->
23
24 <div class="header-container">
25 <header class="wrapper clearfix">
26 <h1 class="title">Playlist Switching Simulator</h1>
27 </header>
28 </div>
29
30 <div class="main-container">
31 <div class="main wrapper clearfix">
32
33 <article>
34 <header>
35 <p>
36 You can use this page to visualize the behavior of the
37 playlist switcher under different network conditions. It
38 can helpful to understand how tweaks to the switching
39 logic will affect playback.
40 </p>
41 <p>
42 Flash security restrictions prevent this page from running
43 over the file protocol. Run <code>grunt connect</code> and
44 then <a href="http://localhost:9999/test/switcher">reload.</a>
45 </p>
46 </header>
47 <section>
48 <h2>Timeline</h2>
49 <p class=timeline>Simulation results will show up here</p>
50 <button id=run-simulation type=submit>Run Simulation</button>
51 </section>
52 <section>
53 <h2>Parameters</h2>
54 <form id=parameters>
55 <ol class=network-timeline>
56 <li>
57 <label>
58 After <input name=time0 class=time type=number value=0 min=0> seconds,
59 </label>
60 <label>
61 the link capacity is <input name=bandwidth0 class=bandwidth type=number value=921600> bits per second
62 </label>
63 </li>
64 <li>
65 <label>
66 After <input name=time1 class=time type=number value=150 min=0> seconds,
67 </label>
68 <label>
69 the link capacity is <input name=bandwidth1 class=bandwidth type=number value=450560> bits per second
70 </label>
71 </li>
72 <li>
73 <label>
74 After <input name=time2 class=time type=number value=600 min=0> seconds,
75 </label>
76 <label>
77 the link capacity is <input name=bandwidth2 class=bandwidth type=number value=1843200> bits per second
78 </label>
79 </li>
80 </ol>
81 <p>
82 You can simulate more complex network conditions by
83 changing the bandwidth during the run. Start a new time
84 period and then define its starting time and link
85 capacity.
86 <button type=button class=add-time-period>Add time period</button>
87 </p>
88 The video is available at
89 <ul>
90 <li><input class=bitrate type=number min=1 value=65536> bits per second</li>
91 <li><input class=bitrate type=number min=1 value=153600> bits per second</li>
92 <li><input class=bitrate type=number min=1 value=450560> bits per second</li>
93 <li><input class=bitrate type=number min=1 value=921600> bits per second</li>
94 <li><input class=bitrate type=number min=1 value=1536000> bits per second</li>
95 </ul>
96 </form>
97 </section>
98 </article>
99
100 </div> <!-- #main -->
101 </div> <!-- #main-container -->
102
103 <div class="footer-container">
104 <footer class="wrapper">
105 <h3>videojs-contrib-hls</h3>
106 </footer>
107 </div>
108 <div id=fixture></div>
109 <script src="../../node_modules/sinon/lib/sinon.js"></script>
110 <script src="../../node_modules/sinon/lib/sinon/util/event.js"></script>
111 <script src="../../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js"></script>
112 <script src="../../node_modules/sinon/lib/sinon/util/xhr_ie.js"></script>
113 <script src="../../node_modules/sinon/lib/sinon/util/fake_timers.js"></script>
114 <script src="js/vendor/d3.min.js"></script>
115
116 <script src="../../node_modules/video.js/dist/video-js/video.js"></script>
117 <script src="../../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
118 <script src="../../src/videojs-hls.js"></script>
119 <script src="../../src/stream.js"></script>
120 <script src="../../src/m3u8/m3u8-parser.js"></script>
121 <script src="../../src/playlist-loader.js"></script>
122
123 <script src="js/switcher.js"></script>
124 </body>
125 </html>
1 (function(window, document) {
2 'use strict';
3 var segmentDuration = 9, // seconds
4 segmentCount = 100,
5 duration = segmentDuration * segmentCount,
6 propagationDelay = 1,
7
8 runSimulation,
9 playlistResponse,
10 player,
11 runButton,
12 parameters,
13 addTimePeriod,
14 networkTimeline,
15 timePeriod,
16 timeline,
17
18 displayTimeline;
19
20 // mock out the environment and dependencies
21 videojs.options.flash.swf = '../../node_modules/video.js/dist/video-js/video-js.swf';
22 videojs.Hls.SegmentParser = function() {
23 this.getFlvHeader = function() {
24 return new Uint8Array([]);
25 };
26 this.parseSegmentBinaryData = function() {};
27 this.flushTags = function() {};
28 this.tagsAvailable = function() {
29 return false;
30 };
31 };
32
33 // a dynamic number of time-bandwidth pairs may be defined to drive the simulation
34 addTimePeriod = document.querySelector('.add-time-period');
35 networkTimeline = document.querySelector('.network-timeline');
36 timePeriod = networkTimeline.cloneNode(true);
37 addTimePeriod.addEventListener('click', function() {
38 var clone = timePeriod.cloneNode(true),
39 fragment = document.createDocumentFragment(),
40 count = networkTimeline.querySelectorAll('input.bandwidth').length,
41 time = clone.querySelector('.time'),
42 bandwidth = clone.querySelector('input.bandwidth');
43
44 time.name = 'time' + count;
45 bandwidth.name = 'bandwidth' + count;
46 while (clone.childNodes.length) {
47 fragment.appendChild(clone.childNodes[0]);
48 }
49 networkTimeline.appendChild(fragment);
50 });
51
52 // collect the simulation parameters
53 parameters = function() {
54 var times = Array.prototype.slice.call(document.querySelectorAll('.time')),
55 bandwidths = document.querySelectorAll('input.bandwidth'),
56 playlists = Array.prototype.slice.call(document.querySelectorAll('input.bitrate'));
57
58 return {
59 playlists: playlists.map(function(input) {
60 return +input.value;
61 }),
62 bandwidths: times.reduce(function(conditions, time, i) {
63 return conditions.concat({
64 time: +time.value,
65 bandwidth: +bandwidths[i].value
66 });
67 }, [])
68 };
69 };
70
71 // send a mock playlist response
72 playlistResponse = function(bitrate) {
73 var i = segmentCount,
74 response = '#EXTM3U\n';
75
76 while (i--) {
77 response += '#EXTINF:' + segmentDuration + ',\n';
78 response += bitrate + '-' + (segmentCount - i) + '\n';
79 }
80 response += '#EXT-X-ENDLIST\n';
81
82 return response;
83 };
84
85 // run the simulation
86 runSimulation = function(options, done) {
87 var results = {
88 bandwidth: [],
89 playlists: [],
90 buffered: [],
91 options: options
92 },
93 bandwidths = options.bandwidths,
94 fixture = document.getElementById('fixture'),
95
96 realSetTimeout = window.setTimeout,
97 clock,
98 fakeXhr,
99 requests,
100 video,
101 t = 0,
102 i = 0;
103
104 // clean up the last run if necessary
105 if (player) {
106 player.dispose();
107 };
108
109 // mock out the environment
110 clock = sinon.useFakeTimers();
111 fakeXhr = sinon.useFakeXMLHttpRequest();
112 requests = [];
113 fakeXhr.onCreate = function(xhr) {
114 xhr.startTime = +new Date();
115 xhr.delivered = 0;
116 requests.push(xhr);
117 };
118
119 // initialize the HLS tech
120 fixture.innerHTML = '';
121 video = document.createElement('video');
122 video.className = 'video-js vjs-default-skin';
123 video.controls = true;
124 fixture.appendChild(video);
125 player = videojs(video, {
126 techOrder: ['hls'],
127 sources: [{
128 src: 'http://example.com/master.m3u8',
129 type: 'application/x-mpegurl'
130 }]
131 });
132
133 player.ready(function() {
134 // run next tick so that Flash doesn't swallow exceptions
135 realSetTimeout(function() {
136 var master = '#EXTM3U\n' +
137 options.playlists.reduce(function(playlists, value) {
138 return playlists +
139 '#EXT-X-STREAM-INF:BANDWIDTH=' + value + '\n' +
140 'playlist-' + value + '\n';
141 }, ''),
142 buffered = 0,
143 currentTime = 0;
144
145 // mock out buffered and currentTime
146 player.buffered = function() {
147 return videojs.createTimeRange(0, currentTime + buffered);
148 };
149 player.currentTime = function() {
150 return currentTime;
151 };
152
153 // respond to the playlist requests
154 requests.shift().respond(200, null, master);
155 requests[0].respond(200, null, playlistResponse(+requests[0].url.match(/\d+$/)));
156 requests.shift();
157
158 bandwidths.sort(function(left, right) {
159 return left.time - right.time;
160 });
161
162 // pre-calculate the bandwidth at each second
163 for (t = i = 0; t < duration; t++) {
164 while (bandwidths[i + 1] && bandwidths[i + 1].time <= t) {
165 i++;
166 }
167 results.bandwidth.push({
168 time: t,
169 bandwidth: bandwidths[i].bandwidth
170 });
171 }
172
173 // advance time and collect simulation results
174 for (t = 0; t < duration; clock.tick(1000), t++) {
175 // schedule response deliveries
176 while (requests.length) {
177 (function(request) {
178 var segmentSize;
179
180 // playlist responses
181 if (/playlist-\d+$/.test(request.url)) {
182 // for simplicity, playlist responses have zero trasmission time
183 return setTimeout(function() {
184 request.respond(200, null, playlistResponse(+request.url.match(/\d+$/)));
185 }, propagationDelay * 1000);
186 }
187
188 // segment responses
189 segmentSize = +request.url.match(/(\d+)-\d+$/)[1] * segmentDuration;
190 // segment response headers arrive after the propogation delay
191 setTimeout(function() {
192 var arrival = Math.ceil(+new Date() * 0.001);
193 results.playlists.push({
194 time: arrival,
195 bitrate: +request.url.match(/(\d+)-\d+$/)[1]
196 });
197 request.setResponseHeaders({
198 'Content-Type': 'video/mp2t'
199 });
200
201 results.bandwidth.slice(arrival).every(function(value, i) {
202 var remaining = segmentSize - request.delivered;
203 if (remaining - value.bandwidth <= 0) {
204 // send the response body once all bytes have been delivered
205 setTimeout(function() {
206 buffered += segmentDuration;
207 request.status = 200;
208 request.response = new Uint8Array(segmentSize * 0.125);
209 request.setResponseBody('');
210 }, ((remaining / value.bandwidth) + i) * 1000);
211 return false;
212 }
213 // record the bits for this tick
214 request.delivered += value.bandwidth;
215 return true;
216 });
217 }, propagationDelay * 1000);
218 })(requests.shift());
219 }
220
221 results.buffered.push({
222 time: t,
223 buffered: buffered
224 });
225
226 // simulate playback
227 if (buffered > 0) {
228 buffered--;
229 currentTime++;
230 }
231 player.trigger('timeupdate');
232 }
233
234 // restore the environment
235 clock.restore();
236 fakeXhr.restore();
237
238 done(null, results);
239 }, 0);
240 });
241 };
242 runButton = document.getElementById('run-simulation');
243 runButton.addEventListener('click', function() {
244 runSimulation(parameters(), displayTimeline);
245 });
246
247 // render the timeline with d3
248 timeline = document.querySelector('.timeline');
249 timeline.innerHTML = '';
250 (function() {
251 var margin = {
252 top: 20,
253 right: 80,
254 bottom: 30,
255 left: 50
256 },
257 width = 960 - margin.left - margin.right,
258 height = 500 - margin.top - margin.bottom,
259 svg;
260 svg = d3.select('.timeline').append('svg')
261 .attr('width', width + margin.left + margin.right)
262 .attr('height', height + margin.top + margin.bottom)
263 .append('g')
264 .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
265
266 displayTimeline = function(error, data) {
267 var x = d3.scale.linear().range([0, width]),
268 y = d3.scale.linear().range([height, 0]),
269 y0 = d3.scale.linear().range([height, 0]),
270
271 timeAxis = d3.svg.axis().scale(x).orient('bottom'),
272 tickFormatter = d3.format(',.0f'),
273 bitrateAxis = d3.svg.axis()
274 .scale(y)
275 .tickFormat(function(value) {
276 return tickFormatter(value / 1024);
277 })
278 .orient('left'),
279
280 bandwidthLine = d3.svg.line()
281 .interpolate('basis')
282 .x(function(data) {
283 return x(data.time);
284 })
285 .y(function(data) {
286 return y(data.bandwidth);
287 }),
288 bufferedLine = d3.svg.line()
289 .interpolate('basis')
290 .x(function(data) {
291 return x(data.time);
292 })
293 .y(function(data) {
294 return y0(data.buffered);
295 });
296
297 x.domain(d3.extent(data.bandwidth, function(data) {
298 return data.time;
299 }));
300 y.domain([0, Math.max(d3.max(data.bandwidth, function(data) {
301 return data.bandwidth;
302 }), d3.max(data.options.playlists))]);
303 y0.domain([0, d3.max(data.buffered, function(data) {
304 return data.buffered;
305 })]);
306
307 // time axis
308 svg.selectAll('.axis').remove();
309 svg.append('g')
310 .attr('class', 'x axis')
311 .attr('transform', 'translate(0,' + height + ')')
312 .call(timeAxis);
313
314 // bitrate axis
315 svg.append('g')
316 .attr('class', 'y axis')
317 .call(bitrateAxis)
318 .append('text')
319 .attr('transform', 'rotate(-90)')
320 .attr('y', 6)
321 .attr('dy', '.71em')
322 .style('text-anchor', 'end')
323 .text('Bitrate (kb/s)');
324
325 // playlist bitrate lines
326 svg.selectAll('.line.bitrate')
327 .data(data.options.playlists)
328 .enter().append('path')
329 .attr('class', 'line bitrate')
330 .attr('d', function(playlist) {
331 return 'M0,' + y(playlist) + 'L' + width + ',' + y(playlist);
332 });
333
334 // bandwidth line
335 svg.selectAll('.bandwidth').remove();
336 svg.append('path')
337 .datum(data.bandwidth)
338 .attr('class', 'line bandwidth')
339 .attr('d', bandwidthLine);
340
341 svg.append('text')
342 .attr('class', 'bandwidth label')
343 .attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.bandwidth.slice(-1)[0].bandwidth) + ')')
344 .attr('dy', '1.35em')
345 .text('bandwidth');
346
347 // buffered line
348 svg.selectAll('.buffered').remove();
349 svg.append('path')
350 .datum(data.buffered)
351 .attr('class', 'line buffered')
352 .attr('y', 6)
353 .attr('d', bufferedLine);
354
355 // segment bitrate dots
356 svg.selectAll('.segment-bitrate').remove();
357 svg.selectAll('.segment-bitrate')
358 .data(data.playlists)
359 .enter().append('circle')
360 .attr('class', 'dot segment-bitrate')
361 .attr('r', 3.5)
362 .attr('cx', function(playlist) {
363 return x(playlist.time);
364 })
365 .attr('cy', function(playlist) {
366 return y(playlist.bitrate);
367 });
368 };
369 })();
370
371 runSimulation(parameters(), displayTimeline);
372
373 })(window, document);
1 Copyright (c) 2010-2014, Michael Bostock
2 All rights reserved.
3
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6
7 * Redistributions of source code must retain the above copyright notice, this
8 list of conditions and the following disclaimer.
9
10 * Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 * The name Michael Bostock may not be used to endorse or promote products
15 derived from this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
21 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
No preview for this file type
1 /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
3 */
4 ;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};