├── .gitignore ├── .npmignore ├── MIT-license.txt ├── NODE.md ├── README.md ├── assets ├── ajax-loader.gif ├── chrome.png ├── firefox.png └── ie10.png ├── browser.js ├── index.html ├── lib ├── async.js └── require.js ├── mpegts_to_mp4 ├── adts.js ├── h264.js ├── index.js ├── mp4.js ├── mpegts.js └── pes.js ├── node.js ├── package.json ├── screenshot.png ├── shim ├── console.time.js └── console.worker.js ├── style.css └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /lib 3 | /shim 4 | /browser.js 5 | /worker.js 6 | *.css 7 | *.png 8 | *.gif 9 | -------------------------------------------------------------------------------- /MIT-license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2013 Ingvar Stepanyan, http://rreverser.com 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /NODE.md: -------------------------------------------------------------------------------- 1 | Usage in Node.js 2 | ================ 3 | 4 | Node.js version can be used for conversion of standalone `.ts` files to `.mp4`. 5 | 6 | Using as executable 7 | ------------------- 8 | 9 | ```bash 10 | npm i -g mpegts_to_mp4 11 | mpegts_to_mp4 src.ts dest.mp4 12 | ``` 13 | 14 | Using as module 15 | --------------- 16 | 17 | ```bash 18 | npm i --save mpegts_to_mp4 19 | ``` 20 | 21 | ```javascript 22 | var mpegts_to_mp4 = require('mpegts_to_mp4'); 23 | 24 | mpegts_to_mp4('src.ts', 'dest.mp4', function (err) { 25 | // ... handle success/error ... 26 | }); 27 | // or 28 | var promise = mpegts_to_mp4('src.ts', 'dest.mp4'); 29 | promise.then( 30 | function () { /* handle success */ }, 31 | function (err) { /* handle error */ } 32 | ); 33 | ``` 34 | 35 | Both source and destination can be either string paths or Readable/Writable streams. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HTTP Live Streaming JavaScript player 2 | ===================================== 3 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/RReverser/mpegts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | What's this? 6 | ------------ 7 | This is [Apple HTTP Live Streaming](http://developer.apple.com/streaming/) JavaScript player created by 8 | performing realtime conversion of MPEG-TS video chunks to MPEG-4 in separate thread using 9 | Web Worker and playing them in order in main thread. 10 | 11 | How does it work? 12 | ----------------- 13 | Conversion is done using [jBinary](https://github.com/jDataView/jBinary) binary manipulation library with programmatically described data structures 14 | according to ISO 13818-1, ISO-14496-2, ISO-14496-12 and ITU-T H.222.0 specifications. 15 | 16 | Where does it work? 17 | ------------------- 18 | Works best in Chrome (stable branch), having more noticable lags when switching videos 19 | but still working in latest Firefox versions and IE10+. 20 | 21 | Where I can see that? 22 | --------------------- 23 | Check out [http://rreverser.github.io/mpegts/](http://rreverser.github.io/mpegts/) for live demo. 24 | 25 | Screenshot: 26 | [![Screenshot](http://rreverser.github.io/mpegts/screenshot.png?)](http://rreverser.github.io/mpegts/) 27 | 28 | Disclaimer 29 | ---------- 30 | Please note that demo uses 3rd-party HLS demo source and service [http://www.corsproxy.com/](http://www.corsproxy.com/) for proxying it with 31 | needed Cross-Origin-Request headers for browsers to allow chunk downloading, so it may be unstable. 32 | 33 | Can I use it in Node.js? 34 | ------------------------ 35 | [Yes, you can.](NODE.md) 36 | 37 | What license is it issued under? 38 | -------------------------------- 39 | It's regular [MIT license](MIT-license.txt). -------------------------------------------------------------------------------- /assets/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RReverser/mpegts/013ff8a03395b2404d3021d4b84be437ce683f56/assets/ajax-loader.gif -------------------------------------------------------------------------------- /assets/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RReverser/mpegts/013ff8a03395b2404d3021d4b84be437ce683f56/assets/chrome.png -------------------------------------------------------------------------------- /assets/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RReverser/mpegts/013ff8a03395b2404d3021d4b84be437ce683f56/assets/firefox.png -------------------------------------------------------------------------------- /assets/ie10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RReverser/mpegts/013ff8a03395b2404d3021d4b84be437ce683f56/assets/ie10.png -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // requestAnimationFrame polyfill 5 | window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || setTimeout; 6 | 7 | // preconfiguration using 7 | 8 | 9 | 10 | 11 | 12 | 13 | Fork me on GitHub 14 | 15 |

HTTP Live Streaming
JavaScript converter and player demo

16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |

Works best in Chrome (stable branch), having lags when switching videos but still working in latest Firefox and IE10+.

33 | 34 |

Please note that demo uses 3rd-party HLS demo source and service http://www.corsproxy.com/ for proxying it with needed Cross-Origin-Request headers, so it may be unstable.

35 | 36 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/async.js: -------------------------------------------------------------------------------- 1 | /*global setImmediate: false, setTimeout: false, console: false */ 2 | (function () { 3 | 4 | var async = {}; 5 | 6 | // global on the server, window in the browser 7 | var root, previous_async; 8 | 9 | root = this; 10 | if (root != null) { 11 | previous_async = root.async; 12 | } 13 | 14 | async.noConflict = function () { 15 | root.async = previous_async; 16 | return async; 17 | }; 18 | 19 | function only_once(fn) { 20 | var called = false; 21 | return function() { 22 | if (called) throw new Error("Callback was already called."); 23 | called = true; 24 | fn.apply(root, arguments); 25 | } 26 | } 27 | 28 | //// cross-browser compatiblity functions //// 29 | 30 | var _each = function (arr, iterator) { 31 | if (arr.forEach) { 32 | return arr.forEach(iterator); 33 | } 34 | for (var i = 0; i < arr.length; i += 1) { 35 | iterator(arr[i], i, arr); 36 | } 37 | }; 38 | 39 | var _map = function (arr, iterator) { 40 | if (arr.map) { 41 | return arr.map(iterator); 42 | } 43 | var results = []; 44 | _each(arr, function (x, i, a) { 45 | results.push(iterator(x, i, a)); 46 | }); 47 | return results; 48 | }; 49 | 50 | var _reduce = function (arr, iterator, memo) { 51 | if (arr.reduce) { 52 | return arr.reduce(iterator, memo); 53 | } 54 | _each(arr, function (x, i, a) { 55 | memo = iterator(memo, x, i, a); 56 | }); 57 | return memo; 58 | }; 59 | 60 | var _keys = function (obj) { 61 | if (Object.keys) { 62 | return Object.keys(obj); 63 | } 64 | var keys = []; 65 | for (var k in obj) { 66 | if (obj.hasOwnProperty(k)) { 67 | keys.push(k); 68 | } 69 | } 70 | return keys; 71 | }; 72 | 73 | //// exported async module functions //// 74 | 75 | //// nextTick implementation with browser-compatible fallback //// 76 | if (typeof process === 'undefined' || !(process.nextTick)) { 77 | if (typeof setImmediate === 'function') { 78 | async.nextTick = function (fn) { 79 | // not a direct alias for IE10 compatibility 80 | setImmediate(fn); 81 | }; 82 | async.setImmediate = async.nextTick; 83 | } 84 | else { 85 | async.nextTick = function (fn) { 86 | setTimeout(fn, 0); 87 | }; 88 | async.setImmediate = async.nextTick; 89 | } 90 | } 91 | else { 92 | async.nextTick = process.nextTick; 93 | if (typeof setImmediate !== 'undefined') { 94 | async.setImmediate = setImmediate; 95 | } 96 | else { 97 | async.setImmediate = async.nextTick; 98 | } 99 | } 100 | 101 | async.each = function (arr, iterator, callback) { 102 | callback = callback || function () {}; 103 | if (!arr.length) { 104 | return callback(); 105 | } 106 | var completed = 0; 107 | _each(arr, function (x) { 108 | iterator(x, only_once(function (err) { 109 | if (err) { 110 | callback(err); 111 | callback = function () {}; 112 | } 113 | else { 114 | completed += 1; 115 | if (completed >= arr.length) { 116 | callback(null); 117 | } 118 | } 119 | })); 120 | }); 121 | }; 122 | async.forEach = async.each; 123 | 124 | async.eachSeries = function (arr, iterator, callback) { 125 | callback = callback || function () {}; 126 | if (!arr.length) { 127 | return callback(); 128 | } 129 | var completed = 0; 130 | var iterate = function () { 131 | iterator(arr[completed], function (err) { 132 | if (err) { 133 | callback(err); 134 | callback = function () {}; 135 | } 136 | else { 137 | completed += 1; 138 | if (completed >= arr.length) { 139 | callback(null); 140 | } 141 | else { 142 | iterate(); 143 | } 144 | } 145 | }); 146 | }; 147 | iterate(); 148 | }; 149 | async.forEachSeries = async.eachSeries; 150 | 151 | async.eachLimit = function (arr, limit, iterator, callback) { 152 | var fn = _eachLimit(limit); 153 | fn.apply(null, [arr, iterator, callback]); 154 | }; 155 | async.forEachLimit = async.eachLimit; 156 | 157 | var _eachLimit = function (limit) { 158 | 159 | return function (arr, iterator, callback) { 160 | callback = callback || function () {}; 161 | if (!arr.length || limit <= 0) { 162 | return callback(); 163 | } 164 | var completed = 0; 165 | var started = 0; 166 | var running = 0; 167 | 168 | (function replenish () { 169 | if (completed >= arr.length) { 170 | return callback(); 171 | } 172 | 173 | while (running < limit && started < arr.length) { 174 | started += 1; 175 | running += 1; 176 | iterator(arr[started - 1], function (err) { 177 | if (err) { 178 | callback(err); 179 | callback = function () {}; 180 | } 181 | else { 182 | completed += 1; 183 | running -= 1; 184 | if (completed >= arr.length) { 185 | callback(); 186 | } 187 | else { 188 | replenish(); 189 | } 190 | } 191 | }); 192 | } 193 | })(); 194 | }; 195 | }; 196 | 197 | 198 | var doParallel = function (fn) { 199 | return function () { 200 | var args = Array.prototype.slice.call(arguments); 201 | return fn.apply(null, [async.each].concat(args)); 202 | }; 203 | }; 204 | var doParallelLimit = function(limit, fn) { 205 | return function () { 206 | var args = Array.prototype.slice.call(arguments); 207 | return fn.apply(null, [_eachLimit(limit)].concat(args)); 208 | }; 209 | }; 210 | var doSeries = function (fn) { 211 | return function () { 212 | var args = Array.prototype.slice.call(arguments); 213 | return fn.apply(null, [async.eachSeries].concat(args)); 214 | }; 215 | }; 216 | 217 | 218 | var _asyncMap = function (eachfn, arr, iterator, callback) { 219 | var results = []; 220 | arr = _map(arr, function (x, i) { 221 | return {index: i, value: x}; 222 | }); 223 | eachfn(arr, function (x, callback) { 224 | iterator(x.value, function (err, v) { 225 | results[x.index] = v; 226 | callback(err); 227 | }); 228 | }, function (err) { 229 | callback(err, results); 230 | }); 231 | }; 232 | async.map = doParallel(_asyncMap); 233 | async.mapSeries = doSeries(_asyncMap); 234 | async.mapLimit = function (arr, limit, iterator, callback) { 235 | return _mapLimit(limit)(arr, iterator, callback); 236 | }; 237 | 238 | var _mapLimit = function(limit) { 239 | return doParallelLimit(limit, _asyncMap); 240 | }; 241 | 242 | // reduce only has a series version, as doing reduce in parallel won't 243 | // work in many situations. 244 | async.reduce = function (arr, memo, iterator, callback) { 245 | async.eachSeries(arr, function (x, callback) { 246 | iterator(memo, x, function (err, v) { 247 | memo = v; 248 | callback(err); 249 | }); 250 | }, function (err) { 251 | callback(err, memo); 252 | }); 253 | }; 254 | // inject alias 255 | async.inject = async.reduce; 256 | // foldl alias 257 | async.foldl = async.reduce; 258 | 259 | async.reduceRight = function (arr, memo, iterator, callback) { 260 | var reversed = _map(arr, function (x) { 261 | return x; 262 | }).reverse(); 263 | async.reduce(reversed, memo, iterator, callback); 264 | }; 265 | // foldr alias 266 | async.foldr = async.reduceRight; 267 | 268 | var _filter = function (eachfn, arr, iterator, callback) { 269 | var results = []; 270 | arr = _map(arr, function (x, i) { 271 | return {index: i, value: x}; 272 | }); 273 | eachfn(arr, function (x, callback) { 274 | iterator(x.value, function (v) { 275 | if (v) { 276 | results.push(x); 277 | } 278 | callback(); 279 | }); 280 | }, function (err) { 281 | callback(_map(results.sort(function (a, b) { 282 | return a.index - b.index; 283 | }), function (x) { 284 | return x.value; 285 | })); 286 | }); 287 | }; 288 | async.filter = doParallel(_filter); 289 | async.filterSeries = doSeries(_filter); 290 | // select alias 291 | async.select = async.filter; 292 | async.selectSeries = async.filterSeries; 293 | 294 | var _reject = function (eachfn, arr, iterator, callback) { 295 | var results = []; 296 | arr = _map(arr, function (x, i) { 297 | return {index: i, value: x}; 298 | }); 299 | eachfn(arr, function (x, callback) { 300 | iterator(x.value, function (v) { 301 | if (!v) { 302 | results.push(x); 303 | } 304 | callback(); 305 | }); 306 | }, function (err) { 307 | callback(_map(results.sort(function (a, b) { 308 | return a.index - b.index; 309 | }), function (x) { 310 | return x.value; 311 | })); 312 | }); 313 | }; 314 | async.reject = doParallel(_reject); 315 | async.rejectSeries = doSeries(_reject); 316 | 317 | var _detect = function (eachfn, arr, iterator, main_callback) { 318 | eachfn(arr, function (x, callback) { 319 | iterator(x, function (result) { 320 | if (result) { 321 | main_callback(x); 322 | main_callback = function () {}; 323 | } 324 | else { 325 | callback(); 326 | } 327 | }); 328 | }, function (err) { 329 | main_callback(); 330 | }); 331 | }; 332 | async.detect = doParallel(_detect); 333 | async.detectSeries = doSeries(_detect); 334 | 335 | async.some = function (arr, iterator, main_callback) { 336 | async.each(arr, function (x, callback) { 337 | iterator(x, function (v) { 338 | if (v) { 339 | main_callback(true); 340 | main_callback = function () {}; 341 | } 342 | callback(); 343 | }); 344 | }, function (err) { 345 | main_callback(false); 346 | }); 347 | }; 348 | // any alias 349 | async.any = async.some; 350 | 351 | async.every = function (arr, iterator, main_callback) { 352 | async.each(arr, function (x, callback) { 353 | iterator(x, function (v) { 354 | if (!v) { 355 | main_callback(false); 356 | main_callback = function () {}; 357 | } 358 | callback(); 359 | }); 360 | }, function (err) { 361 | main_callback(true); 362 | }); 363 | }; 364 | // all alias 365 | async.all = async.every; 366 | 367 | async.sortBy = function (arr, iterator, callback) { 368 | async.map(arr, function (x, callback) { 369 | iterator(x, function (err, criteria) { 370 | if (err) { 371 | callback(err); 372 | } 373 | else { 374 | callback(null, {value: x, criteria: criteria}); 375 | } 376 | }); 377 | }, function (err, results) { 378 | if (err) { 379 | return callback(err); 380 | } 381 | else { 382 | var fn = function (left, right) { 383 | var a = left.criteria, b = right.criteria; 384 | return a < b ? -1 : a > b ? 1 : 0; 385 | }; 386 | callback(null, _map(results.sort(fn), function (x) { 387 | return x.value; 388 | })); 389 | } 390 | }); 391 | }; 392 | 393 | async.auto = function (tasks, callback) { 394 | callback = callback || function () {}; 395 | var keys = _keys(tasks); 396 | if (!keys.length) { 397 | return callback(null); 398 | } 399 | 400 | var results = {}; 401 | 402 | var listeners = []; 403 | var addListener = function (fn) { 404 | listeners.unshift(fn); 405 | }; 406 | var removeListener = function (fn) { 407 | for (var i = 0; i < listeners.length; i += 1) { 408 | if (listeners[i] === fn) { 409 | listeners.splice(i, 1); 410 | return; 411 | } 412 | } 413 | }; 414 | var taskComplete = function () { 415 | _each(listeners.slice(0), function (fn) { 416 | fn(); 417 | }); 418 | }; 419 | 420 | addListener(function () { 421 | if (_keys(results).length === keys.length) { 422 | callback(null, results); 423 | callback = function () {}; 424 | } 425 | }); 426 | 427 | _each(keys, function (k) { 428 | var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; 429 | var taskCallback = function (err) { 430 | var args = Array.prototype.slice.call(arguments, 1); 431 | if (args.length <= 1) { 432 | args = args[0]; 433 | } 434 | if (err) { 435 | var safeResults = {}; 436 | _each(_keys(results), function(rkey) { 437 | safeResults[rkey] = results[rkey]; 438 | }); 439 | safeResults[k] = args; 440 | callback(err, safeResults); 441 | // stop subsequent errors hitting callback multiple times 442 | callback = function () {}; 443 | } 444 | else { 445 | results[k] = args; 446 | async.setImmediate(taskComplete); 447 | } 448 | }; 449 | var requires = task.slice(0, Math.abs(task.length - 1)) || []; 450 | var ready = function () { 451 | return _reduce(requires, function (a, x) { 452 | return (a && results.hasOwnProperty(x)); 453 | }, true) && !results.hasOwnProperty(k); 454 | }; 455 | if (ready()) { 456 | task[task.length - 1](taskCallback, results); 457 | } 458 | else { 459 | var listener = function () { 460 | if (ready()) { 461 | removeListener(listener); 462 | task[task.length - 1](taskCallback, results); 463 | } 464 | }; 465 | addListener(listener); 466 | } 467 | }); 468 | }; 469 | 470 | async.waterfall = function (tasks, callback) { 471 | callback = callback || function () {}; 472 | if (tasks.constructor !== Array) { 473 | var err = new Error('First argument to waterfall must be an array of functions'); 474 | return callback(err); 475 | } 476 | if (!tasks.length) { 477 | return callback(); 478 | } 479 | var wrapIterator = function (iterator) { 480 | return function (err) { 481 | if (err) { 482 | callback.apply(null, arguments); 483 | callback = function () {}; 484 | } 485 | else { 486 | var args = Array.prototype.slice.call(arguments, 1); 487 | var next = iterator.next(); 488 | if (next) { 489 | args.push(wrapIterator(next)); 490 | } 491 | else { 492 | args.push(callback); 493 | } 494 | async.setImmediate(function () { 495 | iterator.apply(null, args); 496 | }); 497 | } 498 | }; 499 | }; 500 | wrapIterator(async.iterator(tasks))(); 501 | }; 502 | 503 | var _parallel = function(eachfn, tasks, callback) { 504 | callback = callback || function () {}; 505 | if (tasks.constructor === Array) { 506 | eachfn.map(tasks, function (fn, callback) { 507 | if (fn) { 508 | fn(function (err) { 509 | var args = Array.prototype.slice.call(arguments, 1); 510 | if (args.length <= 1) { 511 | args = args[0]; 512 | } 513 | callback.call(null, err, args); 514 | }); 515 | } 516 | }, callback); 517 | } 518 | else { 519 | var results = {}; 520 | eachfn.each(_keys(tasks), function (k, callback) { 521 | tasks[k](function (err) { 522 | var args = Array.prototype.slice.call(arguments, 1); 523 | if (args.length <= 1) { 524 | args = args[0]; 525 | } 526 | results[k] = args; 527 | callback(err); 528 | }); 529 | }, function (err) { 530 | callback(err, results); 531 | }); 532 | } 533 | }; 534 | 535 | async.parallel = function (tasks, callback) { 536 | _parallel({ map: async.map, each: async.each }, tasks, callback); 537 | }; 538 | 539 | async.parallelLimit = function(tasks, limit, callback) { 540 | _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); 541 | }; 542 | 543 | async.series = function (tasks, callback) { 544 | callback = callback || function () {}; 545 | if (tasks.constructor === Array) { 546 | async.mapSeries(tasks, function (fn, callback) { 547 | if (fn) { 548 | fn(function (err) { 549 | var args = Array.prototype.slice.call(arguments, 1); 550 | if (args.length <= 1) { 551 | args = args[0]; 552 | } 553 | callback.call(null, err, args); 554 | }); 555 | } 556 | }, callback); 557 | } 558 | else { 559 | var results = {}; 560 | async.eachSeries(_keys(tasks), function (k, callback) { 561 | tasks[k](function (err) { 562 | var args = Array.prototype.slice.call(arguments, 1); 563 | if (args.length <= 1) { 564 | args = args[0]; 565 | } 566 | results[k] = args; 567 | callback(err); 568 | }); 569 | }, function (err) { 570 | callback(err, results); 571 | }); 572 | } 573 | }; 574 | 575 | async.iterator = function (tasks) { 576 | var makeCallback = function (index) { 577 | var fn = function () { 578 | if (tasks.length) { 579 | tasks[index].apply(null, arguments); 580 | } 581 | return fn.next(); 582 | }; 583 | fn.next = function () { 584 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; 585 | }; 586 | return fn; 587 | }; 588 | return makeCallback(0); 589 | }; 590 | 591 | async.apply = function (fn) { 592 | var args = Array.prototype.slice.call(arguments, 1); 593 | return function () { 594 | return fn.apply( 595 | null, args.concat(Array.prototype.slice.call(arguments)) 596 | ); 597 | }; 598 | }; 599 | 600 | var _concat = function (eachfn, arr, fn, callback) { 601 | var r = []; 602 | eachfn(arr, function (x, cb) { 603 | fn(x, function (err, y) { 604 | r = r.concat(y || []); 605 | cb(err); 606 | }); 607 | }, function (err) { 608 | callback(err, r); 609 | }); 610 | }; 611 | async.concat = doParallel(_concat); 612 | async.concatSeries = doSeries(_concat); 613 | 614 | async.whilst = function (test, iterator, callback) { 615 | if (test()) { 616 | iterator(function (err) { 617 | if (err) { 618 | return callback(err); 619 | } 620 | async.whilst(test, iterator, callback); 621 | }); 622 | } 623 | else { 624 | callback(); 625 | } 626 | }; 627 | 628 | async.doWhilst = function (iterator, test, callback) { 629 | iterator(function (err) { 630 | if (err) { 631 | return callback(err); 632 | } 633 | if (test()) { 634 | async.doWhilst(iterator, test, callback); 635 | } 636 | else { 637 | callback(); 638 | } 639 | }); 640 | }; 641 | 642 | async.until = function (test, iterator, callback) { 643 | if (!test()) { 644 | iterator(function (err) { 645 | if (err) { 646 | return callback(err); 647 | } 648 | async.until(test, iterator, callback); 649 | }); 650 | } 651 | else { 652 | callback(); 653 | } 654 | }; 655 | 656 | async.doUntil = function (iterator, test, callback) { 657 | iterator(function (err) { 658 | if (err) { 659 | return callback(err); 660 | } 661 | if (!test()) { 662 | async.doUntil(iterator, test, callback); 663 | } 664 | else { 665 | callback(); 666 | } 667 | }); 668 | }; 669 | 670 | async.queue = function (worker, concurrency) { 671 | if (concurrency === undefined) { 672 | concurrency = 1; 673 | } 674 | function _insert(q, data, pos, callback) { 675 | if(data.constructor !== Array) { 676 | data = [data]; 677 | } 678 | _each(data, function(task) { 679 | var item = { 680 | data: task, 681 | callback: typeof callback === 'function' ? callback : null 682 | }; 683 | 684 | if (pos) { 685 | q.tasks.unshift(item); 686 | } else { 687 | q.tasks.push(item); 688 | } 689 | 690 | if (q.saturated && q.tasks.length === concurrency) { 691 | q.saturated(); 692 | } 693 | async.setImmediate(q.process); 694 | }); 695 | } 696 | 697 | var workers = 0; 698 | var q = { 699 | tasks: [], 700 | concurrency: concurrency, 701 | saturated: null, 702 | empty: null, 703 | drain: null, 704 | push: function (data, callback) { 705 | _insert(q, data, false, callback); 706 | }, 707 | unshift: function (data, callback) { 708 | _insert(q, data, true, callback); 709 | }, 710 | process: function () { 711 | if (workers < q.concurrency && q.tasks.length) { 712 | var task = q.tasks.shift(); 713 | if (q.empty && q.tasks.length === 0) { 714 | q.empty(); 715 | } 716 | workers += 1; 717 | var next = function () { 718 | workers -= 1; 719 | if (task.callback) { 720 | task.callback.apply(task, arguments); 721 | } 722 | if (q.drain && q.tasks.length + workers === 0) { 723 | q.drain(); 724 | } 725 | q.process(); 726 | }; 727 | var cb = only_once(next); 728 | worker(task.data, cb); 729 | } 730 | }, 731 | length: function () { 732 | return q.tasks.length; 733 | }, 734 | running: function () { 735 | return workers; 736 | } 737 | }; 738 | return q; 739 | }; 740 | 741 | async.cargo = function (worker, payload) { 742 | var working = false, 743 | tasks = []; 744 | 745 | var cargo = { 746 | tasks: tasks, 747 | payload: payload, 748 | saturated: null, 749 | empty: null, 750 | drain: null, 751 | push: function (data, callback) { 752 | if(data.constructor !== Array) { 753 | data = [data]; 754 | } 755 | _each(data, function(task) { 756 | tasks.push({ 757 | data: task, 758 | callback: typeof callback === 'function' ? callback : null 759 | }); 760 | if (cargo.saturated && tasks.length === payload) { 761 | cargo.saturated(); 762 | } 763 | }); 764 | async.setImmediate(cargo.process); 765 | }, 766 | process: function process() { 767 | if (working) return; 768 | if (tasks.length === 0) { 769 | if(cargo.drain) cargo.drain(); 770 | return; 771 | } 772 | 773 | var ts = typeof payload === 'number' 774 | ? tasks.splice(0, payload) 775 | : tasks.splice(0); 776 | 777 | var ds = _map(ts, function (task) { 778 | return task.data; 779 | }); 780 | 781 | if(cargo.empty) cargo.empty(); 782 | working = true; 783 | worker(ds, function () { 784 | working = false; 785 | 786 | var args = arguments; 787 | _each(ts, function (data) { 788 | if (data.callback) { 789 | data.callback.apply(null, args); 790 | } 791 | }); 792 | 793 | process(); 794 | }); 795 | }, 796 | length: function () { 797 | return tasks.length; 798 | }, 799 | running: function () { 800 | return working; 801 | } 802 | }; 803 | return cargo; 804 | }; 805 | 806 | var _console_fn = function (name) { 807 | return function (fn) { 808 | var args = Array.prototype.slice.call(arguments, 1); 809 | fn.apply(null, args.concat([function (err) { 810 | var args = Array.prototype.slice.call(arguments, 1); 811 | if (typeof console !== 'undefined') { 812 | if (err) { 813 | if (console.error) { 814 | console.error(err); 815 | } 816 | } 817 | else if (console[name]) { 818 | _each(args, function (x) { 819 | console[name](x); 820 | }); 821 | } 822 | } 823 | }])); 824 | }; 825 | }; 826 | async.log = _console_fn('log'); 827 | async.dir = _console_fn('dir'); 828 | /*async.info = _console_fn('info'); 829 | async.warn = _console_fn('warn'); 830 | async.error = _console_fn('error');*/ 831 | 832 | async.memoize = function (fn, hasher) { 833 | var memo = {}; 834 | var queues = {}; 835 | hasher = hasher || function (x) { 836 | return x; 837 | }; 838 | var memoized = function () { 839 | var args = Array.prototype.slice.call(arguments); 840 | var callback = args.pop(); 841 | var key = hasher.apply(null, args); 842 | if (key in memo) { 843 | callback.apply(null, memo[key]); 844 | } 845 | else if (key in queues) { 846 | queues[key].push(callback); 847 | } 848 | else { 849 | queues[key] = [callback]; 850 | fn.apply(null, args.concat([function () { 851 | memo[key] = arguments; 852 | var q = queues[key]; 853 | delete queues[key]; 854 | for (var i = 0, l = q.length; i < l; i++) { 855 | q[i].apply(null, arguments); 856 | } 857 | }])); 858 | } 859 | }; 860 | memoized.memo = memo; 861 | memoized.unmemoized = fn; 862 | return memoized; 863 | }; 864 | 865 | async.unmemoize = function (fn) { 866 | return function () { 867 | return (fn.unmemoized || fn).apply(null, arguments); 868 | }; 869 | }; 870 | 871 | async.times = function (count, iterator, callback) { 872 | var counter = []; 873 | for (var i = 0; i < count; i++) { 874 | counter.push(i); 875 | } 876 | return async.map(counter, iterator, callback); 877 | }; 878 | 879 | async.timesSeries = function (count, iterator, callback) { 880 | var counter = []; 881 | for (var i = 0; i < count; i++) { 882 | counter.push(i); 883 | } 884 | return async.mapSeries(counter, iterator, callback); 885 | }; 886 | 887 | async.compose = function (/* functions... */) { 888 | var fns = Array.prototype.reverse.call(arguments); 889 | return function () { 890 | var that = this; 891 | var args = Array.prototype.slice.call(arguments); 892 | var callback = args.pop(); 893 | async.reduce(fns, args, function (newargs, fn, cb) { 894 | fn.apply(that, newargs.concat([function () { 895 | var err = arguments[0]; 896 | var nextargs = Array.prototype.slice.call(arguments, 1); 897 | cb(err, nextargs); 898 | }])) 899 | }, 900 | function (err, results) { 901 | callback.apply(that, [err].concat(results)); 902 | }); 903 | }; 904 | }; 905 | 906 | var _applyEach = function (eachfn, fns /*args...*/) { 907 | var go = function () { 908 | var that = this; 909 | var args = Array.prototype.slice.call(arguments); 910 | var callback = args.pop(); 911 | return eachfn(fns, function (fn, cb) { 912 | fn.apply(that, args.concat([cb])); 913 | }, 914 | callback); 915 | }; 916 | if (arguments.length > 2) { 917 | var args = Array.prototype.slice.call(arguments, 2); 918 | return go.apply(this, args); 919 | } 920 | else { 921 | return go; 922 | } 923 | }; 924 | async.applyEach = doParallel(_applyEach); 925 | async.applyEachSeries = doSeries(_applyEach); 926 | 927 | async.forever = function (fn, callback) { 928 | function next(err) { 929 | if (err) { 930 | if (callback) { 931 | return callback(err); 932 | } 933 | throw err; 934 | } 935 | fn(next); 936 | } 937 | next(); 938 | }; 939 | 940 | // AMD / RequireJS 941 | if (typeof define !== 'undefined' && define.amd) { 942 | define([], function () { 943 | return async; 944 | }); 945 | } 946 | // Node.js 947 | else if (typeof module !== 'undefined' && module.exports) { 948 | module.exports = async; 949 | } 950 | // included directly via