got the netowrk-switcher working (#766)
Showing
16 changed files
with
444 additions
and
1084 deletions
... | @@ -40,9 +40,10 @@ | ... | @@ -40,9 +40,10 @@ |
40 | "version": "npm run build", | 40 | "version": "npm run build", |
41 | "watch": "npm-run-all -p watch:*", | 41 | "watch": "npm-run-all -p watch:*", |
42 | "watch:docs": "nodemon --watch src/ --exec npm run docs", | 42 | "watch:docs": "nodemon --watch src/ --exec npm run docs", |
43 | "watch:js": "npm-run-all -p watch:js:babel watch:js:browserify", | 43 | "watch:js": "npm-run-all -p watch:js:babel watch:js:browserify watch:js:switcher", |
44 | "watch:js:babel": "npm run build:js:babel -- --watch", | 44 | "watch:js:babel": "npm run build:js:babel -- --watch", |
45 | "watch:js:browserify": "watchify . -v -g browserify-shim -o dist/videojs-contrib-hls.js", | 45 | "watch:js:browserify": "watchify . -v -g browserify-shim -o dist/videojs-contrib-hls.js", |
46 | "watch:js:switcher": "watchify utils/switcher/switcher.js -v -t babelify -g browserify-shim -o dist/switcher.js", | ||
46 | "watch:test": "npm-run-all -p watch:test:*", | 47 | "watch:test": "npm-run-all -p watch:test:*", |
47 | "watch:test:js": "node scripts/watch-test.js", | 48 | "watch:test:js": "node scripts/watch-test.js", |
48 | "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", | 49 | "watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"", |
... | @@ -98,6 +99,7 @@ | ... | @@ -98,6 +99,7 @@ |
98 | "browserify-shim": "^3.0.0", | 99 | "browserify-shim": "^3.0.0", |
99 | "connect": "^3.4.0", | 100 | "connect": "^3.4.0", |
100 | "cowsay": "^1.1.0", | 101 | "cowsay": "^1.1.0", |
102 | "d3": "3.4.8", | ||
101 | "doctoc": "^0.15.0", | 103 | "doctoc": "^0.15.0", |
102 | "glob": "^6.0.3", | 104 | "glob": "^6.0.3", |
103 | "jsdoc": "^3.4.0", | 105 | "jsdoc": "^3.4.0", | ... | ... |
... | @@ -117,7 +117,9 @@ let fakeEnvironment = { | ... | @@ -117,7 +117,9 @@ let fakeEnvironment = { |
117 | this.xhr.restore(); | 117 | this.xhr.restore(); |
118 | ['warn', 'error'].forEach((level) => { | 118 | ['warn', 'error'].forEach((level) => { |
119 | if (this.log && this.log[level] && this.log[level].restore) { | 119 | if (this.log && this.log[level] && this.log[level].restore) { |
120 | if (QUnit) { | ||
120 | QUnit.equal(this.log[level].callCount, 0, `no unexpected logs on ${level}`); | 121 | QUnit.equal(this.log[level].callCount, 0, `no unexpected logs on ${level}`); |
122 | } | ||
121 | this.log[level].restore(); | 123 | this.log[level].restore(); |
122 | } | 124 | } |
123 | }); | 125 | }); |
... | @@ -256,7 +258,7 @@ export const openMediaSource = function(player, clock) { | ... | @@ -256,7 +258,7 @@ export const openMediaSource = function(player, clock) { |
256 | }); | 258 | }); |
257 | }; | 259 | }; |
258 | 260 | ||
259 | export const standardXHRResponse = function(request) { | 261 | export const standardXHRResponse = function(request, data) { |
260 | if (!request.url) { | 262 | if (!request.url) { |
261 | return; | 263 | return; |
262 | } | 264 | } |
... | @@ -277,9 +279,12 @@ export const standardXHRResponse = function(request) { | ... | @@ -277,9 +279,12 @@ export const standardXHRResponse = function(request) { |
277 | contentType = 'video/MP2T'; | 279 | contentType = 'video/MP2T'; |
278 | } | 280 | } |
279 | 281 | ||
282 | if (!data) { | ||
283 | data = testDataManifests[manifestName]; | ||
284 | } | ||
285 | |||
280 | request.response = new Uint8Array(16).buffer; | 286 | request.response = new Uint8Array(16).buffer; |
281 | request.respond(200, { 'Content-Type': contentType }, | 287 | request.respond(200, {'Content-Type': contentType}, data); |
282 | testDataManifests[manifestName]); | ||
283 | }; | 288 | }; |
284 | 289 | ||
285 | // return an absolute version of a page-relative URL | 290 | // return an absolute version of a page-relative URL | ... | ... |
utils/switcher/css/normalize.css
deleted
100644 → 0
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 | } |
utils/switcher/css/normalize.min.css
deleted
100644 → 0
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 |
utils/switcher/display-timeline.js
0 → 100644
1 | // render the timeline with d3 | ||
2 | let timeline = document.querySelector('.timeline'); | ||
3 | timeline.innerHTML = ''; | ||
4 | |||
5 | let margin = { | ||
6 | top: 20, | ||
7 | right: 80, | ||
8 | bottom: 30, | ||
9 | left: 50 | ||
10 | }; | ||
11 | let width = 960 - margin.left - margin.right; | ||
12 | let height = 500 - margin.top - margin.bottom; | ||
13 | let svg = d3.select('.timeline').append('svg') | ||
14 | .attr('width', width + margin.left + margin.right) | ||
15 | .attr('height', height + margin.top + margin.bottom) | ||
16 | .append('g') | ||
17 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | ||
18 | |||
19 | |||
20 | const displayTimeline = function(error, data) { | ||
21 | var x = d3.scale.linear().range([0, width]), | ||
22 | y = d3.scale.linear().range([height, 0]), | ||
23 | |||
24 | timeAxis = d3.svg.axis().scale(x).orient('bottom'), | ||
25 | tickFormatter = d3.format(',.0f'), | ||
26 | bitrateAxis = d3.svg.axis() | ||
27 | .scale(y) | ||
28 | .tickFormat(function(value) { | ||
29 | return tickFormatter(value / 1024); | ||
30 | }) | ||
31 | .orient('left'), | ||
32 | |||
33 | bandwidthLine = d3.svg.line() | ||
34 | .interpolate('basis') | ||
35 | .x(function(data) { | ||
36 | return x(data.time); | ||
37 | }) | ||
38 | .y(function(data) { | ||
39 | return y(data.bandwidth); | ||
40 | }), | ||
41 | effectiveBandwidthLine = d3.svg.line() | ||
42 | .interpolate('basis') | ||
43 | .x(function(data) { | ||
44 | return x(data.time); | ||
45 | }) | ||
46 | .y(function(data) { | ||
47 | return y(data.bandwidth); | ||
48 | }); | ||
49 | |||
50 | x.domain(d3.extent(data.bandwidth, function(data) { | ||
51 | return data.time; | ||
52 | })); | ||
53 | y.domain([0, Math.max(d3.max(data.bandwidth, function(data) { | ||
54 | return data.bandwidth; | ||
55 | }), d3.max(data.options.playlists), d3.max(data.playlists, function(data) { | ||
56 | return data.bitrate; | ||
57 | }))]); | ||
58 | |||
59 | // time axis | ||
60 | svg.selectAll('.axis').remove(); | ||
61 | svg.append('g') | ||
62 | .attr('class', 'x axis') | ||
63 | .attr('transform', 'translate(0,' + height + ')') | ||
64 | .call(timeAxis); | ||
65 | |||
66 | // bitrate axis | ||
67 | svg.append('g') | ||
68 | .attr('class', 'y axis') | ||
69 | .call(bitrateAxis) | ||
70 | .append('text') | ||
71 | .attr('transform', 'rotate(-90)') | ||
72 | .attr('y', 6) | ||
73 | .attr('dy', '.71em') | ||
74 | .style('text-anchor', 'end') | ||
75 | .text('Bitrate (kb/s)'); | ||
76 | |||
77 | // playlist bitrate lines | ||
78 | svg.selectAll('.line.bitrate').remove(); | ||
79 | svg.selectAll('.line.bitrate') | ||
80 | .data(data.options.playlists) | ||
81 | .enter().append('path') | ||
82 | .attr('class', 'line bitrate') | ||
83 | .attr('d', function(playlist) { | ||
84 | return 'M0,' + y(playlist) + 'L' + width + ',' + y(playlist); | ||
85 | }); | ||
86 | |||
87 | // bandwidth line | ||
88 | svg.selectAll('.bandwidth').remove(); | ||
89 | svg.append('path') | ||
90 | .datum(data.bandwidth) | ||
91 | .attr('class', 'line bandwidth') | ||
92 | .attr('d', bandwidthLine); | ||
93 | svg.selectAll('.effective-bandwidth').remove(); | ||
94 | svg.append('path') | ||
95 | .datum(data.effectiveBandwidth) | ||
96 | .attr('class', 'line effective-bandwidth') | ||
97 | .attr('d', effectiveBandwidthLine); | ||
98 | |||
99 | svg.append('text') | ||
100 | .attr('class', 'bandwidth label') | ||
101 | .attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.bandwidth.slice(-1)[0].bandwidth) + ')') | ||
102 | .attr('dy', '1.35em') | ||
103 | .text('bandwidth'); | ||
104 | svg.append('text') | ||
105 | .attr('class', 'bandwidth label') | ||
106 | .attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.effectiveBandwidth.slice(-1)[0].bandwidth) + ')') | ||
107 | .attr('dy', '1.35em') | ||
108 | .text('measured'); | ||
109 | |||
110 | // segment bitrate dots | ||
111 | svg.selectAll('.segment-bitrate').remove(); | ||
112 | svg.selectAll('.segment-bitrate') | ||
113 | .data(data.playlists) | ||
114 | .enter().append('circle') | ||
115 | .attr('class', 'dot segment-bitrate') | ||
116 | .attr('r', 3.5) | ||
117 | .attr('cx', function(playlist) { | ||
118 | return x(playlist.time); | ||
119 | }) | ||
120 | .attr('cy', function(playlist) { | ||
121 | return y(playlist.bitrate); | ||
122 | }); | ||
123 | |||
124 | // highlight intervals when the buffer is empty | ||
125 | svg.selectAll('.buffer-empty').remove(); | ||
126 | svg.selectAll('.buffer-empty') | ||
127 | .data(data.buffered.reduce(function(result, sample) { | ||
128 | var last = result[result.length - 1]; | ||
129 | if (sample.buffered === 0) { | ||
130 | if (last && sample.time === last.end + 1) { | ||
131 | // add this sample to the interval we're accumulating | ||
132 | return result.slice(0, result.length - 1).concat({ | ||
133 | start: last.start, | ||
134 | end: sample.time | ||
135 | }); | ||
136 | } else { | ||
137 | // this sample starts a new interval | ||
138 | return result.concat({ | ||
139 | start: sample.time, | ||
140 | end: sample.time | ||
141 | }); | ||
142 | } | ||
143 | } | ||
144 | // filter out time periods where the buffer isn't empty | ||
145 | return result; | ||
146 | }, [])) | ||
147 | .enter().append('rect') | ||
148 | .attr('class', 'buffer-empty') | ||
149 | .attr('x', function(data) { | ||
150 | return x(data.start); | ||
151 | }) | ||
152 | .attr('width', function(data) { | ||
153 | return x(1 + data.end - data.start); | ||
154 | }) | ||
155 | .attr('y', 0) | ||
156 | .attr('height', y(height)); | ||
157 | }; | ||
158 | |||
159 | export default displayTimeline; |
1 | <!DOCTYPE html> | 1 | <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> | 2 | <head> |
7 | <meta charset="utf-8"> | 3 | <meta charset="utf-8"> |
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title>Playlist Switching Simulator</title> | 4 | <title>Playlist Switching Simulator</title> |
10 | <meta name="description" content=""> | 5 | <link rel="stylesheet" href="switcher.css"> |
11 | <meta name="viewport" content="width=device-width"> | 6 | <link rel="stylesheet" href="/node_modules/video.js/dist/video-js.css"> |
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.css"> | ||
16 | |||
17 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
18 | </head> | 7 | </head> |
19 | <body> | 8 | <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"> | 9 | <div class="header-container"> |
25 | <header class="wrapper clearfix"> | 10 | <header class="wrapper clearfix"> |
26 | <h1 class="title">Playlist Switching Simulator</h1> | 11 | <h1 class="title">Playlist Switching Simulator</h1> |
... | @@ -29,7 +14,6 @@ | ... | @@ -29,7 +14,6 @@ |
29 | 14 | ||
30 | <div class="main-container"> | 15 | <div class="main-container"> |
31 | <div class="main wrapper clearfix"> | 16 | <div class="main wrapper clearfix"> |
32 | |||
33 | <article> | 17 | <article> |
34 | <header> | 18 | <header> |
35 | <p> | 19 | <p> |
... | @@ -38,11 +22,6 @@ | ... | @@ -38,11 +22,6 @@ |
38 | can helpful to understand how tweaks to the switching | 22 | can helpful to understand how tweaks to the switching |
39 | logic will affect playback. | 23 | logic will affect playback. |
40 | </p> | 24 | </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> | 25 | </header> |
47 | <section> | 26 | <section> |
48 | <h2>Timeline</h2> | 27 | <h2>Timeline</h2> |
... | @@ -58,7 +37,7 @@ | ... | @@ -58,7 +37,7 @@ |
58 | After <input name=time0 class=time type=number value=0 min=0> seconds, | 37 | After <input name=time0 class=time type=number value=0 min=0> seconds, |
59 | </label> | 38 | </label> |
60 | <label> | 39 | <label> |
61 | the link capacity is <input name=bandwidth0 class=bandwidth type=number value=921600> bits per second | 40 | the link capacity is <input name=bandwidth0 class=bandwidth type=number value=250000> bits per second |
62 | </label> | 41 | </label> |
63 | </li> | 42 | </li> |
64 | <li> | 43 | <li> |
... | @@ -66,7 +45,7 @@ | ... | @@ -66,7 +45,7 @@ |
66 | After <input name=time1 class=time type=number value=150 min=0> seconds, | 45 | After <input name=time1 class=time type=number value=150 min=0> seconds, |
67 | </label> | 46 | </label> |
68 | <label> | 47 | <label> |
69 | the link capacity is <input name=bandwidth1 class=bandwidth type=number value=450560> bits per second | 48 | the link capacity is <input name=bandwidth1 class=bandwidth type=number value=500000> bits per second |
70 | </label> | 49 | </label> |
71 | </li> | 50 | </li> |
72 | <li> | 51 | <li> |
... | @@ -74,7 +53,7 @@ | ... | @@ -74,7 +53,7 @@ |
74 | After <input name=time2 class=time type=number value=600 min=0> seconds, | 53 | After <input name=time2 class=time type=number value=600 min=0> seconds, |
75 | </label> | 54 | </label> |
76 | <label> | 55 | <label> |
77 | the link capacity is <input name=bandwidth2 class=bandwidth type=number value=1843200> bits per second | 56 | the link capacity is <input name=bandwidth2 class=bandwidth type=number value=250000> bits per second |
78 | </label> | 57 | </label> |
79 | </li> | 58 | </li> |
80 | </ol> | 59 | </ol> |
... | @@ -94,44 +73,28 @@ | ... | @@ -94,44 +73,28 @@ |
94 | </p> | 73 | </p> |
95 | The video is available at | 74 | The video is available at |
96 | <ul> | 75 | <ul> |
97 | <li><input class=bitrate type=number min=1 value=312000> bits per second</li> | 76 | <li><input class=bitrate type=number min=1 value=52400> bits per second</li> |
98 | <li><input class=bitrate type=number min=1 value=524000> bits per second</li> | 77 | <li><input class=bitrate type=number min=1 value=129600> bits per second</li> |
99 | <li><input class=bitrate type=number min=1 value=1296000> bits per second</li> | 78 | <li><input class=bitrate type=number min=1 value=212500> bits per second</li> |
100 | <li><input class=bitrate type=number min=1 value=2125000> bits per second</li> | 79 | <li><input class=bitrate type=number min=1 value=312500> bits per second</li> |
101 | <li><input class=bitrate type=number min=1 value=3125000> bits per second</li> | ||
102 | </ul> | 80 | </ul> |
103 | </form> | 81 | </form> |
104 | </section> | 82 | </section> |
105 | </article> | 83 | </article> |
106 | 84 | ||
107 | </div> <!-- #main --> | 85 | </div> |
108 | </div> <!-- #main-container --> | 86 | </div> |
109 | 87 | ||
110 | <div class="footer-container"> | 88 | <div class="footer-container"> |
111 | <footer class="wrapper"> | 89 | <footer class="wrapper"> |
112 | <h3>videojs-contrib-hls</h3> | 90 | <h3>videojs-contrib-hls</h3> |
113 | </footer> | 91 | </footer> |
114 | </div> | 92 | </div> |
115 | <div id=fixture></div> | 93 | <div id="qunit-fixture"></div> |
116 | <script src="../../node_modules/sinon/lib/sinon.js"></script> | 94 | <script src="/node_modules/sinon/pkg/sinon.js"></script> |
117 | <script src="../../node_modules/sinon/lib/sinon/util/event.js"></script> | ||
118 | <script src="../../node_modules/sinon/lib/sinon/util/fake_xml_http_request.js"></script> | ||
119 | <script src="../../node_modules/sinon/lib/sinon/util/xhr_ie.js"></script> | ||
120 | <script src="../../node_modules/sinon/lib/sinon/util/fake_timers.js"></script> | ||
121 | <script src="js/vendor/d3.min.js"></script> | ||
122 | |||
123 | <script src="/node_modules/video.js/dist/video.js"></script> | 95 | <script src="/node_modules/video.js/dist/video.js"></script> |
124 | <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script> | 96 | <script src="/dist/videojs-contrib-hls.js"></script> |
125 | <script src="/node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | 97 | <script src="/node_modules/d3/d3.min.js"></script> |
126 | <script src="/src/videojs-hls.js"></script> | 98 | <script src="/dist/switcher.js"></script> |
127 | <script src="/src/xhr.js"></script> | ||
128 | <script src="/src/stream.js"></script> | ||
129 | <script src="/src/m3u8/m3u8-parser.js"></script> | ||
130 | <script src="/src/playlist.js"></script> | ||
131 | <script src="/src/playlist-loader.js"></script> | ||
132 | <script src="/src/decrypter.js"></script> | ||
133 | <script src="/src/bin-utils.js"></script> | ||
134 | |||
135 | <script src="js/switcher.js"></script> | ||
136 | </body> | 99 | </body> |
137 | </html> | 100 | </html> | ... | ... |
utils/switcher/js/main.js
deleted
100644 → 0
utils/switcher/js/switcher.js
deleted
100644 → 0
1 | (function(window, document) { | ||
2 | 'use strict'; | ||
3 | |||
4 | // the number of seconds of video in each segment | ||
5 | var segmentDuration = 9, // seconds | ||
6 | |||
7 | // the number of segments in the video | ||
8 | segmentCount = 100, | ||
9 | |||
10 | // the length of the simulation | ||
11 | duration = segmentDuration * segmentCount, | ||
12 | |||
13 | // the number of seconds it takes for a single bit to be | ||
14 | // transmitted from the client to the server, or vice-versa | ||
15 | propagationDelay = 0.5, | ||
16 | |||
17 | runSimulation, | ||
18 | playlistResponse, | ||
19 | player, | ||
20 | runButton, | ||
21 | parameters, | ||
22 | timeline, | ||
23 | |||
24 | displayTimeline; | ||
25 | |||
26 | // mock out the environment and dependencies | ||
27 | videojs.options.flash.swf = '../../node_modules/video.js/dist/video-js/video-js.swf'; | ||
28 | videojs.Hls.SegmentParser = function() { | ||
29 | this.getFlvHeader = function() { | ||
30 | return new Uint8Array([]); | ||
31 | }; | ||
32 | this.parseSegmentBinaryData = function() {}; | ||
33 | this.flushTags = function() {}; | ||
34 | this.tagsAvailable = function() { | ||
35 | return false; | ||
36 | }; | ||
37 | this.metadataStream = { | ||
38 | on: function() {} | ||
39 | }; | ||
40 | }; | ||
41 | |||
42 | // a dynamic number of time-bandwidth pairs may be defined to drive the simulation | ||
43 | (function() { | ||
44 | var params, | ||
45 | networkTimeline = document.querySelector('.network-timeline'), | ||
46 | timePeriod = networkTimeline.querySelector('li:last-child').cloneNode(true), | ||
47 | appendTimePeriod = function() { | ||
48 | var clone = timePeriod.cloneNode(true), | ||
49 | count = networkTimeline.querySelectorAll('input.bandwidth').length, | ||
50 | time = clone.querySelector('.time'), | ||
51 | bandwidth = clone.querySelector('input.bandwidth'); | ||
52 | |||
53 | time.name = 'time' + count; | ||
54 | bandwidth.name = 'bandwidth' + count; | ||
55 | networkTimeline.appendChild(clone); | ||
56 | }; | ||
57 | document.querySelector('.add-time-period') | ||
58 | .addEventListener('click', appendTimePeriod); | ||
59 | |||
60 | // apply any simulation parameters that were set in the fragment identifier | ||
61 | if (!window.location.hash) { | ||
62 | return; | ||
63 | } | ||
64 | |||
65 | // time periods are specified as t<seconds>=<bitrate> | ||
66 | // e.g. #t15=450560&t150=65530 | ||
67 | params = window.location.hash.substring(1) | ||
68 | .split('&') | ||
69 | .map(function(param) { | ||
70 | return ((/t(\d+)=(\d+)/i).exec(param) || []) | ||
71 | .map(window.parseFloat).slice(1); | ||
72 | }).filter(function(pair) { | ||
73 | return pair.length === 2; | ||
74 | }); | ||
75 | |||
76 | networkTimeline.innerHTML = ''; | ||
77 | params.forEach(function(param) { | ||
78 | appendTimePeriod(); | ||
79 | networkTimeline.querySelector('li:last-child .time').value = param[0]; | ||
80 | networkTimeline.querySelector('li:last-child input.bandwidth').value = param[1]; | ||
81 | }); | ||
82 | })(); | ||
83 | |||
84 | // collect the simulation parameters | ||
85 | parameters = function() { | ||
86 | var times = Array.prototype.slice.call(document.querySelectorAll('.time')), | ||
87 | bandwidths = document.querySelectorAll('input.bandwidth'), | ||
88 | playlists = Array.prototype.slice.call(document.querySelectorAll('input.bitrate')); | ||
89 | |||
90 | return { | ||
91 | playlists: playlists.map(function(input) { | ||
92 | return +input.value; | ||
93 | }), | ||
94 | bandwidths: times.reduce(function(conditions, time, i) { | ||
95 | return conditions.concat({ | ||
96 | time: +time.value, | ||
97 | bandwidth: +bandwidths[i].value | ||
98 | }); | ||
99 | }, []) | ||
100 | }; | ||
101 | }; | ||
102 | |||
103 | // send a mock playlist response | ||
104 | playlistResponse = function(bitrate) { | ||
105 | var i = segmentCount, | ||
106 | response = '#EXTM3U\n'; | ||
107 | |||
108 | while (i--) { | ||
109 | response += '#EXTINF:' + segmentDuration + ',\n'; | ||
110 | response += bitrate + '-' + (segmentCount - i) + '\n'; | ||
111 | } | ||
112 | response += '#EXT-X-ENDLIST\n'; | ||
113 | |||
114 | return response; | ||
115 | }; | ||
116 | |||
117 | // run the simulation | ||
118 | runSimulation = function(options, done) { | ||
119 | var results = { | ||
120 | bandwidth: [], | ||
121 | effectiveBandwidth: [], | ||
122 | playlists: [], | ||
123 | buffered: [], | ||
124 | options: options | ||
125 | }, | ||
126 | bandwidths = options.bandwidths, | ||
127 | fixture = document.getElementById('fixture'), | ||
128 | |||
129 | realSetTimeout = window.setTimeout, | ||
130 | clock, | ||
131 | fakeXhr, | ||
132 | requests, | ||
133 | video, | ||
134 | t = 0, | ||
135 | i = 0; | ||
136 | |||
137 | // clean up the last run if necessary | ||
138 | if (player) { | ||
139 | player.dispose(); | ||
140 | }; | ||
141 | |||
142 | // mock out the environment | ||
143 | clock = sinon.useFakeTimers(); | ||
144 | fakeXhr = sinon.useFakeXMLHttpRequest(); | ||
145 | videojs.xhr.XMLHttpRequest = fakeXhr; | ||
146 | requests = []; | ||
147 | fakeXhr.onCreate = function(xhr) { | ||
148 | xhr.startTime = +new Date(); | ||
149 | xhr.delivered = 0; | ||
150 | requests.push(xhr); | ||
151 | }; | ||
152 | |||
153 | // initialize the HLS tech | ||
154 | fixture.innerHTML = ''; | ||
155 | video = document.createElement('video'); | ||
156 | video.className = 'video-js vjs-default-skin'; | ||
157 | video.controls = true; | ||
158 | fixture.appendChild(video); | ||
159 | player = videojs(video, { | ||
160 | sources: [{ | ||
161 | src: 'http://example.com/master.m3u8', | ||
162 | type: 'application/x-mpegurl' | ||
163 | }] | ||
164 | }); | ||
165 | |||
166 | player.ready(function() { | ||
167 | // run next tick so that Flash doesn't swallow exceptions | ||
168 | realSetTimeout(function() { | ||
169 | var master = '#EXTM3U\n' + | ||
170 | options.playlists.reduce(function(playlists, value) { | ||
171 | return playlists + | ||
172 | '#EXT-X-STREAM-INF:BANDWIDTH=' + value + '\n' + | ||
173 | 'playlist-' + value + '\n'; | ||
174 | }, ''), | ||
175 | buffered = 0, | ||
176 | currentTime = 0; | ||
177 | |||
178 | // simulate buffered and currentTime during playback | ||
179 | player.buffered = function() { | ||
180 | return videojs.createTimeRange(0, currentTime + buffered); | ||
181 | }; | ||
182 | player.currentTime = function() { | ||
183 | return currentTime; | ||
184 | }; | ||
185 | |||
186 | bandwidths.sort(function(left, right) { | ||
187 | return left.time - right.time; | ||
188 | }); | ||
189 | |||
190 | // respond to the playlist requests | ||
191 | requests[0].bandwidth = bandwidths[0].bandwidth; | ||
192 | requests.shift().respond(200, null, master); | ||
193 | requests[0].bandwidth = bandwidths[0].bandwidth; | ||
194 | requests[0].respond(200, null, playlistResponse(+requests[0].url.match(/\d+$/))); | ||
195 | requests.shift(); | ||
196 | |||
197 | // record the measured bandwidth for the playlist requests | ||
198 | results.effectiveBandwidth.push({ | ||
199 | time: 0, | ||
200 | bandwidth: player.hls.bandwidth | ||
201 | }); | ||
202 | |||
203 | // pre-calculate the bandwidth at each second | ||
204 | for (t = i = 0; t < duration; t++) { | ||
205 | while (bandwidths[i + 1] && bandwidths[i + 1].time <= t) { | ||
206 | i++; | ||
207 | } | ||
208 | results.bandwidth.push({ | ||
209 | time: t, | ||
210 | bandwidth: bandwidths[i].bandwidth | ||
211 | }); | ||
212 | } | ||
213 | |||
214 | // advance time and collect simulation results | ||
215 | for (t = 0; t < duration; clock.tick(1000), t++) { | ||
216 | // schedule response deliveries | ||
217 | while (requests.length) { | ||
218 | (function(request) { | ||
219 | var segmentSize; | ||
220 | |||
221 | // playlist responses | ||
222 | if (/playlist-\d+$/.test(request.url)) { | ||
223 | // for simplicity, playlist responses have zero trasmission time | ||
224 | return setTimeout(function() { | ||
225 | request.respond(200, null, playlistResponse(+request.url.match(/\d+$/))); | ||
226 | }, propagationDelay * 1000); | ||
227 | } | ||
228 | |||
229 | // segment responses | ||
230 | segmentSize = +request.url.match(/(\d+)-\d+$/)[1] * segmentDuration; | ||
231 | // segment response headers arrive after the propogation delay | ||
232 | setTimeout(function() { | ||
233 | var arrival = Math.ceil(+new Date() * 0.001); | ||
234 | request.setResponseHeaders({ | ||
235 | 'Content-Type': 'video/mp2t' | ||
236 | }); | ||
237 | |||
238 | results.bandwidth.slice(arrival).every(function(value, i) { | ||
239 | var remaining = segmentSize - request.delivered; | ||
240 | if (remaining - value.bandwidth <= 0) { | ||
241 | // send the response body once all bytes have been delivered | ||
242 | setTimeout(function() { | ||
243 | var time = Math.ceil(+new Date() * 0.001); | ||
244 | if (request.aborted) { | ||
245 | return; | ||
246 | } | ||
247 | request.status = 200; | ||
248 | request.response = new Uint8Array(segmentSize * 0.125); | ||
249 | request.setResponseBody(''); | ||
250 | |||
251 | results.playlists.push({ | ||
252 | time: time, | ||
253 | bitrate: +request.url.match(/(\d+)-\d+$/)[1] | ||
254 | }); | ||
255 | // update the buffered value | ||
256 | buffered += segmentDuration; | ||
257 | results.buffered[results.buffered.length - 1].buffered = buffered; | ||
258 | results.effectiveBandwidth.push({ | ||
259 | time: time, | ||
260 | bandwidth: player.hls.bandwidth | ||
261 | }); | ||
262 | }, ((remaining / value.bandwidth) + i) * 1000); | ||
263 | return false; | ||
264 | } | ||
265 | // record the bits for this tick | ||
266 | request.delivered += value.bandwidth; | ||
267 | return true; | ||
268 | }); | ||
269 | }, propagationDelay * 1000); | ||
270 | })(requests.shift()); | ||
271 | } | ||
272 | |||
273 | results.buffered.push({ | ||
274 | time: t, | ||
275 | buffered: buffered | ||
276 | }); | ||
277 | |||
278 | // simulate playback | ||
279 | if (buffered > 0) { | ||
280 | buffered--; | ||
281 | currentTime++; | ||
282 | } | ||
283 | player.trigger('timeupdate'); | ||
284 | } | ||
285 | |||
286 | // restore the environment | ||
287 | clock.restore(); | ||
288 | fakeXhr.restore(); | ||
289 | |||
290 | // update the fragment identifier so this scenario can be re-run easily | ||
291 | window.location.hash = '#' + options.bandwidths.map(function(interval) { | ||
292 | return 't' + interval.time + '=' + interval.bandwidth; | ||
293 | }).join('&'); | ||
294 | |||
295 | done(null, results); | ||
296 | }, 0); | ||
297 | }); | ||
298 | /// trigger the ready function through set timeout | ||
299 | clock.tick(1); | ||
300 | }; | ||
301 | runButton = document.getElementById('run-simulation'); | ||
302 | runButton.addEventListener('click', function() { | ||
303 | runSimulation(parameters(), displayTimeline); | ||
304 | }); | ||
305 | |||
306 | // render the timeline with d3 | ||
307 | timeline = document.querySelector('.timeline'); | ||
308 | timeline.innerHTML = ''; | ||
309 | (function() { | ||
310 | var margin = { | ||
311 | top: 20, | ||
312 | right: 80, | ||
313 | bottom: 30, | ||
314 | left: 50 | ||
315 | }, | ||
316 | width = 960 - margin.left - margin.right, | ||
317 | height = 500 - margin.top - margin.bottom, | ||
318 | svg; | ||
319 | svg = d3.select('.timeline').append('svg') | ||
320 | .attr('width', width + margin.left + margin.right) | ||
321 | .attr('height', height + margin.top + margin.bottom) | ||
322 | .append('g') | ||
323 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | ||
324 | |||
325 | displayTimeline = function(error, data) { | ||
326 | var x = d3.scale.linear().range([0, width]), | ||
327 | y = d3.scale.linear().range([height, 0]), | ||
328 | |||
329 | timeAxis = d3.svg.axis().scale(x).orient('bottom'), | ||
330 | tickFormatter = d3.format(',.0f'), | ||
331 | bitrateAxis = d3.svg.axis() | ||
332 | .scale(y) | ||
333 | .tickFormat(function(value) { | ||
334 | return tickFormatter(value / 1024); | ||
335 | }) | ||
336 | .orient('left'), | ||
337 | |||
338 | bandwidthLine = d3.svg.line() | ||
339 | .interpolate('basis') | ||
340 | .x(function(data) { | ||
341 | return x(data.time); | ||
342 | }) | ||
343 | .y(function(data) { | ||
344 | return y(data.bandwidth); | ||
345 | }), | ||
346 | effectiveBandwidthLine = d3.svg.line() | ||
347 | .interpolate('basis') | ||
348 | .x(function(data) { | ||
349 | return x(data.time); | ||
350 | }) | ||
351 | .y(function(data) { | ||
352 | return y(data.bandwidth); | ||
353 | }); | ||
354 | |||
355 | x.domain(d3.extent(data.bandwidth, function(data) { | ||
356 | return data.time; | ||
357 | })); | ||
358 | y.domain([0, Math.max(d3.max(data.bandwidth, function(data) { | ||
359 | return data.bandwidth; | ||
360 | }), d3.max(data.options.playlists), d3.max(data.playlists, function(data) { | ||
361 | return data.bitrate; | ||
362 | }))]); | ||
363 | |||
364 | // time axis | ||
365 | svg.selectAll('.axis').remove(); | ||
366 | svg.append('g') | ||
367 | .attr('class', 'x axis') | ||
368 | .attr('transform', 'translate(0,' + height + ')') | ||
369 | .call(timeAxis); | ||
370 | |||
371 | // bitrate axis | ||
372 | svg.append('g') | ||
373 | .attr('class', 'y axis') | ||
374 | .call(bitrateAxis) | ||
375 | .append('text') | ||
376 | .attr('transform', 'rotate(-90)') | ||
377 | .attr('y', 6) | ||
378 | .attr('dy', '.71em') | ||
379 | .style('text-anchor', 'end') | ||
380 | .text('Bitrate (kb/s)'); | ||
381 | |||
382 | // playlist bitrate lines | ||
383 | svg.selectAll('.line.bitrate').remove(); | ||
384 | svg.selectAll('.line.bitrate') | ||
385 | .data(data.options.playlists) | ||
386 | .enter().append('path') | ||
387 | .attr('class', 'line bitrate') | ||
388 | .attr('d', function(playlist) { | ||
389 | return 'M0,' + y(playlist) + 'L' + width + ',' + y(playlist); | ||
390 | }); | ||
391 | |||
392 | // bandwidth line | ||
393 | svg.selectAll('.bandwidth').remove(); | ||
394 | svg.append('path') | ||
395 | .datum(data.bandwidth) | ||
396 | .attr('class', 'line bandwidth') | ||
397 | .attr('d', bandwidthLine); | ||
398 | svg.selectAll('.effective-bandwidth').remove(); | ||
399 | svg.append('path') | ||
400 | .datum(data.effectiveBandwidth) | ||
401 | .attr('class', 'line effective-bandwidth') | ||
402 | .attr('d', effectiveBandwidthLine); | ||
403 | |||
404 | svg.append('text') | ||
405 | .attr('class', 'bandwidth label') | ||
406 | .attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.bandwidth.slice(-1)[0].bandwidth) + ')') | ||
407 | .attr('dy', '1.35em') | ||
408 | .text('bandwidth'); | ||
409 | svg.append('text') | ||
410 | .attr('class', 'bandwidth label') | ||
411 | .attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.effectiveBandwidth.slice(-1)[0].bandwidth) + ')') | ||
412 | .attr('dy', '1.35em') | ||
413 | .text('measured'); | ||
414 | |||
415 | // segment bitrate dots | ||
416 | svg.selectAll('.segment-bitrate').remove(); | ||
417 | svg.selectAll('.segment-bitrate') | ||
418 | .data(data.playlists) | ||
419 | .enter().append('circle') | ||
420 | .attr('class', 'dot segment-bitrate') | ||
421 | .attr('r', 3.5) | ||
422 | .attr('cx', function(playlist) { | ||
423 | return x(playlist.time); | ||
424 | }) | ||
425 | .attr('cy', function(playlist) { | ||
426 | return y(playlist.bitrate); | ||
427 | }); | ||
428 | |||
429 | // highlight intervals when the buffer is empty | ||
430 | svg.selectAll('.buffer-empty').remove(); | ||
431 | svg.selectAll('.buffer-empty') | ||
432 | .data(data.buffered.reduce(function(result, sample) { | ||
433 | var last = result[result.length - 1]; | ||
434 | if (sample.buffered === 0) { | ||
435 | if (last && sample.time === last.end + 1) { | ||
436 | // add this sample to the interval we're accumulating | ||
437 | return result.slice(0, result.length - 1).concat({ | ||
438 | start: last.start, | ||
439 | end: sample.time | ||
440 | }); | ||
441 | } else { | ||
442 | // this sample starts a new interval | ||
443 | return result.concat({ | ||
444 | start: sample.time, | ||
445 | end: sample.time | ||
446 | }); | ||
447 | } | ||
448 | } | ||
449 | // filter out time periods where the buffer isn't empty | ||
450 | return result; | ||
451 | }, [])) | ||
452 | .enter().append('rect') | ||
453 | .attr('class', 'buffer-empty') | ||
454 | .attr('x', function(data) { | ||
455 | return x(data.start); | ||
456 | }) | ||
457 | .attr('width', function(data) { | ||
458 | return x(1 + data.end - data.start); | ||
459 | }) | ||
460 | .attr('y', 0) | ||
461 | .attr('height', y(height)); | ||
462 | }; | ||
463 | })(); | ||
464 | |||
465 | runSimulation(parameters(), displayTimeline); | ||
466 | |||
467 | })(window, document); |
utils/switcher/js/vendor/LICENSE
deleted
100644 → 0
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. |
utils/switcher/js/vendor/d3.js
deleted
100644 → 0
This diff could not be displayed because it is too large.
utils/switcher/js/vendor/d3.min.js
deleted
100644 → 0
This diff could not be displayed because it is too large.
utils/switcher/js/vendor/d3.zip
deleted
100644 → 0
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=["­",'<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))}; |
utils/switcher/run-simulation.js
0 → 100644
1 | import { | ||
2 | useFakeEnvironment, | ||
3 | useFakeMediaSource, | ||
4 | createPlayer, | ||
5 | openMediaSource, | ||
6 | standardXHRResponse, | ||
7 | } from '../../test/test-helpers.js'; | ||
8 | |||
9 | // the number of seconds of video in each segment | ||
10 | const segmentDuration = 9; // seconds | ||
11 | |||
12 | // the number of segments in the video | ||
13 | const segmentCount = 100; | ||
14 | |||
15 | // the length of the simulation | ||
16 | const duration = segmentDuration * segmentCount; | ||
17 | |||
18 | // the number of seconds it takes for a single bit to be | ||
19 | // transmitted from the client to the server, or vice-versa | ||
20 | const propagationDelay = 0.5; | ||
21 | |||
22 | // send a mock playlist response | ||
23 | const playlistResponse = function(request) { | ||
24 | let match = request.url.match(/\d+/); | ||
25 | let bitrate = match[0]; | ||
26 | |||
27 | let i = segmentCount; | ||
28 | let response = | ||
29 | '#EXTM3U\n' + | ||
30 | '#EXT-X-PLAYLIST-TYPE:VOD\n' + | ||
31 | '#EXT-X-TARGETDURATION:' + segmentDuration + '\n'; | ||
32 | |||
33 | while (i--) { | ||
34 | response += '#EXTINF:' + segmentDuration + ',\n'; | ||
35 | response += bitrate + '-' + (segmentCount - i) + '.ts\n'; | ||
36 | } | ||
37 | response += '#EXT-X-ENDLIST\n'; | ||
38 | |||
39 | return response; | ||
40 | }; | ||
41 | |||
42 | // run the simulation | ||
43 | const runSimulation = function(options, done) { | ||
44 | // SETUP | ||
45 | let results = { | ||
46 | bandwidth: [], | ||
47 | effectiveBandwidth: [], | ||
48 | playlists: [], | ||
49 | buffered: [], | ||
50 | options: options | ||
51 | }; | ||
52 | let t = 0; | ||
53 | let i = 0; | ||
54 | let env = useFakeEnvironment(); | ||
55 | let clock = env.clock; | ||
56 | let requests = env.requests; | ||
57 | let mse = useFakeMediaSource(); | ||
58 | let buffered = 0; | ||
59 | let currentTime = 0; | ||
60 | let player = window.player = createPlayer(); | ||
61 | |||
62 | document.querySelector('#qunit-fixture').style = 'display: none;'; | ||
63 | player.src({ | ||
64 | src: 'http://example.com/master.m3u8', | ||
65 | type: 'application/x-mpegurl' | ||
66 | }); | ||
67 | openMediaSource(player, clock); | ||
68 | |||
69 | // run next tick so that Flash doesn't swallow exceptions | ||
70 | let master = '#EXTM3U\n'; | ||
71 | options.playlists.forEach((bandwidth) => { | ||
72 | master+= '#EXT-X-STREAM-INF:BANDWIDTH=' + bandwidth + '\n'; | ||
73 | master += 'playlist-' + bandwidth + '.m3u8\n'; | ||
74 | }); | ||
75 | |||
76 | // simulate buffered and currentTime during playback | ||
77 | let getBuffer = (buff) => { | ||
78 | return videojs.createTimeRange(0, currentTime + buffered); | ||
79 | }; | ||
80 | player.tech_.buffered = getBuffer; | ||
81 | |||
82 | Object.defineProperty(player.tech_, 'time_', { | ||
83 | get: () => currentTime | ||
84 | }); | ||
85 | |||
86 | options.bandwidths.sort(function(left, right) { | ||
87 | return left.time - right.time; | ||
88 | }); | ||
89 | |||
90 | // respond to the playlist requests | ||
91 | let masterRequest = requests.shift(); | ||
92 | masterRequest.bandwidth = options.bandwidths[0].bandwidth; | ||
93 | masterRequest.respond(200, null, master); | ||
94 | |||
95 | let playlistRequest = requests.shift(); | ||
96 | playlistRequest.bandwidth = options.bandwidths[0].bandwidth; | ||
97 | playlistRequest.respond(200, null, playlistResponse(playlistRequest)); | ||
98 | |||
99 | let sourceBuffer = player.tech_.hls.mediaSource.sourceBuffers[0]; | ||
100 | Object.defineProperty(sourceBuffer, 'buffered', { | ||
101 | get: () => buffered | ||
102 | }); | ||
103 | |||
104 | // record the measured bandwidth for the playlist requests | ||
105 | results.effectiveBandwidth.push({ | ||
106 | time: 0, | ||
107 | bandwidth: player.tech_.hls.bandwidth | ||
108 | }); | ||
109 | |||
110 | // advance time and collect simulation results | ||
111 | for (t = i = 0; t < duration; clock.tick(1000), t++) { | ||
112 | while (options.bandwidths[i + 1] && options.bandwidths[i + 1].time <= t) { | ||
113 | i++; | ||
114 | } | ||
115 | let bandwidth = options.bandwidths[i].bandwidth; | ||
116 | results.bandwidth.push({ | ||
117 | time: t, | ||
118 | bandwidth: bandwidth | ||
119 | }); | ||
120 | |||
121 | // schedule response deliveries | ||
122 | while (requests.length) { | ||
123 | let request = requests.shift(); | ||
124 | request.bandwidth = bandwidth; | ||
125 | |||
126 | // playlist responses | ||
127 | if (/\.m3u8$/.test(request.url)) { | ||
128 | // for simplicity, playlist responses have zero trasmission time | ||
129 | request.respond(200, null, playlistResponse(request)); | ||
130 | continue; | ||
131 | } | ||
132 | |||
133 | // segment responses | ||
134 | let segmentSize = request.url.match(/(\d+)-\d+/)[1] * segmentDuration; | ||
135 | |||
136 | //console.log(segmentSize); | ||
137 | //console.log(bandwidth); | ||
138 | console.log(request.url); | ||
139 | let timeToTake = segmentSize/bandwidth + (propagationDelay * 1); | ||
140 | |||
141 | setTimeout(() => { | ||
142 | if (request.aborted) { | ||
143 | console.error("Request for segment aborted, download timedout") | ||
144 | return; | ||
145 | } | ||
146 | |||
147 | request.response = new Uint8Array(segmentSize * 0.125); | ||
148 | request.respond(200, null, ''); | ||
149 | sourceBuffer.trigger('updateend'); | ||
150 | |||
151 | results.playlists.push({ | ||
152 | time: t, | ||
153 | bitrate: +request.url.match(/(\d+)-\d+/)[1] | ||
154 | }); | ||
155 | |||
156 | buffered += segmentDuration; | ||
157 | results.effectiveBandwidth.push({ | ||
158 | time: t, | ||
159 | bandwidth: player.tech_.hls.bandwidth | ||
160 | }); | ||
161 | }, timeToTake * 1000); | ||
162 | // console.log(`taking ${timeToTake}s for response`); | ||
163 | } | ||
164 | |||
165 | results.buffered.push({ | ||
166 | time: t, | ||
167 | buffered: buffered | ||
168 | }); | ||
169 | |||
170 | // simulate playback | ||
171 | if (buffered > 0) { | ||
172 | buffered--; | ||
173 | currentTime++; | ||
174 | } | ||
175 | player.trigger('timeupdate'); | ||
176 | } | ||
177 | |||
178 | // update the fragment identifier so this scenario can be re-run easily | ||
179 | window.location.hash = '#' + options.bandwidths.map(function(interval) { | ||
180 | return 't' + interval.time + '=' + interval.bandwidth; | ||
181 | }).join('&'); | ||
182 | |||
183 | player.dispose(); | ||
184 | mse.restore(); | ||
185 | env.restore(); | ||
186 | |||
187 | console.log(results); | ||
188 | done(null, results); | ||
189 | }; | ||
190 | |||
191 | export default runSimulation; |
utils/switcher/switcher.js
0 → 100644
1 | import runSimulation from './run-simulation'; | ||
2 | import displayTimeline from './display-timeline'; | ||
3 | |||
4 | |||
5 | |||
6 | // a dynamic number of time-bandwidth pairs may be defined to drive the simulation | ||
7 | let networkTimeline = document.querySelector('.network-timeline'); | ||
8 | let timePeriod = networkTimeline.querySelector('li:last-child').cloneNode(true); | ||
9 | const appendTimePeriod = function() { | ||
10 | let clone = timePeriod.cloneNode(true); | ||
11 | let count = networkTimeline.querySelectorAll('input.bandwidth').length; | ||
12 | let time = clone.querySelector('.time'); | ||
13 | let bandwidth = clone.querySelector('input.bandwidth'); | ||
14 | |||
15 | time.name = 'time' + count; | ||
16 | bandwidth.name = 'bandwidth' + count; | ||
17 | networkTimeline.appendChild(clone); | ||
18 | }; | ||
19 | document.querySelector('.add-time-period').addEventListener('click', appendTimePeriod); | ||
20 | |||
21 | // apply any simulation parameters that were set in the fragment identifier | ||
22 | if (window.location.hash) { | ||
23 | // time periods are specified as t<seconds>=<bitrate> | ||
24 | // e.g. #t15=450560&t150=65530 | ||
25 | let params = window.location.hash.substring(1) | ||
26 | .split('&') | ||
27 | .map(function(param) { | ||
28 | return ((/t(\d+)=(\d+)/i).exec(param) || []) | ||
29 | .map(window.parseFloat).slice(1); | ||
30 | }).filter(function(pair) { | ||
31 | return pair.length === 2; | ||
32 | }); | ||
33 | |||
34 | networkTimeline.innerHTML = ''; | ||
35 | params.forEach(function(param) { | ||
36 | appendTimePeriod(); | ||
37 | networkTimeline.querySelector('li:last-child .time').value = param[0]; | ||
38 | networkTimeline.querySelector('li:last-child input.bandwidth').value = param[1]; | ||
39 | }); | ||
40 | } | ||
41 | |||
42 | // collect the simulation parameters | ||
43 | const parameters = function() { | ||
44 | let times = Array.prototype.slice.call(document.querySelectorAll('.time')); | ||
45 | let bandwidths = document.querySelectorAll('input.bandwidth'); | ||
46 | let playlists = Array.prototype.slice.call(document.querySelectorAll('input.bitrate')); | ||
47 | |||
48 | return { | ||
49 | playlists: playlists.map(function(input) { | ||
50 | return +input.value; | ||
51 | }), | ||
52 | bandwidths: times.reduce(function(conditions, time, i) { | ||
53 | return conditions.concat({ | ||
54 | time: +time.value, | ||
55 | bandwidth: +bandwidths[i].value | ||
56 | }); | ||
57 | }, []) | ||
58 | }; | ||
59 | }; | ||
60 | |||
61 | let runButton = document.getElementById('run-simulation'); | ||
62 | runButton.addEventListener('click', function() { | ||
63 | runSimulation(parameters(), displayTimeline); | ||
64 | }); | ||
65 | |||
66 | runButton.click(); |
-
Please register or sign in to post a comment