├── README.md ├── examples ├── in.mp4 └── stream.js ├── index.js ├── lib └── HLSKit.js ├── node_modules ├── async │ ├── LICENSE │ ├── README.md │ ├── component.json │ ├── lib │ │ └── async.js │ └── package.json ├── debug │ ├── Readme.md │ ├── debug.js │ ├── index.js │ ├── lib │ │ └── debug.js │ └── package.json └── underscore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── underscore-min.js │ └── underscore.js ├── package.json └── reference ├── PlaylistSession-Example.js └── good-playlists.js /README.md: -------------------------------------------------------------------------------- 1 | HLSKit 2 | ====== 3 | 4 | Simple adaptive-bitrate HTTP Live Streaming: mp4 to mpegts transcoding & m3u8 playlist serialization (with support for stream discontinuities, sliding-window live, and replay playlists). 5 | -------------------------------------------------------------------------------- /examples/in.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danscan/HLSKit/58d50772072089bd823bdf7598a3a4d4bd72d4c9/examples/in.mp4 -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | var HLSKit = require('..'), 2 | async = require('async'), 3 | debug = require('debug')('stream'); 4 | 5 | /** 6 | * configure HLSKit 7 | */ 8 | var hLSKit = new HLSKit({ 9 | audioOutputOptions: { 10 | bitrate: 40000, 11 | sampleRate: 22050 12 | }, 13 | 14 | workDirectory: __dirname +'/work' 15 | }); 16 | 17 | hLSKit.addOutputVariant('lo', { 18 | bitrate: 80000, 19 | frameRate: 16, 20 | keyframeInterval: 8, 21 | resolution: '360x360' 22 | }); 23 | 24 | hLSKit.addOutputVariant('hi', { 25 | bitrate: 480000, 26 | frameRate: 24, 27 | keyframeInterval: 12, 28 | resolution: '480x480' 29 | }); 30 | 31 | hLSKit.addOutputVariant('audio', { 32 | skipVideo: true 33 | }); 34 | 35 | hLSKit.addCoverImage('still', { 36 | resolution: '480x480' 37 | }); 38 | 39 | var playlistSession = new hLSKit.PlaylistSession('12345', { targetDuration: 12, windowLength: 3 }); 40 | 41 | /** 42 | * Append in.mp4 10 times, simulating API append requests 43 | * ====================================================== 44 | */ 45 | console.log('Starting stream simulation!'); 46 | 47 | async.series([ 48 | function(next) { 49 | debug('append 0'); 50 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 0 }, next); 51 | }, 52 | function(next) { 53 | debug('append 1'); 54 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 1 }, next); 55 | }, 56 | function(next) { 57 | debug('append 2'); 58 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 2 }, next); 59 | }, 60 | function(next) { 61 | debug('append 3'); 62 | playlistSession.meta.isAvailable = true; 63 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 3 }, next); 64 | }, 65 | function(next) { 66 | debug('append 4'); 67 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 4 }, next); 68 | }, 69 | function(next) { 70 | debug('append 5'); 71 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 5 }, next); 72 | }, 73 | function(next) { 74 | debug('append 7'); 75 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 7 }, next); 76 | }, 77 | function(next) { 78 | debug('append 6'); 79 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 6 }, next); 80 | }, 81 | function(next) { 82 | debug('append 8'); 83 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 8 }, next); 84 | }, 85 | function(next) { 86 | debug('append 11'); 87 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 11 }, next); 88 | }, 89 | function(next) { 90 | debug('append 10'); 91 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 10 }, next); 92 | }, 93 | function(next) { 94 | debug('append 12'); 95 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 12 }, next); 96 | }, 97 | function(next) { 98 | debug('append 100'); 99 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 100, }, next); 100 | }, 101 | function(next) { 102 | debug('append 13'); 103 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 13 }, next); 104 | }, 105 | function(next) { 106 | debug('append 14'); 107 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 14 }, next); 108 | }, 109 | function(next) { 110 | debug('append 101'); 111 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 101 }, next); 112 | }, 113 | function(next) { 114 | debug('append 102'); 115 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 102 }, next); 116 | }, 117 | function(next) { 118 | debug('append 103'); 119 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 103 }, next); 120 | }, 121 | function(next) { 122 | debug('append 104'); 123 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 104 }, next); 124 | }, 125 | function(next) { 126 | debug('append 105'); 127 | playlistSession.appendMP4('./in.mp4', { mediaSequence: 105, shouldFinish: true }, next); 128 | }, 129 | ], function(error) { 130 | if (error) { 131 | console.error('Error appending mp4 to playlistSession:', error); 132 | return; 133 | } 134 | 135 | console.log('Stream Simulation FINISHED!'); 136 | 137 | /** 138 | * END Append in.mp4 10 times, simulating API append requests 139 | * ========================================================== 140 | */ 141 | /** 142 | * serialize playlist state object 143 | */ 144 | playlistSession = playlistSession.serializeStateObject(); 145 | console.log('Serialized playlistSession to state object: ', playlistSession); 146 | 147 | /** 148 | * create playlist session from playlist state object 149 | */ 150 | playlistSession = hLSKit.playlistSessionFromStateObject(playlistSession); 151 | console.log('Playlist session from state object... configured: ', playlistSession); 152 | }); 153 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/HLSKit.js'); 2 | 3 | -------------------------------------------------------------------------------- /lib/HLSKit.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | debug = require('debug')('HLSKit'), 3 | exec = require('child_process').exec, 4 | fs = require('fs'), 5 | async = require('async'), 6 | _ = require('underscore'), 7 | HLSKit; 8 | 9 | /** 10 | * HLSKit 11 | * 12 | * @param config 13 | * 14 | * @returns HLSKit 15 | * 16 | * config properties: 17 | * audioOutputOptions 18 | * * bitrate 19 | * * sampleRate 20 | * workDirectory 21 | */ 22 | HLSKit = function HLSKit(config) { 23 | var _thisHLSKit = this, 24 | config = config || {}; 25 | 26 | debug('Construct HLSKit. config:', config); 27 | 28 | /** 29 | * if required config options are missing, revert to defaults 30 | */ 31 | _thisHLSKit.config = { 32 | audioOutputOptions: config.audioOutputOptions || { bitrate: 64000, sampleRate: 44050 }, 33 | workDirectory: config.workDirectory || process.cwd() 34 | }; 35 | 36 | /** 37 | * set up arrays to store cover images and output variants for this instance of HLSKit 38 | */ 39 | _thisHLSKit._coverImages = []; 40 | _thisHLSKit._outputVariants = []; 41 | 42 | /** 43 | * PlaylistSession Constructor 44 | * 45 | * @param id 46 | * @param config (optonal) 47 | */ 48 | _thisHLSKit.PlaylistSession = function PlaylistSession(id, config) { 49 | var _thisPlaylistSession = this; 50 | 51 | debug('Construct PlaylistSession. id: %s. config:', id, config); 52 | 53 | /** 54 | * Construct Playlist Session 55 | */ 56 | assert(_.isString(id), 'Required parameter \'id\' must be a string'); 57 | 58 | _thisPlaylistSession.mediaSegments = []; 59 | _thisPlaylistSession.config = { 60 | targetDuration: 12, 61 | windowLength: 3 62 | }; 63 | _thisPlaylistSession.meta = { 64 | id: id, 65 | shouldBeAvailable: false, 66 | isAvailable: false, 67 | shouldFinish: false, 68 | isFinished: false 69 | }; 70 | 71 | if (config) { 72 | _thisPlaylistSession.config.targetDuration = config.targetDuration || _thisPlaylistSession.config.targetDuration; 73 | _thisPlaylistSession.config.windowLength = config.windowLength || _thisPlaylistSession.config.windowLength; 74 | 75 | debug('Constructing PlaylistSession with custom config. new PlaylistSession:', _thisPlaylistSession); 76 | } 77 | }; 78 | 79 | /** 80 | * playlistSessionFromStateObject 81 | * returns an instance of PlaylistSession with properties from state object 82 | */ 83 | _thisHLSKit.playlistSessionFromStateObject = function playlistSessionFromStateObject(object) { 84 | var playlistSession; 85 | 86 | debug('Initializing PlaylistSession from state object:', object); 87 | debug('Validating PlaylistSession state object'); 88 | 89 | /** 90 | * validate playlist session properties 91 | */ 92 | assert(_.isArray(object.mediaSegments), 'mediaSegments property must be an array'); 93 | 94 | assert(_.isObject(object.config), 'config property must be an object'); 95 | assert(_.isNumber(object.config.targetDuration), 'config.targetDuration property must be a number'); 96 | assert(_.isNumber(object.config.windowLength), 'config.windowLength property must be a number'); 97 | 98 | assert(_.isObject(object.meta), 'meta property must be an object'); 99 | assert(_.isString(object.meta.id), 'meta.id property must be a string'); 100 | assert(_.isBoolean(object.meta.shouldBeAvailable), 'meta.shouldBeAvailable property must be a boolean'); 101 | assert(_.isBoolean(object.meta.isAvailable), 'meta.isAvailable property must be a boolean'); 102 | assert(_.isBoolean(object.meta.shouldFinish), 'meta.shouldFinish property must be a boolean'); 103 | assert(_.isBoolean(object.meta.isFinished), 'meta.isFinished property must be a boolean'); 104 | 105 | /** 106 | * validate media segment properties for each media segment in the array 107 | */ 108 | for (i in object.mediaSegments) { 109 | assert(_.isNumber(object.mediaSegments[i].duration), 'duration property of media segment must be a number'); 110 | assert(_.isNumber(object.mediaSegments[i].timeElapsed), 'timeElapsed property of media segment must be a number'); 111 | assert(_.isNumber(object.mediaSegments[i].mediaSequence), 'mediaSequence property of media segment must be a number'); 112 | assert(_.isNumber(object.mediaSegments[i].discontinuitySequence), 'discontinuitySequence property of media segment must be a number'); 113 | } 114 | 115 | debug('PlaylistSession state object is valid'); 116 | 117 | /** 118 | * create new playlist session 119 | */ 120 | debug('creating new PlaylistSession'); 121 | playlistSession = new _thisHLSKit.PlaylistSession(object.meta.id, object.config); 122 | 123 | /** 124 | * set playlist session's mediaSegments and meta properties 125 | */ 126 | debug('setting PlaylistSession\'s `mediaSegments` and `meta` properties'); 127 | playlistSession.mediaSegments = object.mediaSegments; 128 | playlistSession.meta = object.meta; 129 | 130 | debug('created PlaylistSession from state object. PlaylistSession:', playlistSession); 131 | 132 | return playlistSession; 133 | }; 134 | 135 | /** 136 | * appendMP4 137 | * instance method of PlaylistSession 138 | * appends an MP4 to a playlist session, outputting a variant segment, 139 | * and serializing a variant playlist for each output variant 140 | * 141 | * @param path 142 | * @param options (optional) 143 | * @param callback 144 | * 145 | * options properties: 146 | * mediaSequence 147 | */ 148 | _thisHLSKit.PlaylistSession.prototype.appendMP4 = function appendMP4(path, options, callback) { 149 | var _thisPlaylistSession = this; 150 | 151 | debug('Appending MP4 to PlaylistSession...'); 152 | 153 | /** 154 | * If options.mediaSequence isn't passed, it's the number of mediaSegments in 155 | * this PlaylistSession 156 | */ 157 | options = { 158 | mediaSequence: options.mediaSequence || _thisPlaylistSession.mediaSegments.length, 159 | }; 160 | 161 | debug('Calculated mediaSequence:', options.mediaSequence); 162 | 163 | async.series([ 164 | function calculateDiscontinuitySequenceAndITSOffset(next) { 165 | var previousMediaSegment = _thisPlaylistSession.mediaSegments[_thisPlaylistSession.mediaSegments.length - 1]; 166 | 167 | /** 168 | * set options.discontinuitySequence & options.itsoffset 169 | */ 170 | if (_thisPlaylistSession.mediaSegments.length < 1) { 171 | debug('This is the first media segment'); 172 | 173 | /** 174 | * This is the first media segment. 175 | * It's discontinuitySequence is equal to 0 176 | */ 177 | options.discontinuitySequence = 0; 178 | options.itsoffset = 0; 179 | } else if (previousMediaSegment && previousMediaSegment.mediaSequence === (options.mediaSequence - 1) && previousMediaSegment.discontinuitySequence != -1) { 180 | debug('This is the next media segment'); 181 | 182 | /** 183 | * This media segment is the one that should come immediately 184 | * after the media segment that was received immediately before it. 185 | * It's discontinuitySequence is equal to the discontinuitySequence of the segment before it 186 | */ 187 | options.discontinuitySequence = previousMediaSegment.discontinuitySequence; 188 | options.itsoffset = previousMediaSegment.timeElapsed; 189 | } else { 190 | debug('This IS NOT the next media segment'); 191 | 192 | /** 193 | * This media segment IS NOT the one that should come immediately 194 | * after the media segment that was received immediately before it. 195 | * CHECK: the media segment that was received immediately before it 196 | * If the discontinuitySequence of the segment being checked is -1: 197 | * * CHECK the media segment that received immediately before the media segment being checked 198 | * If the discontinuitySequence of the segment being checked IS NOT -1: 199 | * * If its mediaSequence - the mediaSequence of the segment being checked is >= 1: 200 | * * * its discontinuitySequence is that of the segment being checked + 1 201 | * * * its itsoffset is 0 202 | * * If its mediaSequence - the mediaSequence of the segment being checked is < 1: 203 | * * * its discontinuitySequence is -1 204 | * * * its itsoffset is 0 205 | */ 206 | for (var len = _thisPlaylistSession.mediaSegments.length, i = len - 1, complete = false; i > len - 7 && i >= 0 && !complete; i--) { 207 | var mediaSegment = _thisPlaylistSession.mediaSegments[i]; 208 | 209 | debug('Checking against segment with mediaSequence:', mediaSegment.mediaSequence); 210 | 211 | if (mediaSegment.discontinuitySequence === -1) { 212 | debug('Previous segment (%s) discontinuitySequence is -1, check previous segment...', mediaSegment.mediaSequence); 213 | 214 | complete = false; 215 | } else { 216 | debug('Previous segment (%s) discontinuitySequence is NOT -1', mediaSegment.mediaSequence); 217 | 218 | if (options.mediaSequence - mediaSegment.mediaSequence >= 1) { 219 | debug('Discontinuity + append...'); 220 | 221 | options.discontinuitySequence = mediaSegment.discontinuitySequence + 1; 222 | options.itsoffset = 0; 223 | 224 | complete = true; 225 | } else { 226 | debug('Discontinuity & splice...') 227 | 228 | options.discontinuitySequence = -1; 229 | options.itsoffset = 0; 230 | 231 | complete = true; 232 | } 233 | 234 | } 235 | } 236 | } 237 | 238 | /** 239 | * ensure all media segment properties exist 240 | */ 241 | if (! _.isNumber(options.discontinuitySequence)) { 242 | options.discontinuitySequence = -1; 243 | } 244 | if (! _.isNumber(options.itsoffset)) { 245 | options.itsoffset = 0; 246 | } 247 | 248 | next(null); 249 | }, 250 | function transcodeMP4(next) { 251 | _thisPlaylistSession._transcodeMP4(path, options, next); 252 | }, 253 | function appendSegment(next) { 254 | _thisPlaylistSession._appendSegment(options, next); 255 | }, 256 | function serializePlaylistFiles(next) { 257 | _thisPlaylistSession._serializePlaylistFiles(next) 258 | } 259 | ], callback); 260 | }; 261 | 262 | /** 263 | * _transcodeMP4 264 | * private instance method of PlaylistSessiopn 265 | * transcodes an MP4 that's being appended to the playlist session, 266 | * outputting a variant segment for each output variant, and 267 | * outputting a cover image if it's the first segment OR its mediaSequence is 0, 268 | * writes generated files to disk, and calls callback 269 | * 270 | * @param path 271 | * @param options (optional; passed in from public method appendMP4) 272 | * @param callback 273 | * 274 | * options properties: 275 | * mediaSequence 276 | * discontinuitySequence 277 | * itsoffset 278 | */ 279 | _thisHLSKit.PlaylistSession.prototype._transcodeMP4 = function _transcodeMP4(path, options, callback) { 280 | var _thisPlaylistSession = this, 281 | AVConvCommand; 282 | 283 | debug('Appending MP4 to PlaylistSession... path: %s options:', path, options); 284 | 285 | /** 286 | * Create AVConvCommand for this media segment 287 | */ 288 | AVConvCommand = _thisPlaylistSession._createAVConvCommand(path, options); 289 | 290 | /** 291 | * Execute AVConvCommand, and then callback 292 | */ 293 | debug('Executing AVConvCommand...'); 294 | 295 | exec(AVConvCommand, function(error, stdout, stderr) { 296 | debug('avconv stdout:', stdout); 297 | debug('avconv stderr:', stderr); 298 | 299 | callback(error); 300 | }); 301 | }; 302 | 303 | /** 304 | * _appendSegment 305 | * probes an output file of the segment for options.mediaSequence to get the segment's duration, 306 | * detects discontinuitySequence and timeElapsed, 307 | * and then appends segment object to PlaylistSession 308 | * 309 | * @param options 310 | * @param callback 311 | * 312 | * options properties: 313 | * mediaSequence 314 | * discontinuitySequence 315 | * itsoffset 316 | */ 317 | _thisHLSKit.PlaylistSession.prototype._appendSegment = function _appendSegment(options, callback) { 318 | var _thisPlaylistSession = this, 319 | AVProbeCommand, 320 | AVProbeResponse, 321 | segmentInfo; 322 | 323 | /** 324 | * Create AVProbeCommand for this media segment 325 | */ 326 | AVProbeCommand = _thisPlaylistSession._createAVProbeCommand(options); 327 | 328 | async.series([ 329 | function probeSegment(next) { 330 | 331 | /** 332 | * Execute AVProbeCommand, and then callback 333 | */ 334 | debug('Executing AVProbeCommand...'); 335 | 336 | exec(AVProbeCommand, function(error, stdout, stderr) { 337 | debug('avprobe stdout:', stdout); 338 | debug('avprobe stderr:', stderr); 339 | 340 | AVProbeResponse = stdout; 341 | 342 | next(error); 343 | }); 344 | }, 345 | function parseAVProbeResponse(next) { 346 | debug('Parsing avprobe response...'); 347 | 348 | try { 349 | segmentInfo = JSON.parse(AVProbeResponse); 350 | } catch (error) { 351 | debug('Error parsing avprobe response:', error); 352 | 353 | next(error); 354 | return; 355 | } 356 | 357 | debug('Parsed avprobe response:', segmentInfo); 358 | 359 | next(null); 360 | }, 361 | function appendSegment(next) { 362 | var thisMediaSegment, 363 | audioStream; 364 | 365 | /** 366 | * Set options.duration to audio stream duration 367 | */ 368 | try { 369 | assert(Array.isArray(segmentInfo.streams), 'segment info contains no streams'); 370 | audioStream = segmentInfo.streams[1]; 371 | options.duration = Number(audioStream.duration); 372 | } catch (error) { 373 | debug('Error getting segment duration:', error); 374 | 375 | next(error); 376 | return; 377 | } 378 | 379 | debug('Appending segment to PlaylistSession. options:', options); 380 | 381 | /** 382 | * build this media segment 383 | */ 384 | thisMediaSegment = { 385 | mediaSequence: options.mediaSequence, 386 | discontinuitySequence: options.discontinuitySequence, 387 | timeElapsed: options.itsoffset + options.duration, 388 | duration: options.duration 389 | }; 390 | 391 | /** 392 | * append to PlaylistSession 393 | */ 394 | _thisPlaylistSession.mediaSegments.push(thisMediaSegment); 395 | 396 | /** 397 | * if this PlaylistSession has config.windowLength segments, it should be available 398 | */ 399 | if (_thisPlaylistSession.mediaSegments.length >= _thisPlaylistSession.config.windowLength) { 400 | _thisPlaylistSession.meta.shouldBeAvailable = true; 401 | } 402 | 403 | next(null); 404 | } 405 | ], callback); 406 | }; 407 | 408 | /** 409 | * _createAVConvCommand 410 | * returns avconv command used to transcode a given media segment 411 | * for this PlaylistSession (given its HLSKit output variants and cover images) 412 | * 413 | * @param path 414 | * @param options 415 | * 416 | * @returns AVConvCommand 417 | * 418 | * options properties: 419 | * mediaSequence 420 | * discontinuitySequence 421 | * itsoffset 422 | */ 423 | _thisHLSKit.PlaylistSession.prototype._createAVConvCommand = function _createAVConvCommand(path, options) { 424 | var _thisPlaylistSession = this, 425 | AVConvCommand; 426 | 427 | debug('Creating avconv command'); 428 | 429 | /** 430 | * Create avconv command 431 | */ 432 | AVConvCommand = 'avconv -y -loglevel error ' 433 | + '-itsoffset '+ options.itsoffset +' ' 434 | + '-i '+ path +' '; 435 | 436 | /** 437 | * Add variant output options for each variant 438 | */ 439 | for (i in _thisHLSKit._outputVariants) { 440 | var variant = _thisHLSKit._outputVariants[i], 441 | variantOutputOptions = '-strict experimental -f mpegts '; 442 | 443 | /** 444 | * add audio options 445 | */ 446 | variantOutputOptions += '-acodec libfaac ' 447 | + '-ar '+ _thisHLSKit.config.audioOutputOptions.sampleRate +' ' 448 | + '-b:a '+ _thisHLSKit.config.audioOutputOptions.bitrate +' '; 449 | 450 | /** 451 | * add variant video options 452 | */ 453 | if (! variant.config.skipVideo) { 454 | variantOutputOptions += '-vcodec libx264 ' 455 | + '-bsf:v h264_mp4toannexb ' 456 | + '-b:v '+ variant.config.bitrate +' ' 457 | + '-s '+ variant.config.resolution +' ' 458 | + '-r '+ variant.config.frameRate +' ' 459 | + '-g '+ variant.config.keyframeInterval +' '; 460 | } else { 461 | variantOutputOptions += '-vn '; 462 | } 463 | 464 | /** 465 | * add variant output file path 466 | */ 467 | variantOutputOptions += _thisHLSKit.config.workDirectory +'/'+ _thisPlaylistSession.meta.id +'_'+ variant.name +'_'+ options.mediaSequence +'.ts '; 468 | 469 | AVConvCommand += variantOutputOptions; 470 | }; 471 | 472 | /** 473 | * If options.mediaSequence is 0 and there is a cover image, 474 | * add cover image output options for each cover image 475 | */ 476 | if (options.mediaSequence === 0 && Array(_thisHLSKit._coverImages).length > 0) { 477 | for (i in _thisHLSKit._coverImages) { 478 | var coverImage = _thisHLSKit._coverImages[i], 479 | coverImageOutputOptions = '-strict experimental -ss 0 -vframes 1 ' 480 | + '-s '+ coverImage.config.resolution +' ' 481 | + _thisHLSKit.config.workDirectory +'/'+ _thisPlaylistSession.meta.id +'_'+ coverImage.name +'.png '; 482 | 483 | AVConvCommand += coverImageOutputOptions; 484 | } 485 | } 486 | 487 | debug('Created avconv command:', AVConvCommand); 488 | 489 | return AVConvCommand; 490 | }; 491 | 492 | /** 493 | * _createAVProbeCommand 494 | * returns avprobe command used to get information about a given media segment 495 | * for this PlaylistSession (given its HLSKit output variants and cover images) 496 | * 497 | * @param path 498 | * @param options 499 | * 500 | * 501 | * @return AVProbeCommand 502 | */ 503 | _thisHLSKit.PlaylistSession.prototype._createAVProbeCommand = function _createAVProbeCommand(options) { 504 | var _thisPlaylistSession = this, 505 | AVProbeCommand; 506 | 507 | debug('Creating avprobe command...'); 508 | 509 | AVProbeCommand = 'avprobe -of json -show_streams -show_format -loglevel panic ' 510 | + _thisHLSKit.config.workDirectory +'/'+ _thisPlaylistSession.meta.id +'_'+ _thisHLSKit._outputVariants[0].name +'_'+ options.mediaSequence +'.ts'; 511 | 512 | debug('Created avprobe command:', AVProbeCommand); 513 | 514 | return AVProbeCommand; 515 | }; 516 | 517 | /** 518 | * _serializePlaylistFiles 519 | * private instance method of PlaylistSession 520 | * serializes master playlists (if playlistSession isn't yet available), 521 | * and variant playlists for each output variant, 522 | * writes generated files to disk, and calls callback 523 | * 524 | * @param callback 525 | */ 526 | _thisHLSKit.PlaylistSession.prototype._serializePlaylistFiles = function _serializePlaylistFiles(callback) { 527 | var _thisPlaylistSession = this; 528 | 529 | debug('Serializing playlist files for PlaylistSession:', _thisPlaylistSession); 530 | 531 | async.parallel([ 532 | function serializeMasterPlaylists(next) { 533 | 534 | /** 535 | * If this playlist session isn't available yet, 536 | * serialize its master playlist files 537 | */ 538 | if (! _thisPlaylistSession.meta.isAvailable) { 539 | _thisPlaylistSession._serializeMasterPlaylists(next); 540 | } else { 541 | next(null); 542 | } 543 | }, 544 | function serializeLivePlaylists(next) { 545 | _thisPlaylistSession._serializeLivePlaylists(next); 546 | }, 547 | function serializeReplayPlaylists(next) { 548 | _thisPlaylistSession._serializeReplayPlaylists(next); 549 | } 550 | ], callback); 551 | }; 552 | 553 | /** 554 | * _serializeMasterPlaylists 555 | * serializes and saves live and replay master playlists 556 | * that list live and replay variant playlists for each 557 | * output variant in this HLSKit instance 558 | * 559 | * @param callback 560 | */ 561 | _thisHLSKit.PlaylistSession.prototype._serializeMasterPlaylists = function _serializeMasterPlaylists(callback) { 562 | var _thisPlaylistSession = this, 563 | liveMasterPlaylist, 564 | replayMasterPlaylist; 565 | 566 | liveMasterPlaylist = replayMasterPlaylist = '#EXTM3U\n'; 567 | 568 | debug('Serializing master playlists...'); 569 | 570 | for (i in _thisHLSKit._outputVariants) { 571 | var variant = _thisHLSKit._outputVariants[i], 572 | bandwidth; 573 | 574 | if (variant.config.bitrate && variant.config.frameRate) { 575 | 576 | /** 577 | * Video variant bandwidth is equal to 578 | * ((VariantBitrate + AudioBitrate) * VariantFrameRate) + StandardDeviation. 579 | * StandardDeviation is 0.09 580 | */ 581 | bandwidth = ((variant.config.bitrate + _thisHLSKit.config.audioOutputOptions.bitrate) * variant.config.frameRate) * 0.09; 582 | } else { 583 | bandwidth = 64000; 584 | } 585 | 586 | liveMasterPlaylist += '#EXT-X-STREAM-INF:BANDWIDTH='+ bandwidth +'\n'; 587 | replayMasterPlaylist += '#EXT-X-STREAM-INF:BANDWIDTH='+ bandwidth +'\n'; 588 | 589 | liveMasterPlaylist += variant.name +'/live.m3u8\n'; 590 | replayMasterPlaylist += variant.name +'/replay.m3u8\n'; 591 | } 592 | 593 | async.parallel([ 594 | function writeLiveMasterPlaylist(next) { 595 | fs.writeFile(_thisHLSKit.config.workDirectory +'/'+ _thisPlaylistSession.meta.id +'_live.m3u8', liveMasterPlaylist, next); 596 | }, 597 | function writeReplayMasterPlaylist(next) { 598 | fs.writeFile(_thisHLSKit.config.workDirectory +'/'+ _thisPlaylistSession.meta.id +'_replay.m3u8', replayMasterPlaylist, next); 599 | }, 600 | ], callback); 601 | }; 602 | 603 | /** 604 | * _serializeLivePlaylists 605 | * serializes and saves live variant playlists for 606 | * each output variant in this HLSKit instance 607 | * 608 | * @param callback 609 | */ 610 | _thisHLSKit.PlaylistSession.prototype._serializeLivePlaylists = function _serializeLivePlaylists(callback) { 611 | var _thisPlaylistSession = this, 612 | liveWindow = [], 613 | liveWindowMediaSequence = 0, 614 | liveWindowDiscontinuitySequence = 0, 615 | absolutePositionOfFirstMediaSegmentInLiveWindow = 0, 616 | livePlaylist; 617 | 618 | debug('Serializing live variant playlists...'); 619 | 620 | livePlaylist = '#EXTM3U\n' 621 | + '#EXT-X-VERSION:4\n' 622 | + '#EXT-X-ALLOW-CACHE:NO\n' 623 | + '#EXT-X-TARGETDURATION:'+ _thisPlaylistSession.config.targetDuration +'\n'; 624 | 625 | /** 626 | * Build Live Window 627 | * Starting from the end of media segments, 628 | * iterate backward and yield this PlaylistSession's windowLength 629 | * amount of media segments with discontinuitySequence != -1 630 | */ 631 | for (var len = _thisPlaylistSession.mediaSegments.length, i = len - 1, j = 0; i >= 0 && j < _thisPlaylistSession.config.windowLength; i--) { 632 | var thisMediaSegment = _thisPlaylistSession.mediaSegments[i]; 633 | 634 | debug('Checking if media segment should be in live window. media segment:', thisMediaSegment); 635 | 636 | if (thisMediaSegment.discontinuitySequence != -1) { 637 | debug('Pushing segment into liveWindow. segment:', thisMediaSegment); 638 | 639 | /** 640 | * If this is the first media segment that will go into the live 641 | * window, set the absolute position of first media segment in live window 642 | * for later use in calculating liveWindowMediaSequence 643 | */ 644 | if (liveWindow.length === _thisPlaylistSession.config.windowLength - 1) { 645 | absolutePositionOfFirstMediaSegmentInLiveWindow = i; 646 | 647 | debug('First media segment in live window has absolute position:', absolutePositionOfFirstMediaSegmentInLiveWindow); 648 | } 649 | 650 | liveWindow.unshift(thisMediaSegment); 651 | j++; 652 | } 653 | } 654 | 655 | /** 656 | * set liveWindowDiscontinuitySequence 657 | * equal to the discontinuity sequence of the first media segment in the live window 658 | */ 659 | liveWindowDiscontinuitySequence = liveWindow[0].discontinuitySequence; 660 | 661 | /** 662 | * set liveWindowMediaSequence 663 | * if the PlaylistSession does not have more media segments than its window length, 664 | * live window media sequence is 0, otherwise live window media sequence is 665 | * equal to the number of media segments that appear before the first media segment 666 | * in the live window whose discontinuitySequence != -1 667 | */ 668 | debug('_thisPlaylistSession.mediaSegments.length:', _thisPlaylistSession.mediaSegments.length); 669 | debug('_thisPlaylistSession.config.windowLength:', _thisPlaylistSession.config.windowLength); 670 | 671 | if (_thisPlaylistSession.mediaSegments.length <= _thisPlaylistSession.config.windowLength) { 672 | debug('PlaylistSession segments than its window length... liveWindowMediaSequence = 0'); 673 | 674 | liveWindowMediaSequence = 0; 675 | } else { 676 | debug('live window has more segments than its maximum length. calculating liveWindowMediaSequence...'); 677 | 678 | for (var i = absolutePositionOfFirstMediaSegmentInLiveWindow - 1; i >= 0; i--) { 679 | var thisMediaSegment = _thisPlaylistSession.mediaSegments[i]; 680 | 681 | if (thisMediaSegment.discontinuitySequence != -1) { 682 | liveWindowMediaSequence++; 683 | } 684 | } 685 | } 686 | 687 | debug('liveWindow:', liveWindow); 688 | debug('liveWindowMediaSequence:', liveWindowMediaSequence); 689 | debug('liveWindowDiscontinuitySequence:', liveWindowDiscontinuitySequence); 690 | 691 | /** 692 | * Render sliding-window live playlist 693 | */ 694 | livePlaylist += '#EXT-X-MEDIA-SEQUENCE:'+ liveWindowMediaSequence +'\n' 695 | + '#EXT-X-DISCONTINUITY-SEQUENCE:'+ liveWindowDiscontinuitySequence +'\n'; 696 | 697 | /** 698 | * List each segment in the live window 699 | */ 700 | for (var i = 0; i < liveWindow.length; i++) { 701 | var thisMediaSegment = liveWindow[i], 702 | nextMediaSegment = (i < liveWindow.length - 1 ? liveWindow[(i+1)] : null); 703 | 704 | thisMediaSegment.duration = (_.isNaN(thisMediaSegment.duration) || thisMediaSegment.duration === NaN || !_.isNumber(thisMediaSegment.duration) ? 0 : thisMediaSegment.duration); 705 | 706 | livePlaylist += '#EXTINF:'+ thisMediaSegment.duration +',\n' 707 | + 'fileSequence'+ thisMediaSegment.mediaSequence +'.ts\n'; 708 | 709 | debug('thisMediaSegment (index %s):', i, thisMediaSegment); 710 | debug('nextMediaSegment (index %s):', i+1, nextMediaSegment); 711 | 712 | /** 713 | * If next media segment's discontinuity sequence is different from that of this media segment 714 | * apply to the next media segment an #EXT-X-DISCONTINUITY tag 715 | */ 716 | if (nextMediaSegment && nextMediaSegment.discontinuitySequence != thisMediaSegment.discontinuitySequence) { 717 | livePlaylist += '#EXT-X-DISCONTINUITY\n'; 718 | } 719 | } 720 | 721 | /** 722 | * End live playlist if it should finish 723 | */ 724 | if (_thisPlaylistSession.meta.shouldFinish) { 725 | livePlaylist += '#EXT-X-ENDLIST\n'; 726 | } 727 | 728 | debug('Live playlist: \n\n%s\n\n', livePlaylist); 729 | 730 | /** 731 | * Save a live playlist file for each output variant 732 | */ 733 | debug('Saving live playlist files...'); 734 | 735 | async.each(_thisHLSKit._outputVariants, function saveLivePlaylistFile(variant, next) { 736 | var livePlaylistFilePath = _thisHLSKit.config.workDirectory +'/' 737 | + _thisPlaylistSession.meta.id 738 | +'_'+ variant.name 739 | +'_live.m3u8'; 740 | 741 | debug('Saving live playlist file at path:', livePlaylistFilePath); 742 | 743 | fs.writeFile(livePlaylistFilePath, livePlaylist, next); 744 | }, callback); 745 | }; 746 | 747 | /** 748 | * _serializeReplayPlaylists 749 | * serializes and saves replay variant playlists for 750 | * each output variant in this HLSKit instance 751 | * 752 | * @param callback 753 | */ 754 | _thisHLSKit.PlaylistSession.prototype._serializeReplayPlaylists = function _serializeReplayPlaylists(callback) { 755 | var _thisPlaylistSession = this, 756 | replayPlaylistSegments, 757 | replayPlaylist; 758 | 759 | /** 760 | * sort segments 761 | */ 762 | debug('Sorting segments...'); 763 | 764 | replayPlaylistSegments = _thisPlaylistSession.mediaSegments.slice(0).sort(function sortSegments(a, b) { 765 | return (a.mediaSequence < b.mediaSequence ? -1 : 1); 766 | }); 767 | 768 | /** 769 | * serialize replay variant playlists 770 | */ 771 | debug('Serializing replay variant playlists...'); 772 | 773 | replayPlaylist = '#EXTM3U\n' 774 | + '#EXT-X-VERSION:4\n' 775 | + '#EXT-X-ALLOW-CACHE:YES\n' 776 | + '#EXT-X-TARGETDURATION:'+ _thisPlaylistSession.config.targetDuration +'\n'; 777 | 778 | for (var i = 0; i < replayPlaylistSegments.length; i++) { 779 | var thisMediaSegment = replayPlaylistSegments[i], 780 | nextMediaSegment = (i < replayPlaylistSegments.length - 1 ? replayPlaylistSegments[(i+1)] : null); 781 | 782 | if (_.isNumber(thisMediaSegment.duration)) { 783 | replayPlaylist += '#EXTINF:'+ thisMediaSegment.duration +',\n' 784 | + 'fileSequence'+ thisMediaSegment.mediaSequence +'.ts\n'; 785 | } else { 786 | debug('Duration of media segment with mediaSequence %s IS NOT a number.', thisMediaSegment.mediaSequence); 787 | } 788 | 789 | /** 790 | * If next media segment's discontinuity sequence is different from that of this media segment 791 | * apply to the next media segment an #EXT-X-DISCONTINUITY tag 792 | */ 793 | if (nextMediaSegment && nextMediaSegment.discontinuitySequence != thisMediaSegment.discontinuitySequence) { 794 | replayPlaylist += '#EXT-X-DISCONTINUITY\n'; 795 | } 796 | } 797 | 798 | /** 799 | * end replay playlist 800 | */ 801 | replayPlaylist += '#EXT-X-ENDLIST\n'; 802 | 803 | debug('Replay playlist: \n\n%s\n\n', replayPlaylist); 804 | 805 | /** 806 | * Save a replay playlist file for each output variant 807 | */ 808 | debug('Saving replay playlist files...'); 809 | 810 | async.each(_thisHLSKit._outputVariants, function saveReplayPlaylistFile(variant, next) { 811 | var replayPlaylistFilePath = _thisHLSKit.config.workDirectory +'/' 812 | + _thisPlaylistSession.meta.id 813 | +'_'+ variant.name 814 | +'_replay.m3u8'; 815 | 816 | debug('Saving replay playlist file at path:', replayPlaylistFilePath); 817 | 818 | fs.writeFile(replayPlaylistFilePath, replayPlaylist, next); 819 | }, callback); 820 | }; 821 | 822 | /** 823 | * serializeStateObject 824 | * instance method of PlaylistSession 825 | * serializes playlistSession instance into an object 826 | * with only public properties (no instance methods) 827 | * 828 | * @returns playlistSession 829 | */ 830 | _thisHLSKit.PlaylistSession.prototype.serializeStateObject = function serializeStateObject() { 831 | var _thisPlaylistSession = this, 832 | stateObject; 833 | 834 | debug('Serializing state object'); 835 | 836 | stateObject = { 837 | mediaSegments: _thisPlaylistSession.mediaSegments, 838 | config: _thisPlaylistSession.config, 839 | meta: _thisPlaylistSession.meta 840 | }; 841 | 842 | debug('PlaylistSession:', _thisPlaylistSession); 843 | debug('stateObject:', stateObject); 844 | 845 | return stateObject; 846 | }; 847 | 848 | }; 849 | 850 | /** 851 | * addOutputVariant 852 | * instance method of HLSKit 853 | * adds an output variant to an instance of HLSKit 854 | * any MP4 appended to a PlaylistSession in this instance of HLSKit will be transcoded, 855 | * and a playlist file will be serialized for this output variant as configured 856 | * 857 | * @param name 858 | * @param config 859 | * 860 | * config properties: 861 | * skipVideo 862 | * bitrate 863 | * frameRate 864 | * keyframeInterval 865 | * resolution ([width]x[height]) 866 | * 867 | * IMPORTANT: If config.skipVideo is not true, a value must be passed for 868 | * config.bitrate, config.frameRate, config.keyframeInterval, and config.resolution 869 | */ 870 | HLSKit.prototype.addOutputVariant = function addOutputVariant(name, config) { 871 | var _thisHLSKit = this; 872 | 873 | debug('Adding output variant to HLSKit instance... variant name: %s config:', name, config); 874 | 875 | _thisHLSKit._outputVariants.push({ 876 | name: name, 877 | config: config 878 | }); 879 | 880 | debug('Added output variant to HLSKit instance. variant name: %s HLSKit:', name, _thisHLSKit); 881 | }; 882 | 883 | /** 884 | * addCoverImage 885 | * instance method of HLSKit 886 | * adds a cover image to this instance of HLSKit causing a 887 | * cover image to be saved upon the first append to its any 888 | * instance of PlaylistSession 889 | * 890 | * @param name 891 | * @param config 892 | * 893 | * config properties: 894 | * resolution ([width]x[height]) 895 | */ 896 | HLSKit.prototype.addCoverImage = function addCoverImage(name, config) { 897 | var _thisHLSKit = this; 898 | 899 | debug('Adding cover image output to HLSKit instance... cover image name: %s config:', name, config); 900 | 901 | _thisHLSKit._coverImages.push({ 902 | name: name, 903 | config: config 904 | }); 905 | 906 | debug('Added cover image output to HLSKit instance. cover image name: %s HLSKit:', name, _thisHLSKit); 907 | }; 908 | 909 | module.exports = HLSKit; 910 | -------------------------------------------------------------------------------- /node_modules/async/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Caolan McMahon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /node_modules/async/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async", 3 | "repo": "caolan/async", 4 | "description": "Higher-order functions and common patterns for asynchronous code", 5 | "version": "0.1.23", 6 | "keywords": [], 7 | "dependencies": {}, 8 | "development": {}, 9 | "main": "lib/async.js", 10 | "scripts": [ "lib/async.js" ] 11 | } 12 | -------------------------------------------------------------------------------- /node_modules/async/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 \n\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [map](#map)\n* [filter](#filter)\n* [reject](#reject)\n* [reduce](#reduce)\n* [detect](#detect)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n\n\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n\n\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err) which must be called once it has \n completed. If no error has occured, the callback should be run without \n arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, transformed) which must be called once \n it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array of the\n transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], 1, fs.stat, function(err, results){\n // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n array to produce the next step in the reduction. The iterator is passed a\n callback(err, reduction) which accepts an optional error as its first \n argument, and the state of the reduction as the second. If an error is \n passed to the callback, the reduction is stopped and the main callback is \n immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n // pointless async:\n process.nextTick(function(){\n callback(null, memo + item)\n });\n}, function(err, result){\n // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n the first item in the array that passes the truth test (iterator) or the\n value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, sortValue) which must be called once it\n has completed with an error (which can be null) and a value to use as the sort\n criteria.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is the items from\n the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n fs.stat(file, function(err, stats){\n callback(err, stats.mtime);\n });\n}, function(err, results){\n // results is now the original array of files sorted by\n // modified date\n});\n```\n\n---------------------------------------\n\n\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n true, or after all the iterator functions have finished. Result will be\n either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n The iterator is passed a callback(truthValue) which must be called with a \n boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n functions have finished. Result will be either true or false depending on\n the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n The iterator is passed a callback(err, results) which must be called once it \n has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n functions have finished, or an error has occurred. Results is an array containing\n the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n function(callback){\n // do some stuff ...\n callback(null, 'one');\n },\n function(callback){\n // do some more stuff ...\n callback(null, 'two');\n }\n],\n// optional callback\nfunction(err, results){\n // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n function(callback){\n setTimeout(function(){\n callback(null, 'one');\n }, 200);\n },\n function(callback){\n setTimeout(function(){\n callback(null, 'two');\n }, 100);\n }\n],\n// optional callback\nfunction(err, results){\n // the results array will equal ['one','two'] even though\n // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n one: function(callback){\n setTimeout(function(){\n callback(null, 1);\n }, 200);\n },\n two: function(callback){\n setTimeout(function(){\n callback(null, 2);\n }, 100);\n }\n},\nfunction(err, results) {\n // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n a callback(err, result) it must call on completion with an error (which can\n be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n have completed. This function gets a results array (or object) containing all \n the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n passed a callback(err) which must be called once it has completed with an \n optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n function () { return count < 5; },\n function (callback) {\n count++;\n setTimeout(callback, 1000);\n },\n function (err) {\n // 5 seconds have passed\n }\n);\n```\n\n---------------------------------------\n\n\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n callback(err, result1, result2, ...) it must call on completion. The first\n argument is an error (which can be null) and any further arguments will be \n passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n function(callback){\n callback(null, 'one', 'two');\n },\n function(arg1, arg2, callback){\n callback(null, 'three');\n },\n function(arg1, callback){\n // arg1 now equals 'three'\n callback(null, 'done');\n }\n], function (err, result) {\n // result now equals 'done' \n});\n```\n\n---------------------------------------\n\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n setTimeout(function () {\n callback(null, n + 1);\n }, 10);\n}\n\nfunction mul3(n, callback) {\n setTimeout(function () {\n callback(null, n * 3);\n }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n // result now equals 15\n});\n```\n\n---------------------------------------\n\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n buckets,\n async.applyEach([enableSearch, updateSchema]),\n callback\n);\n```\n\n---------------------------------------\n\n\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n task, which must call its callback(err) argument when finished, with an \n optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n run in parallel. This property can be changed after a queue is created to\n alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n console.log('hello ' + task.name);\n callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n queued tasks, which must call its callback(err) argument when finished, with \n an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n process per round. This property can be changed after a cargo is created to\n alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n once the worker has finished processing the task.\n instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n for(var i=0; i\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n readData: async.apply(fs.readFile, 'data.txt', 'utf-8');\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n readData: function(cb, results){\n fs.readFile('data.txt', 'utf-8', cb);\n }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n requirements, with the function itself the last item in the array. The key\n used for each function or array is used when specifying requirements. The \n function receives two arguments: (1) a callback(err, result) which must be \n called when finished, passing an error (which can be null) and the result of \n the function's execution, and (2) a results object, containing the results of\n the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n tasks have been completed. The callback will receive an error as an argument\n if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n \n\n__Example__\n\n```js\nasync.auto({\n get_data: function(callback){\n // async code to get some data\n },\n make_folder: function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n },\n write_file: ['get_data', 'make_folder', function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n callback(null, filename);\n }],\n email_link: ['write_file', function(callback, results){\n // once the file is written let's email a link to it...\n // results.write_file contains the filename returned by write_file.\n }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n function(callback){\n // async code to get some data\n },\n function(callback){\n // async code to create a directory to store a file in\n // this is run at the same time as getting the data\n }\n],\nfunction(err, results){\n async.series([\n function(callback){\n // once there is some data and the directory exists,\n // write the data to a file in the directory\n },\n function(callback){\n // once the file is written let's email a link to it...\n }\n ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n function(){ sys.p('one'); },\n function(){ sys.p('two'); },\n function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n async.apply(fs.writeFile, 'testfile1', 'test1'),\n async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n function(callback){\n fs.writeFile('testfile1', 'test1', callback);\n },\n function(callback){\n fs.writeFile('testfile2', 'test2', callback);\n }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n call_order.push('two');\n // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n callback(null, {\n id: 'user' + id\n })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n createUser(n, function(err, user) {\n next(err, user)\n })\n}, function(err, users) {\n // we should now have 5 users\n});\n```\n\n\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n results, it has all the arguments applied to it apart from the callback, and\n must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n // do something\n callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n // callback\n});\n```\n\n\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, 'hello ' + name);\n }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n setTimeout(function(){\n callback(null, {hello: name});\n }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n", 39 | "readmeFilename": "README.md", 40 | "_id": "async@0.2.9", 41 | "_from": "async@" 42 | } 43 | -------------------------------------------------------------------------------- /node_modules/debug/Readme.md: -------------------------------------------------------------------------------- 1 | # debug 2 | 3 | tiny node.js debugging utility modelled after node core's debugging technique. 4 | 5 | ## Installation 6 | 7 | ``` 8 | $ npm install debug 9 | ``` 10 | 11 | ## Usage 12 | 13 | With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility. 14 | 15 | Example _app.js_: 16 | 17 | ```js 18 | var debug = require('debug')('http') 19 | , http = require('http') 20 | , name = 'My App'; 21 | 22 | // fake app 23 | 24 | debug('booting %s', name); 25 | 26 | http.createServer(function(req, res){ 27 | debug(req.method + ' ' + req.url); 28 | res.end('hello\n'); 29 | }).listen(3000, function(){ 30 | debug('listening'); 31 | }); 32 | 33 | // fake worker of some kind 34 | 35 | require('./worker'); 36 | ``` 37 | 38 | Example _worker.js_: 39 | 40 | ```js 41 | var debug = require('debug')('worker'); 42 | 43 | setInterval(function(){ 44 | debug('doing some work'); 45 | }, 1000); 46 | ``` 47 | 48 | The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples: 49 | 50 | ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png) 51 | 52 | ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png) 53 | 54 | ## Millisecond diff 55 | 56 | When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. 57 | 58 | ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png) 59 | 60 | When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below: 61 | _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_ 62 | 63 | ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png) 64 | 65 | ## Conventions 66 | 67 | If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". 68 | 69 | ## Wildcards 70 | 71 | The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. 72 | 73 | You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:". 74 | 75 | ## Browser support 76 | 77 | Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. 78 | 79 | ```js 80 | a = debug('worker:a'); 81 | b = debug('worker:b'); 82 | 83 | setInterval(function(){ 84 | a('doing some work'); 85 | }, 1000); 86 | 87 | setInterval(function(){ 88 | a('doing some work'); 89 | }, 1200); 90 | ``` 91 | 92 | ## License 93 | 94 | (The MIT License) 95 | 96 | Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining 99 | a copy of this software and associated documentation files (the 100 | 'Software'), to deal in the Software without restriction, including 101 | without limitation the rights to use, copy, modify, merge, publish, 102 | distribute, sublicense, and/or sell copies of the Software, and to 103 | permit persons to whom the Software is furnished to do so, subject to 104 | the following conditions: 105 | 106 | The above copyright notice and this permission notice shall be 107 | included in all copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 110 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 111 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 112 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 113 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 114 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 115 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 116 | -------------------------------------------------------------------------------- /node_modules/debug/debug.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose `debug()` as the module. 4 | */ 5 | 6 | module.exports = debug; 7 | 8 | /** 9 | * Create a debugger with the given `name`. 10 | * 11 | * @param {String} name 12 | * @return {Type} 13 | * @api public 14 | */ 15 | 16 | function debug(name) { 17 | if (!debug.enabled(name)) return function(){}; 18 | 19 | return function(fmt){ 20 | fmt = coerce(fmt); 21 | 22 | var curr = new Date; 23 | var ms = curr - (debug[name] || curr); 24 | debug[name] = curr; 25 | 26 | fmt = name 27 | + ' ' 28 | + fmt 29 | + ' +' + debug.humanize(ms); 30 | 31 | // This hackery is required for IE8 32 | // where `console.log` doesn't have 'apply' 33 | window.console 34 | && console.log 35 | && Function.prototype.apply.call(console.log, console, arguments); 36 | } 37 | } 38 | 39 | /** 40 | * The currently active debug mode names. 41 | */ 42 | 43 | debug.names = []; 44 | debug.skips = []; 45 | 46 | /** 47 | * Enables a debug mode by name. This can include modes 48 | * separated by a colon and wildcards. 49 | * 50 | * @param {String} name 51 | * @api public 52 | */ 53 | 54 | debug.enable = function(name) { 55 | try { 56 | localStorage.debug = name; 57 | } catch(e){} 58 | 59 | var split = (name || '').split(/[\s,]+/) 60 | , len = split.length; 61 | 62 | for (var i = 0; i < len; i++) { 63 | name = split[i].replace('*', '.*?'); 64 | if (name[0] === '-') { 65 | debug.skips.push(new RegExp('^' + name.substr(1) + '$')); 66 | } 67 | else { 68 | debug.names.push(new RegExp('^' + name + '$')); 69 | } 70 | } 71 | }; 72 | 73 | /** 74 | * Disable debug output. 75 | * 76 | * @api public 77 | */ 78 | 79 | debug.disable = function(){ 80 | debug.enable(''); 81 | }; 82 | 83 | /** 84 | * Humanize the given `ms`. 85 | * 86 | * @param {Number} m 87 | * @return {String} 88 | * @api private 89 | */ 90 | 91 | debug.humanize = function(ms) { 92 | var sec = 1000 93 | , min = 60 * 1000 94 | , hour = 60 * min; 95 | 96 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; 97 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; 98 | if (ms >= sec) return (ms / sec | 0) + 's'; 99 | return ms + 'ms'; 100 | }; 101 | 102 | /** 103 | * Returns true if the given mode name is enabled, false otherwise. 104 | * 105 | * @param {String} name 106 | * @return {Boolean} 107 | * @api public 108 | */ 109 | 110 | debug.enabled = function(name) { 111 | for (var i = 0, len = debug.skips.length; i < len; i++) { 112 | if (debug.skips[i].test(name)) { 113 | return false; 114 | } 115 | } 116 | for (var i = 0, len = debug.names.length; i < len; i++) { 117 | if (debug.names[i].test(name)) { 118 | return true; 119 | } 120 | } 121 | return false; 122 | }; 123 | 124 | /** 125 | * Coerce `val`. 126 | */ 127 | 128 | function coerce(val) { 129 | if (val instanceof Error) return val.stack || val.message; 130 | return val; 131 | } 132 | 133 | // persist 134 | 135 | try { 136 | if (window.localStorage) debug.enable(localStorage.debug); 137 | } catch(e){} 138 | -------------------------------------------------------------------------------- /node_modules/debug/index.js: -------------------------------------------------------------------------------- 1 | if ('undefined' == typeof window) { 2 | module.exports = require('./lib/debug'); 3 | } else { 4 | module.exports = require('./debug'); 5 | } 6 | -------------------------------------------------------------------------------- /node_modules/debug/lib/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var tty = require('tty'); 6 | 7 | /** 8 | * Expose `debug()` as the module. 9 | */ 10 | 11 | module.exports = debug; 12 | 13 | /** 14 | * Enabled debuggers. 15 | */ 16 | 17 | var names = [] 18 | , skips = []; 19 | 20 | (process.env.DEBUG || '') 21 | .split(/[\s,]+/) 22 | .forEach(function(name){ 23 | name = name.replace('*', '.*?'); 24 | if (name[0] === '-') { 25 | skips.push(new RegExp('^' + name.substr(1) + '$')); 26 | } else { 27 | names.push(new RegExp('^' + name + '$')); 28 | } 29 | }); 30 | 31 | /** 32 | * Colors. 33 | */ 34 | 35 | var colors = [6, 2, 3, 4, 5, 1]; 36 | 37 | /** 38 | * Previous debug() call. 39 | */ 40 | 41 | var prev = {}; 42 | 43 | /** 44 | * Previously assigned color. 45 | */ 46 | 47 | var prevColor = 0; 48 | 49 | /** 50 | * Is stdout a TTY? Colored output is disabled when `true`. 51 | */ 52 | 53 | var isatty = tty.isatty(2); 54 | 55 | /** 56 | * Select a color. 57 | * 58 | * @return {Number} 59 | * @api private 60 | */ 61 | 62 | function color() { 63 | return colors[prevColor++ % colors.length]; 64 | } 65 | 66 | /** 67 | * Humanize the given `ms`. 68 | * 69 | * @param {Number} m 70 | * @return {String} 71 | * @api private 72 | */ 73 | 74 | function humanize(ms) { 75 | var sec = 1000 76 | , min = 60 * 1000 77 | , hour = 60 * min; 78 | 79 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; 80 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; 81 | if (ms >= sec) return (ms / sec | 0) + 's'; 82 | return ms + 'ms'; 83 | } 84 | 85 | /** 86 | * Create a debugger with the given `name`. 87 | * 88 | * @param {String} name 89 | * @return {Type} 90 | * @api public 91 | */ 92 | 93 | function debug(name) { 94 | function disabled(){} 95 | disabled.enabled = false; 96 | 97 | var match = skips.some(function(re){ 98 | return re.test(name); 99 | }); 100 | 101 | if (match) return disabled; 102 | 103 | match = names.some(function(re){ 104 | return re.test(name); 105 | }); 106 | 107 | if (!match) return disabled; 108 | var c = color(); 109 | 110 | function colored(fmt) { 111 | fmt = coerce(fmt); 112 | 113 | var curr = new Date; 114 | var ms = curr - (prev[name] || curr); 115 | prev[name] = curr; 116 | 117 | fmt = ' \u001b[9' + c + 'm' + name + ' ' 118 | + '\u001b[3' + c + 'm\u001b[90m' 119 | + fmt + '\u001b[3' + c + 'm' 120 | + ' +' + humanize(ms) + '\u001b[0m'; 121 | 122 | console.error.apply(this, arguments); 123 | } 124 | 125 | function plain(fmt) { 126 | fmt = coerce(fmt); 127 | 128 | fmt = new Date().toUTCString() 129 | + ' ' + name + ' ' + fmt; 130 | console.error.apply(this, arguments); 131 | } 132 | 133 | colored.enabled = plain.enabled = true; 134 | 135 | return isatty || process.env.DEBUG_COLORS 136 | ? colored 137 | : plain; 138 | } 139 | 140 | /** 141 | * Coerce `val`. 142 | */ 143 | 144 | function coerce(val) { 145 | if (val instanceof Error) return val.stack || val.message; 146 | return val; 147 | } 148 | -------------------------------------------------------------------------------- /node_modules/debug/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug", 3 | "version": "0.7.4", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/visionmedia/debug.git" 7 | }, 8 | "description": "small debugging utility", 9 | "keywords": [ 10 | "debug", 11 | "log", 12 | "debugger" 13 | ], 14 | "author": { 15 | "name": "TJ Holowaychuk", 16 | "email": "tj@vision-media.ca" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "mocha": "*" 21 | }, 22 | "main": "lib/debug.js", 23 | "browser": "./debug.js", 24 | "engines": { 25 | "node": "*" 26 | }, 27 | "files": [ 28 | "lib/debug.js", 29 | "debug.js", 30 | "index.js" 31 | ], 32 | "component": { 33 | "scripts": { 34 | "debug/index.js": "index.js", 35 | "debug/debug.js": "debug.js" 36 | } 37 | }, 38 | "readme": "# debug\n\n tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n , http = require('http')\n , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n debug(req.method + ' ' + req.url);\n res.end('hello\\n');\n}).listen(3000, function(){\n debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n \n ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n You can also exclude specific debuggers by prefixing them with a \"-\" character. For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", 39 | "readmeFilename": "Readme.md", 40 | "bugs": { 41 | "url": "https://github.com/visionmedia/debug/issues" 42 | }, 43 | "_id": "debug@0.7.4", 44 | "_from": "debug@" 45 | } 46 | -------------------------------------------------------------------------------- /node_modules/underscore/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative 2 | Reporters & Editors 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /node_modules/underscore/README.md: -------------------------------------------------------------------------------- 1 | __ 2 | /\ \ __ 3 | __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ 4 | /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\ 5 | \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ 6 | \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ 7 | \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ 8 | \ \____/ 9 | \/___/ 10 | 11 | Underscore.js is a utility-belt library for JavaScript that provides 12 | support for the usual functional suspects (each, map, reduce, filter...) 13 | without extending any core JavaScript objects. 14 | 15 | For Docs, License, Tests, and pre-packed downloads, see: 16 | http://underscorejs.org 17 | 18 | Underscore is an open-sourced component of DocumentCloud: 19 | https://github.com/documentcloud 20 | 21 | Many thanks to our contributors: 22 | https://github.com/jashkenas/underscore/contributors 23 | -------------------------------------------------------------------------------- /node_modules/underscore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underscore", 3 | "description": "JavaScript's functional programming helper library.", 4 | "homepage": "http://underscorejs.org", 5 | "keywords": [ 6 | "util", 7 | "functional", 8 | "server", 9 | "client", 10 | "browser" 11 | ], 12 | "author": { 13 | "name": "Jeremy Ashkenas", 14 | "email": "jeremy@documentcloud.org" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/jashkenas/underscore.git" 19 | }, 20 | "main": "underscore.js", 21 | "version": "1.5.2", 22 | "devDependencies": { 23 | "phantomjs": "1.9.0-1" 24 | }, 25 | "scripts": { 26 | "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true" 27 | }, 28 | "licenses": [ 29 | { 30 | "type": "MIT", 31 | "url": "https://raw.github.com/jashkenas/underscore/master/LICENSE" 32 | } 33 | ], 34 | "files": [ 35 | "underscore.js", 36 | "underscore-min.js", 37 | "LICENSE" 38 | ], 39 | "readme": " __\n /\\ \\ __\n __ __ ___ \\_\\ \\ __ _ __ ____ ___ ___ _ __ __ /\\_\\ ____\n /\\ \\/\\ \\ /' _ `\\ /'_ \\ /'__`\\/\\ __\\/ ,__\\ / ___\\ / __`\\/\\ __\\/'__`\\ \\/\\ \\ /',__\\\n \\ \\ \\_\\ \\/\\ \\/\\ \\/\\ \\ \\ \\/\\ __/\\ \\ \\//\\__, `\\/\\ \\__//\\ \\ \\ \\ \\ \\//\\ __/ __ \\ \\ \\/\\__, `\\\n \\ \\____/\\ \\_\\ \\_\\ \\___,_\\ \\____\\\\ \\_\\\\/\\____/\\ \\____\\ \\____/\\ \\_\\\\ \\____\\/\\_\\ _\\ \\ \\/\\____/\n \\/___/ \\/_/\\/_/\\/__,_ /\\/____/ \\/_/ \\/___/ \\/____/\\/___/ \\/_/ \\/____/\\/_//\\ \\_\\ \\/___/\n \\ \\____/\n \\/___/\n\nUnderscore.js is a utility-belt library for JavaScript that provides\nsupport for the usual functional suspects (each, map, reduce, filter...)\nwithout extending any core JavaScript objects.\n\nFor Docs, License, Tests, and pre-packed downloads, see:\nhttp://underscorejs.org\n\nUnderscore is an open-sourced component of DocumentCloud:\nhttps://github.com/documentcloud\n\nMany thanks to our contributors:\nhttps://github.com/jashkenas/underscore/contributors\n", 40 | "readmeFilename": "README.md", 41 | "bugs": { 42 | "url": "https://github.com/jashkenas/underscore/issues" 43 | }, 44 | "_id": "underscore@1.5.2", 45 | "_from": "underscore@" 46 | } 47 | -------------------------------------------------------------------------------- /node_modules/underscore/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /node_modules/underscore/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // Baseline setup 9 | // -------------- 10 | 11 | // Establish the root object, `window` in the browser, or `exports` on the server. 12 | var root = this; 13 | 14 | // Save the previous value of the `_` variable. 15 | var previousUnderscore = root._; 16 | 17 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | // Create quick reference variables for speed access to core prototypes. 24 | var 25 | push = ArrayProto.push, 26 | slice = ArrayProto.slice, 27 | concat = ArrayProto.concat, 28 | toString = ObjProto.toString, 29 | hasOwnProperty = ObjProto.hasOwnProperty; 30 | 31 | // All **ECMAScript 5** native function implementations that we hope to use 32 | // are declared here. 33 | var 34 | nativeForEach = ArrayProto.forEach, 35 | nativeMap = ArrayProto.map, 36 | nativeReduce = ArrayProto.reduce, 37 | nativeReduceRight = ArrayProto.reduceRight, 38 | nativeFilter = ArrayProto.filter, 39 | nativeEvery = ArrayProto.every, 40 | nativeSome = ArrayProto.some, 41 | nativeIndexOf = ArrayProto.indexOf, 42 | nativeLastIndexOf = ArrayProto.lastIndexOf, 43 | nativeIsArray = Array.isArray, 44 | nativeKeys = Object.keys, 45 | nativeBind = FuncProto.bind; 46 | 47 | // Create a safe reference to the Underscore object for use below. 48 | var _ = function(obj) { 49 | if (obj instanceof _) return obj; 50 | if (!(this instanceof _)) return new _(obj); 51 | this._wrapped = obj; 52 | }; 53 | 54 | // Export the Underscore object for **Node.js**, with 55 | // backwards-compatibility for the old `require()` API. If we're in 56 | // the browser, add `_` as a global object via a string identifier, 57 | // for Closure Compiler "advanced" mode. 58 | if (typeof exports !== 'undefined') { 59 | if (typeof module !== 'undefined' && module.exports) { 60 | exports = module.exports = _; 61 | } 62 | exports._ = _; 63 | } else { 64 | root._ = _; 65 | } 66 | 67 | // Current version. 68 | _.VERSION = '1.5.2'; 69 | 70 | // Collection Functions 71 | // -------------------- 72 | 73 | // The cornerstone, an `each` implementation, aka `forEach`. 74 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 75 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 76 | var each = _.each = _.forEach = function(obj, iterator, context) { 77 | if (obj == null) return; 78 | if (nativeForEach && obj.forEach === nativeForEach) { 79 | obj.forEach(iterator, context); 80 | } else if (obj.length === +obj.length) { 81 | for (var i = 0, length = obj.length; i < length; i++) { 82 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 83 | } 84 | } else { 85 | var keys = _.keys(obj); 86 | for (var i = 0, length = keys.length; i < length; i++) { 87 | if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; 88 | } 89 | } 90 | }; 91 | 92 | // Return the results of applying the iterator to each element. 93 | // Delegates to **ECMAScript 5**'s native `map` if available. 94 | _.map = _.collect = function(obj, iterator, context) { 95 | var results = []; 96 | if (obj == null) return results; 97 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 98 | each(obj, function(value, index, list) { 99 | results.push(iterator.call(context, value, index, list)); 100 | }); 101 | return results; 102 | }; 103 | 104 | var reduceError = 'Reduce of empty array with no initial value'; 105 | 106 | // **Reduce** builds up a single result from a list of values, aka `inject`, 107 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 108 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 109 | var initial = arguments.length > 2; 110 | if (obj == null) obj = []; 111 | if (nativeReduce && obj.reduce === nativeReduce) { 112 | if (context) iterator = _.bind(iterator, context); 113 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 114 | } 115 | each(obj, function(value, index, list) { 116 | if (!initial) { 117 | memo = value; 118 | initial = true; 119 | } else { 120 | memo = iterator.call(context, memo, value, index, list); 121 | } 122 | }); 123 | if (!initial) throw new TypeError(reduceError); 124 | return memo; 125 | }; 126 | 127 | // The right-associative version of reduce, also known as `foldr`. 128 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 129 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 130 | var initial = arguments.length > 2; 131 | if (obj == null) obj = []; 132 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 133 | if (context) iterator = _.bind(iterator, context); 134 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 135 | } 136 | var length = obj.length; 137 | if (length !== +length) { 138 | var keys = _.keys(obj); 139 | length = keys.length; 140 | } 141 | each(obj, function(value, index, list) { 142 | index = keys ? keys[--length] : --length; 143 | if (!initial) { 144 | memo = obj[index]; 145 | initial = true; 146 | } else { 147 | memo = iterator.call(context, memo, obj[index], index, list); 148 | } 149 | }); 150 | if (!initial) throw new TypeError(reduceError); 151 | return memo; 152 | }; 153 | 154 | // Return the first value which passes a truth test. Aliased as `detect`. 155 | _.find = _.detect = function(obj, iterator, context) { 156 | var result; 157 | any(obj, function(value, index, list) { 158 | if (iterator.call(context, value, index, list)) { 159 | result = value; 160 | return true; 161 | } 162 | }); 163 | return result; 164 | }; 165 | 166 | // Return all the elements that pass a truth test. 167 | // Delegates to **ECMAScript 5**'s native `filter` if available. 168 | // Aliased as `select`. 169 | _.filter = _.select = function(obj, iterator, context) { 170 | var results = []; 171 | if (obj == null) return results; 172 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 173 | each(obj, function(value, index, list) { 174 | if (iterator.call(context, value, index, list)) results.push(value); 175 | }); 176 | return results; 177 | }; 178 | 179 | // Return all the elements for which a truth test fails. 180 | _.reject = function(obj, iterator, context) { 181 | return _.filter(obj, function(value, index, list) { 182 | return !iterator.call(context, value, index, list); 183 | }, context); 184 | }; 185 | 186 | // Determine whether all of the elements match a truth test. 187 | // Delegates to **ECMAScript 5**'s native `every` if available. 188 | // Aliased as `all`. 189 | _.every = _.all = function(obj, iterator, context) { 190 | iterator || (iterator = _.identity); 191 | var result = true; 192 | if (obj == null) return result; 193 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 194 | each(obj, function(value, index, list) { 195 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 196 | }); 197 | return !!result; 198 | }; 199 | 200 | // Determine if at least one element in the object matches a truth test. 201 | // Delegates to **ECMAScript 5**'s native `some` if available. 202 | // Aliased as `any`. 203 | var any = _.some = _.any = function(obj, iterator, context) { 204 | iterator || (iterator = _.identity); 205 | var result = false; 206 | if (obj == null) return result; 207 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 208 | each(obj, function(value, index, list) { 209 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 210 | }); 211 | return !!result; 212 | }; 213 | 214 | // Determine if the array or object contains a given value (using `===`). 215 | // Aliased as `include`. 216 | _.contains = _.include = function(obj, target) { 217 | if (obj == null) return false; 218 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 219 | return any(obj, function(value) { 220 | return value === target; 221 | }); 222 | }; 223 | 224 | // Invoke a method (with arguments) on every item in a collection. 225 | _.invoke = function(obj, method) { 226 | var args = slice.call(arguments, 2); 227 | var isFunc = _.isFunction(method); 228 | return _.map(obj, function(value) { 229 | return (isFunc ? method : value[method]).apply(value, args); 230 | }); 231 | }; 232 | 233 | // Convenience version of a common use case of `map`: fetching a property. 234 | _.pluck = function(obj, key) { 235 | return _.map(obj, function(value){ return value[key]; }); 236 | }; 237 | 238 | // Convenience version of a common use case of `filter`: selecting only objects 239 | // containing specific `key:value` pairs. 240 | _.where = function(obj, attrs, first) { 241 | if (_.isEmpty(attrs)) return first ? void 0 : []; 242 | return _[first ? 'find' : 'filter'](obj, function(value) { 243 | for (var key in attrs) { 244 | if (attrs[key] !== value[key]) return false; 245 | } 246 | return true; 247 | }); 248 | }; 249 | 250 | // Convenience version of a common use case of `find`: getting the first object 251 | // containing specific `key:value` pairs. 252 | _.findWhere = function(obj, attrs) { 253 | return _.where(obj, attrs, true); 254 | }; 255 | 256 | // Return the maximum element or (element-based computation). 257 | // Can't optimize arrays of integers longer than 65,535 elements. 258 | // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) 259 | _.max = function(obj, iterator, context) { 260 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 261 | return Math.max.apply(Math, obj); 262 | } 263 | if (!iterator && _.isEmpty(obj)) return -Infinity; 264 | var result = {computed : -Infinity, value: -Infinity}; 265 | each(obj, function(value, index, list) { 266 | var computed = iterator ? iterator.call(context, value, index, list) : value; 267 | computed > result.computed && (result = {value : value, computed : computed}); 268 | }); 269 | return result.value; 270 | }; 271 | 272 | // Return the minimum element (or element-based computation). 273 | _.min = function(obj, iterator, context) { 274 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 275 | return Math.min.apply(Math, obj); 276 | } 277 | if (!iterator && _.isEmpty(obj)) return Infinity; 278 | var result = {computed : Infinity, value: Infinity}; 279 | each(obj, function(value, index, list) { 280 | var computed = iterator ? iterator.call(context, value, index, list) : value; 281 | computed < result.computed && (result = {value : value, computed : computed}); 282 | }); 283 | return result.value; 284 | }; 285 | 286 | // Shuffle an array, using the modern version of the 287 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 288 | _.shuffle = function(obj) { 289 | var rand; 290 | var index = 0; 291 | var shuffled = []; 292 | each(obj, function(value) { 293 | rand = _.random(index++); 294 | shuffled[index - 1] = shuffled[rand]; 295 | shuffled[rand] = value; 296 | }); 297 | return shuffled; 298 | }; 299 | 300 | // Sample **n** random values from an array. 301 | // If **n** is not specified, returns a single random element from the array. 302 | // The internal `guard` argument allows it to work with `map`. 303 | _.sample = function(obj, n, guard) { 304 | if (arguments.length < 2 || guard) { 305 | return obj[_.random(obj.length - 1)]; 306 | } 307 | return _.shuffle(obj).slice(0, Math.max(0, n)); 308 | }; 309 | 310 | // An internal function to generate lookup iterators. 311 | var lookupIterator = function(value) { 312 | return _.isFunction(value) ? value : function(obj){ return obj[value]; }; 313 | }; 314 | 315 | // Sort the object's values by a criterion produced by an iterator. 316 | _.sortBy = function(obj, value, context) { 317 | var iterator = lookupIterator(value); 318 | return _.pluck(_.map(obj, function(value, index, list) { 319 | return { 320 | value: value, 321 | index: index, 322 | criteria: iterator.call(context, value, index, list) 323 | }; 324 | }).sort(function(left, right) { 325 | var a = left.criteria; 326 | var b = right.criteria; 327 | if (a !== b) { 328 | if (a > b || a === void 0) return 1; 329 | if (a < b || b === void 0) return -1; 330 | } 331 | return left.index - right.index; 332 | }), 'value'); 333 | }; 334 | 335 | // An internal function used for aggregate "group by" operations. 336 | var group = function(behavior) { 337 | return function(obj, value, context) { 338 | var result = {}; 339 | var iterator = value == null ? _.identity : lookupIterator(value); 340 | each(obj, function(value, index) { 341 | var key = iterator.call(context, value, index, obj); 342 | behavior(result, key, value); 343 | }); 344 | return result; 345 | }; 346 | }; 347 | 348 | // Groups the object's values by a criterion. Pass either a string attribute 349 | // to group by, or a function that returns the criterion. 350 | _.groupBy = group(function(result, key, value) { 351 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value); 352 | }); 353 | 354 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 355 | // when you know that your index values will be unique. 356 | _.indexBy = group(function(result, key, value) { 357 | result[key] = value; 358 | }); 359 | 360 | // Counts instances of an object that group by a certain criterion. Pass 361 | // either a string attribute to count by, or a function that returns the 362 | // criterion. 363 | _.countBy = group(function(result, key) { 364 | _.has(result, key) ? result[key]++ : result[key] = 1; 365 | }); 366 | 367 | // Use a comparator function to figure out the smallest index at which 368 | // an object should be inserted so as to maintain order. Uses binary search. 369 | _.sortedIndex = function(array, obj, iterator, context) { 370 | iterator = iterator == null ? _.identity : lookupIterator(iterator); 371 | var value = iterator.call(context, obj); 372 | var low = 0, high = array.length; 373 | while (low < high) { 374 | var mid = (low + high) >>> 1; 375 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 376 | } 377 | return low; 378 | }; 379 | 380 | // Safely create a real, live array from anything iterable. 381 | _.toArray = function(obj) { 382 | if (!obj) return []; 383 | if (_.isArray(obj)) return slice.call(obj); 384 | if (obj.length === +obj.length) return _.map(obj, _.identity); 385 | return _.values(obj); 386 | }; 387 | 388 | // Return the number of elements in an object. 389 | _.size = function(obj) { 390 | if (obj == null) return 0; 391 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 392 | }; 393 | 394 | // Array Functions 395 | // --------------- 396 | 397 | // Get the first element of an array. Passing **n** will return the first N 398 | // values in the array. Aliased as `head` and `take`. The **guard** check 399 | // allows it to work with `_.map`. 400 | _.first = _.head = _.take = function(array, n, guard) { 401 | if (array == null) return void 0; 402 | return (n == null) || guard ? array[0] : slice.call(array, 0, n); 403 | }; 404 | 405 | // Returns everything but the last entry of the array. Especially useful on 406 | // the arguments object. Passing **n** will return all the values in 407 | // the array, excluding the last N. The **guard** check allows it to work with 408 | // `_.map`. 409 | _.initial = function(array, n, guard) { 410 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 411 | }; 412 | 413 | // Get the last element of an array. Passing **n** will return the last N 414 | // values in the array. The **guard** check allows it to work with `_.map`. 415 | _.last = function(array, n, guard) { 416 | if (array == null) return void 0; 417 | if ((n == null) || guard) { 418 | return array[array.length - 1]; 419 | } else { 420 | return slice.call(array, Math.max(array.length - n, 0)); 421 | } 422 | }; 423 | 424 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 425 | // Especially useful on the arguments object. Passing an **n** will return 426 | // the rest N values in the array. The **guard** 427 | // check allows it to work with `_.map`. 428 | _.rest = _.tail = _.drop = function(array, n, guard) { 429 | return slice.call(array, (n == null) || guard ? 1 : n); 430 | }; 431 | 432 | // Trim out all falsy values from an array. 433 | _.compact = function(array) { 434 | return _.filter(array, _.identity); 435 | }; 436 | 437 | // Internal implementation of a recursive `flatten` function. 438 | var flatten = function(input, shallow, output) { 439 | if (shallow && _.every(input, _.isArray)) { 440 | return concat.apply(output, input); 441 | } 442 | each(input, function(value) { 443 | if (_.isArray(value) || _.isArguments(value)) { 444 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 445 | } else { 446 | output.push(value); 447 | } 448 | }); 449 | return output; 450 | }; 451 | 452 | // Flatten out an array, either recursively (by default), or just one level. 453 | _.flatten = function(array, shallow) { 454 | return flatten(array, shallow, []); 455 | }; 456 | 457 | // Return a version of the array that does not contain the specified value(s). 458 | _.without = function(array) { 459 | return _.difference(array, slice.call(arguments, 1)); 460 | }; 461 | 462 | // Produce a duplicate-free version of the array. If the array has already 463 | // been sorted, you have the option of using a faster algorithm. 464 | // Aliased as `unique`. 465 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 466 | if (_.isFunction(isSorted)) { 467 | context = iterator; 468 | iterator = isSorted; 469 | isSorted = false; 470 | } 471 | var initial = iterator ? _.map(array, iterator, context) : array; 472 | var results = []; 473 | var seen = []; 474 | each(initial, function(value, index) { 475 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 476 | seen.push(value); 477 | results.push(array[index]); 478 | } 479 | }); 480 | return results; 481 | }; 482 | 483 | // Produce an array that contains the union: each distinct element from all of 484 | // the passed-in arrays. 485 | _.union = function() { 486 | return _.uniq(_.flatten(arguments, true)); 487 | }; 488 | 489 | // Produce an array that contains every item shared between all the 490 | // passed-in arrays. 491 | _.intersection = function(array) { 492 | var rest = slice.call(arguments, 1); 493 | return _.filter(_.uniq(array), function(item) { 494 | return _.every(rest, function(other) { 495 | return _.indexOf(other, item) >= 0; 496 | }); 497 | }); 498 | }; 499 | 500 | // Take the difference between one array and a number of other arrays. 501 | // Only the elements present in just the first array will remain. 502 | _.difference = function(array) { 503 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 504 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 505 | }; 506 | 507 | // Zip together multiple lists into a single array -- elements that share 508 | // an index go together. 509 | _.zip = function() { 510 | var length = _.max(_.pluck(arguments, "length").concat(0)); 511 | var results = new Array(length); 512 | for (var i = 0; i < length; i++) { 513 | results[i] = _.pluck(arguments, '' + i); 514 | } 515 | return results; 516 | }; 517 | 518 | // Converts lists into objects. Pass either a single array of `[key, value]` 519 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 520 | // the corresponding values. 521 | _.object = function(list, values) { 522 | if (list == null) return {}; 523 | var result = {}; 524 | for (var i = 0, length = list.length; i < length; i++) { 525 | if (values) { 526 | result[list[i]] = values[i]; 527 | } else { 528 | result[list[i][0]] = list[i][1]; 529 | } 530 | } 531 | return result; 532 | }; 533 | 534 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 535 | // we need this function. Return the position of the first occurrence of an 536 | // item in an array, or -1 if the item is not included in the array. 537 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 538 | // If the array is large and already in sort order, pass `true` 539 | // for **isSorted** to use binary search. 540 | _.indexOf = function(array, item, isSorted) { 541 | if (array == null) return -1; 542 | var i = 0, length = array.length; 543 | if (isSorted) { 544 | if (typeof isSorted == 'number') { 545 | i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); 546 | } else { 547 | i = _.sortedIndex(array, item); 548 | return array[i] === item ? i : -1; 549 | } 550 | } 551 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 552 | for (; i < length; i++) if (array[i] === item) return i; 553 | return -1; 554 | }; 555 | 556 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 557 | _.lastIndexOf = function(array, item, from) { 558 | if (array == null) return -1; 559 | var hasIndex = from != null; 560 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 561 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 562 | } 563 | var i = (hasIndex ? from : array.length); 564 | while (i--) if (array[i] === item) return i; 565 | return -1; 566 | }; 567 | 568 | // Generate an integer Array containing an arithmetic progression. A port of 569 | // the native Python `range()` function. See 570 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 571 | _.range = function(start, stop, step) { 572 | if (arguments.length <= 1) { 573 | stop = start || 0; 574 | start = 0; 575 | } 576 | step = arguments[2] || 1; 577 | 578 | var length = Math.max(Math.ceil((stop - start) / step), 0); 579 | var idx = 0; 580 | var range = new Array(length); 581 | 582 | while(idx < length) { 583 | range[idx++] = start; 584 | start += step; 585 | } 586 | 587 | return range; 588 | }; 589 | 590 | // Function (ahem) Functions 591 | // ------------------ 592 | 593 | // Reusable constructor function for prototype setting. 594 | var ctor = function(){}; 595 | 596 | // Create a function bound to a given object (assigning `this`, and arguments, 597 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 598 | // available. 599 | _.bind = function(func, context) { 600 | var args, bound; 601 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 602 | if (!_.isFunction(func)) throw new TypeError; 603 | args = slice.call(arguments, 2); 604 | return bound = function() { 605 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 606 | ctor.prototype = func.prototype; 607 | var self = new ctor; 608 | ctor.prototype = null; 609 | var result = func.apply(self, args.concat(slice.call(arguments))); 610 | if (Object(result) === result) return result; 611 | return self; 612 | }; 613 | }; 614 | 615 | // Partially apply a function by creating a version that has had some of its 616 | // arguments pre-filled, without changing its dynamic `this` context. 617 | _.partial = function(func) { 618 | var args = slice.call(arguments, 1); 619 | return function() { 620 | return func.apply(this, args.concat(slice.call(arguments))); 621 | }; 622 | }; 623 | 624 | // Bind all of an object's methods to that object. Useful for ensuring that 625 | // all callbacks defined on an object belong to it. 626 | _.bindAll = function(obj) { 627 | var funcs = slice.call(arguments, 1); 628 | if (funcs.length === 0) throw new Error("bindAll must be passed function names"); 629 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 630 | return obj; 631 | }; 632 | 633 | // Memoize an expensive function by storing its results. 634 | _.memoize = function(func, hasher) { 635 | var memo = {}; 636 | hasher || (hasher = _.identity); 637 | return function() { 638 | var key = hasher.apply(this, arguments); 639 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 640 | }; 641 | }; 642 | 643 | // Delays a function for the given number of milliseconds, and then calls 644 | // it with the arguments supplied. 645 | _.delay = function(func, wait) { 646 | var args = slice.call(arguments, 2); 647 | return setTimeout(function(){ return func.apply(null, args); }, wait); 648 | }; 649 | 650 | // Defers a function, scheduling it to run after the current call stack has 651 | // cleared. 652 | _.defer = function(func) { 653 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 654 | }; 655 | 656 | // Returns a function, that, when invoked, will only be triggered at most once 657 | // during a given window of time. Normally, the throttled function will run 658 | // as much as it can, without ever going more than once per `wait` duration; 659 | // but if you'd like to disable the execution on the leading edge, pass 660 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 661 | _.throttle = function(func, wait, options) { 662 | var context, args, result; 663 | var timeout = null; 664 | var previous = 0; 665 | options || (options = {}); 666 | var later = function() { 667 | previous = options.leading === false ? 0 : new Date; 668 | timeout = null; 669 | result = func.apply(context, args); 670 | }; 671 | return function() { 672 | var now = new Date; 673 | if (!previous && options.leading === false) previous = now; 674 | var remaining = wait - (now - previous); 675 | context = this; 676 | args = arguments; 677 | if (remaining <= 0) { 678 | clearTimeout(timeout); 679 | timeout = null; 680 | previous = now; 681 | result = func.apply(context, args); 682 | } else if (!timeout && options.trailing !== false) { 683 | timeout = setTimeout(later, remaining); 684 | } 685 | return result; 686 | }; 687 | }; 688 | 689 | // Returns a function, that, as long as it continues to be invoked, will not 690 | // be triggered. The function will be called after it stops being called for 691 | // N milliseconds. If `immediate` is passed, trigger the function on the 692 | // leading edge, instead of the trailing. 693 | _.debounce = function(func, wait, immediate) { 694 | var timeout, args, context, timestamp, result; 695 | return function() { 696 | context = this; 697 | args = arguments; 698 | timestamp = new Date(); 699 | var later = function() { 700 | var last = (new Date()) - timestamp; 701 | if (last < wait) { 702 | timeout = setTimeout(later, wait - last); 703 | } else { 704 | timeout = null; 705 | if (!immediate) result = func.apply(context, args); 706 | } 707 | }; 708 | var callNow = immediate && !timeout; 709 | if (!timeout) { 710 | timeout = setTimeout(later, wait); 711 | } 712 | if (callNow) result = func.apply(context, args); 713 | return result; 714 | }; 715 | }; 716 | 717 | // Returns a function that will be executed at most one time, no matter how 718 | // often you call it. Useful for lazy initialization. 719 | _.once = function(func) { 720 | var ran = false, memo; 721 | return function() { 722 | if (ran) return memo; 723 | ran = true; 724 | memo = func.apply(this, arguments); 725 | func = null; 726 | return memo; 727 | }; 728 | }; 729 | 730 | // Returns the first function passed as an argument to the second, 731 | // allowing you to adjust arguments, run code before and after, and 732 | // conditionally execute the original function. 733 | _.wrap = function(func, wrapper) { 734 | return function() { 735 | var args = [func]; 736 | push.apply(args, arguments); 737 | return wrapper.apply(this, args); 738 | }; 739 | }; 740 | 741 | // Returns a function that is the composition of a list of functions, each 742 | // consuming the return value of the function that follows. 743 | _.compose = function() { 744 | var funcs = arguments; 745 | return function() { 746 | var args = arguments; 747 | for (var i = funcs.length - 1; i >= 0; i--) { 748 | args = [funcs[i].apply(this, args)]; 749 | } 750 | return args[0]; 751 | }; 752 | }; 753 | 754 | // Returns a function that will only be executed after being called N times. 755 | _.after = function(times, func) { 756 | return function() { 757 | if (--times < 1) { 758 | return func.apply(this, arguments); 759 | } 760 | }; 761 | }; 762 | 763 | // Object Functions 764 | // ---------------- 765 | 766 | // Retrieve the names of an object's properties. 767 | // Delegates to **ECMAScript 5**'s native `Object.keys` 768 | _.keys = nativeKeys || function(obj) { 769 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 770 | var keys = []; 771 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 772 | return keys; 773 | }; 774 | 775 | // Retrieve the values of an object's properties. 776 | _.values = function(obj) { 777 | var keys = _.keys(obj); 778 | var length = keys.length; 779 | var values = new Array(length); 780 | for (var i = 0; i < length; i++) { 781 | values[i] = obj[keys[i]]; 782 | } 783 | return values; 784 | }; 785 | 786 | // Convert an object into a list of `[key, value]` pairs. 787 | _.pairs = function(obj) { 788 | var keys = _.keys(obj); 789 | var length = keys.length; 790 | var pairs = new Array(length); 791 | for (var i = 0; i < length; i++) { 792 | pairs[i] = [keys[i], obj[keys[i]]]; 793 | } 794 | return pairs; 795 | }; 796 | 797 | // Invert the keys and values of an object. The values must be serializable. 798 | _.invert = function(obj) { 799 | var result = {}; 800 | var keys = _.keys(obj); 801 | for (var i = 0, length = keys.length; i < length; i++) { 802 | result[obj[keys[i]]] = keys[i]; 803 | } 804 | return result; 805 | }; 806 | 807 | // Return a sorted list of the function names available on the object. 808 | // Aliased as `methods` 809 | _.functions = _.methods = function(obj) { 810 | var names = []; 811 | for (var key in obj) { 812 | if (_.isFunction(obj[key])) names.push(key); 813 | } 814 | return names.sort(); 815 | }; 816 | 817 | // Extend a given object with all the properties in passed-in object(s). 818 | _.extend = function(obj) { 819 | each(slice.call(arguments, 1), function(source) { 820 | if (source) { 821 | for (var prop in source) { 822 | obj[prop] = source[prop]; 823 | } 824 | } 825 | }); 826 | return obj; 827 | }; 828 | 829 | // Return a copy of the object only containing the whitelisted properties. 830 | _.pick = function(obj) { 831 | var copy = {}; 832 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 833 | each(keys, function(key) { 834 | if (key in obj) copy[key] = obj[key]; 835 | }); 836 | return copy; 837 | }; 838 | 839 | // Return a copy of the object without the blacklisted properties. 840 | _.omit = function(obj) { 841 | var copy = {}; 842 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 843 | for (var key in obj) { 844 | if (!_.contains(keys, key)) copy[key] = obj[key]; 845 | } 846 | return copy; 847 | }; 848 | 849 | // Fill in a given object with default properties. 850 | _.defaults = function(obj) { 851 | each(slice.call(arguments, 1), function(source) { 852 | if (source) { 853 | for (var prop in source) { 854 | if (obj[prop] === void 0) obj[prop] = source[prop]; 855 | } 856 | } 857 | }); 858 | return obj; 859 | }; 860 | 861 | // Create a (shallow-cloned) duplicate of an object. 862 | _.clone = function(obj) { 863 | if (!_.isObject(obj)) return obj; 864 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 865 | }; 866 | 867 | // Invokes interceptor with the obj, and then returns obj. 868 | // The primary purpose of this method is to "tap into" a method chain, in 869 | // order to perform operations on intermediate results within the chain. 870 | _.tap = function(obj, interceptor) { 871 | interceptor(obj); 872 | return obj; 873 | }; 874 | 875 | // Internal recursive comparison function for `isEqual`. 876 | var eq = function(a, b, aStack, bStack) { 877 | // Identical objects are equal. `0 === -0`, but they aren't identical. 878 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 879 | if (a === b) return a !== 0 || 1 / a == 1 / b; 880 | // A strict comparison is necessary because `null == undefined`. 881 | if (a == null || b == null) return a === b; 882 | // Unwrap any wrapped objects. 883 | if (a instanceof _) a = a._wrapped; 884 | if (b instanceof _) b = b._wrapped; 885 | // Compare `[[Class]]` names. 886 | var className = toString.call(a); 887 | if (className != toString.call(b)) return false; 888 | switch (className) { 889 | // Strings, numbers, dates, and booleans are compared by value. 890 | case '[object String]': 891 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 892 | // equivalent to `new String("5")`. 893 | return a == String(b); 894 | case '[object Number]': 895 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 896 | // other numeric values. 897 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 898 | case '[object Date]': 899 | case '[object Boolean]': 900 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 901 | // millisecond representations. Note that invalid dates with millisecond representations 902 | // of `NaN` are not equivalent. 903 | return +a == +b; 904 | // RegExps are compared by their source patterns and flags. 905 | case '[object RegExp]': 906 | return a.source == b.source && 907 | a.global == b.global && 908 | a.multiline == b.multiline && 909 | a.ignoreCase == b.ignoreCase; 910 | } 911 | if (typeof a != 'object' || typeof b != 'object') return false; 912 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 913 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 914 | var length = aStack.length; 915 | while (length--) { 916 | // Linear search. Performance is inversely proportional to the number of 917 | // unique nested structures. 918 | if (aStack[length] == a) return bStack[length] == b; 919 | } 920 | // Objects with different constructors are not equivalent, but `Object`s 921 | // from different frames are. 922 | var aCtor = a.constructor, bCtor = b.constructor; 923 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 924 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) { 925 | return false; 926 | } 927 | // Add the first object to the stack of traversed objects. 928 | aStack.push(a); 929 | bStack.push(b); 930 | var size = 0, result = true; 931 | // Recursively compare objects and arrays. 932 | if (className == '[object Array]') { 933 | // Compare array lengths to determine if a deep comparison is necessary. 934 | size = a.length; 935 | result = size == b.length; 936 | if (result) { 937 | // Deep compare the contents, ignoring non-numeric properties. 938 | while (size--) { 939 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 940 | } 941 | } 942 | } else { 943 | // Deep compare objects. 944 | for (var key in a) { 945 | if (_.has(a, key)) { 946 | // Count the expected number of properties. 947 | size++; 948 | // Deep compare each member. 949 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 950 | } 951 | } 952 | // Ensure that both objects contain the same number of properties. 953 | if (result) { 954 | for (key in b) { 955 | if (_.has(b, key) && !(size--)) break; 956 | } 957 | result = !size; 958 | } 959 | } 960 | // Remove the first object from the stack of traversed objects. 961 | aStack.pop(); 962 | bStack.pop(); 963 | return result; 964 | }; 965 | 966 | // Perform a deep comparison to check if two objects are equal. 967 | _.isEqual = function(a, b) { 968 | return eq(a, b, [], []); 969 | }; 970 | 971 | // Is a given array, string, or object empty? 972 | // An "empty" object has no enumerable own-properties. 973 | _.isEmpty = function(obj) { 974 | if (obj == null) return true; 975 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 976 | for (var key in obj) if (_.has(obj, key)) return false; 977 | return true; 978 | }; 979 | 980 | // Is a given value a DOM element? 981 | _.isElement = function(obj) { 982 | return !!(obj && obj.nodeType === 1); 983 | }; 984 | 985 | // Is a given value an array? 986 | // Delegates to ECMA5's native Array.isArray 987 | _.isArray = nativeIsArray || function(obj) { 988 | return toString.call(obj) == '[object Array]'; 989 | }; 990 | 991 | // Is a given variable an object? 992 | _.isObject = function(obj) { 993 | return obj === Object(obj); 994 | }; 995 | 996 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 997 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 998 | _['is' + name] = function(obj) { 999 | return toString.call(obj) == '[object ' + name + ']'; 1000 | }; 1001 | }); 1002 | 1003 | // Define a fallback version of the method in browsers (ahem, IE), where 1004 | // there isn't any inspectable "Arguments" type. 1005 | if (!_.isArguments(arguments)) { 1006 | _.isArguments = function(obj) { 1007 | return !!(obj && _.has(obj, 'callee')); 1008 | }; 1009 | } 1010 | 1011 | // Optimize `isFunction` if appropriate. 1012 | if (typeof (/./) !== 'function') { 1013 | _.isFunction = function(obj) { 1014 | return typeof obj === 'function'; 1015 | }; 1016 | } 1017 | 1018 | // Is a given object a finite number? 1019 | _.isFinite = function(obj) { 1020 | return isFinite(obj) && !isNaN(parseFloat(obj)); 1021 | }; 1022 | 1023 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 1024 | _.isNaN = function(obj) { 1025 | return _.isNumber(obj) && obj != +obj; 1026 | }; 1027 | 1028 | // Is a given value a boolean? 1029 | _.isBoolean = function(obj) { 1030 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 1031 | }; 1032 | 1033 | // Is a given value equal to null? 1034 | _.isNull = function(obj) { 1035 | return obj === null; 1036 | }; 1037 | 1038 | // Is a given variable undefined? 1039 | _.isUndefined = function(obj) { 1040 | return obj === void 0; 1041 | }; 1042 | 1043 | // Shortcut function for checking if an object has a given property directly 1044 | // on itself (in other words, not on a prototype). 1045 | _.has = function(obj, key) { 1046 | return hasOwnProperty.call(obj, key); 1047 | }; 1048 | 1049 | // Utility Functions 1050 | // ----------------- 1051 | 1052 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1053 | // previous owner. Returns a reference to the Underscore object. 1054 | _.noConflict = function() { 1055 | root._ = previousUnderscore; 1056 | return this; 1057 | }; 1058 | 1059 | // Keep the identity function around for default iterators. 1060 | _.identity = function(value) { 1061 | return value; 1062 | }; 1063 | 1064 | // Run a function **n** times. 1065 | _.times = function(n, iterator, context) { 1066 | var accum = Array(Math.max(0, n)); 1067 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); 1068 | return accum; 1069 | }; 1070 | 1071 | // Return a random integer between min and max (inclusive). 1072 | _.random = function(min, max) { 1073 | if (max == null) { 1074 | max = min; 1075 | min = 0; 1076 | } 1077 | return min + Math.floor(Math.random() * (max - min + 1)); 1078 | }; 1079 | 1080 | // List of HTML entities for escaping. 1081 | var entityMap = { 1082 | escape: { 1083 | '&': '&', 1084 | '<': '<', 1085 | '>': '>', 1086 | '"': '"', 1087 | "'": ''' 1088 | } 1089 | }; 1090 | entityMap.unescape = _.invert(entityMap.escape); 1091 | 1092 | // Regexes containing the keys and values listed immediately above. 1093 | var entityRegexes = { 1094 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1095 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1096 | }; 1097 | 1098 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1099 | _.each(['escape', 'unescape'], function(method) { 1100 | _[method] = function(string) { 1101 | if (string == null) return ''; 1102 | return ('' + string).replace(entityRegexes[method], function(match) { 1103 | return entityMap[method][match]; 1104 | }); 1105 | }; 1106 | }); 1107 | 1108 | // If the value of the named `property` is a function then invoke it with the 1109 | // `object` as context; otherwise, return it. 1110 | _.result = function(object, property) { 1111 | if (object == null) return void 0; 1112 | var value = object[property]; 1113 | return _.isFunction(value) ? value.call(object) : value; 1114 | }; 1115 | 1116 | // Add your own custom functions to the Underscore object. 1117 | _.mixin = function(obj) { 1118 | each(_.functions(obj), function(name) { 1119 | var func = _[name] = obj[name]; 1120 | _.prototype[name] = function() { 1121 | var args = [this._wrapped]; 1122 | push.apply(args, arguments); 1123 | return result.call(this, func.apply(_, args)); 1124 | }; 1125 | }); 1126 | }; 1127 | 1128 | // Generate a unique integer id (unique within the entire client session). 1129 | // Useful for temporary DOM ids. 1130 | var idCounter = 0; 1131 | _.uniqueId = function(prefix) { 1132 | var id = ++idCounter + ''; 1133 | return prefix ? prefix + id : id; 1134 | }; 1135 | 1136 | // By default, Underscore uses ERB-style template delimiters, change the 1137 | // following template settings to use alternative delimiters. 1138 | _.templateSettings = { 1139 | evaluate : /<%([\s\S]+?)%>/g, 1140 | interpolate : /<%=([\s\S]+?)%>/g, 1141 | escape : /<%-([\s\S]+?)%>/g 1142 | }; 1143 | 1144 | // When customizing `templateSettings`, if you don't want to define an 1145 | // interpolation, evaluation or escaping regex, we need one that is 1146 | // guaranteed not to match. 1147 | var noMatch = /(.)^/; 1148 | 1149 | // Certain characters need to be escaped so that they can be put into a 1150 | // string literal. 1151 | var escapes = { 1152 | "'": "'", 1153 | '\\': '\\', 1154 | '\r': 'r', 1155 | '\n': 'n', 1156 | '\t': 't', 1157 | '\u2028': 'u2028', 1158 | '\u2029': 'u2029' 1159 | }; 1160 | 1161 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1162 | 1163 | // JavaScript micro-templating, similar to John Resig's implementation. 1164 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1165 | // and correctly escapes quotes within interpolated code. 1166 | _.template = function(text, data, settings) { 1167 | var render; 1168 | settings = _.defaults({}, settings, _.templateSettings); 1169 | 1170 | // Combine delimiters into one regular expression via alternation. 1171 | var matcher = new RegExp([ 1172 | (settings.escape || noMatch).source, 1173 | (settings.interpolate || noMatch).source, 1174 | (settings.evaluate || noMatch).source 1175 | ].join('|') + '|$', 'g'); 1176 | 1177 | // Compile the template source, escaping string literals appropriately. 1178 | var index = 0; 1179 | var source = "__p+='"; 1180 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1181 | source += text.slice(index, offset) 1182 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1183 | 1184 | if (escape) { 1185 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1186 | } 1187 | if (interpolate) { 1188 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1189 | } 1190 | if (evaluate) { 1191 | source += "';\n" + evaluate + "\n__p+='"; 1192 | } 1193 | index = offset + match.length; 1194 | return match; 1195 | }); 1196 | source += "';\n"; 1197 | 1198 | // If a variable is not specified, place data values in local scope. 1199 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1200 | 1201 | source = "var __t,__p='',__j=Array.prototype.join," + 1202 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1203 | source + "return __p;\n"; 1204 | 1205 | try { 1206 | render = new Function(settings.variable || 'obj', '_', source); 1207 | } catch (e) { 1208 | e.source = source; 1209 | throw e; 1210 | } 1211 | 1212 | if (data) return render(data, _); 1213 | var template = function(data) { 1214 | return render.call(this, data, _); 1215 | }; 1216 | 1217 | // Provide the compiled function source as a convenience for precompilation. 1218 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1219 | 1220 | return template; 1221 | }; 1222 | 1223 | // Add a "chain" function, which will delegate to the wrapper. 1224 | _.chain = function(obj) { 1225 | return _(obj).chain(); 1226 | }; 1227 | 1228 | // OOP 1229 | // --------------- 1230 | // If Underscore is called as a function, it returns a wrapped object that 1231 | // can be used OO-style. This wrapper holds altered versions of all the 1232 | // underscore functions. Wrapped objects may be chained. 1233 | 1234 | // Helper function to continue chaining intermediate results. 1235 | var result = function(obj) { 1236 | return this._chain ? _(obj).chain() : obj; 1237 | }; 1238 | 1239 | // Add all of the Underscore functions to the wrapper object. 1240 | _.mixin(_); 1241 | 1242 | // Add all mutator Array functions to the wrapper. 1243 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1244 | var method = ArrayProto[name]; 1245 | _.prototype[name] = function() { 1246 | var obj = this._wrapped; 1247 | method.apply(obj, arguments); 1248 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1249 | return result.call(this, obj); 1250 | }; 1251 | }); 1252 | 1253 | // Add all accessor Array functions to the wrapper. 1254 | each(['concat', 'join', 'slice'], function(name) { 1255 | var method = ArrayProto[name]; 1256 | _.prototype[name] = function() { 1257 | return result.call(this, method.apply(this._wrapped, arguments)); 1258 | }; 1259 | }); 1260 | 1261 | _.extend(_.prototype, { 1262 | 1263 | // Start chaining a wrapped Underscore object. 1264 | chain: function() { 1265 | this._chain = true; 1266 | return this; 1267 | }, 1268 | 1269 | // Extracts the result from a wrapped and chained object. 1270 | value: function() { 1271 | return this._wrapped; 1272 | } 1273 | 1274 | }); 1275 | 1276 | }).call(this); 1277 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hls-kit", 3 | "version": "0.0.15", 4 | "description": "Simple adaptive-bitrate HTTP Live Streaming: mp4 to mpegts transcoding & m3u8 playlist serialization (with support for stream discontinuities, sliding-window live, and replay playlists).", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/dscanlonpa/HLSKit.git" 12 | }, 13 | "keywords": [ 14 | "hls", 15 | "live", 16 | "video", 17 | "m3u8" 18 | ], 19 | "author": "Dan Scanlon", 20 | "license": "BSD-2-Clause", 21 | "bugs": { 22 | "url": "https://github.com/dscanlonpa/HLSKit/issues" 23 | }, 24 | "dependencies": { 25 | "async": "~0.2.9", 26 | "debug": "~0.7.4", 27 | "underscore": "~1.5.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reference/PlaylistSession-Example.js: -------------------------------------------------------------------------------- 1 | { 2 | mediaSegments: { 3 | '0': { duration: 10, timeElapsed: 10, mediaSequence: 0, discontinuitySequence: 0 }, 4 | '1': { duration: 10, timeElapsed: 20, mediaSequence: 1, discontinuitySequence: 0 }, 5 | '2': { duration: 10, timeElapsed: 30, mediaSequence: 2, discontinuitySequence: 0 }, 6 | '3': { duration: 10, timeElapsed: 40, mediaSequence: 3, discontinuitySequence: 0 }, 7 | '5': { duration: 10, timeElapsed: 10, mediaSequence: 4, discontinuitySequence: 1 }, 8 | '6': { duration: 10, timeElapsed: 20, mediaSequence: 5, discontinuitySequence: 1 }, 9 | '8': { duration: 10, timeElapsed: 10, mediaSequence: 6, discontinuitySequence: 2 }, 10 | '9': { duration: 10, timeElapsed: 20, mediaSequence: 7, discontinuitySequence: 2 }, 11 | '10': { duration: 10, timeElapsed: 30, mediaSequence: 8, discontinuitySequence: 2 }, 12 | '12': { duration: 10, timeElapsed: 10, mediaSequence: 9, discontinuitySequence: 3 }, 13 | '13': { duration: 10, timeElapsed: 20, mediaSequence: 10, discontinuitySequence: 3 }, 14 | '14': { duration: 10, timeElapsed: 30, mediaSequence: 11, discontinuitySequence: 3 }, 15 | '15': { duration: 10, timeElapsed: 40, mediaSequence: 12, discontinuitySequence: 3 }, 16 | 17 | '4': { duration: 10, timeElapsed: 10, mediaSequence: -1, discontinuitySequence: -1 }, 18 | '7': { duration: 10, timeElapsed: 10, mediaSequence: -1, discontinuitySequence: -1 }, 19 | '11': { duration: 10, timeElapsed: 10, mediaSequence: -1, discontinuitySequence: -1 }, 20 | 21 | config: { 22 | targetDuration: 10, 23 | windowLength: 3 24 | }, 25 | 26 | meta: { 27 | id: ''+ req.params.id, 28 | shouldBeAvailable: false, 29 | isAvailable: false, 30 | shouldFinish: false, 31 | isFinished: false 32 | } 33 | } -------------------------------------------------------------------------------- /reference/good-playlists.js: -------------------------------------------------------------------------------- 1 | Replay: 2 | ======= 3 | 4 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 5 | 0 10 N 0 0 6 | 1 20 N 0 1 7 | 2 30 N 0 2 8 | 3 40 N 0 3 9 | Y 10 | 4 10 N 1(-1) 4(-1) 11 | Y 12 | 5 10 N 2(1) 5 13 | 6 20 N 2(1) 6 14 | Y 15 | 7 10 N 3(-1) 7(-1) 16 | Y 17 | 8 10 N 4(2) 8 18 | 9 20 N 4(2) 9 19 | 10 30 N 4(2) 10 20 | Y 21 | 11 10 N 5(-1) 11(-1) 22 | Y 23 | 12 10 N 6(3) 12 24 | 13 20 N 6(3) 13 25 | 14 30 N 6(3) 14 26 | 15 40 N 6(3) 15 27 | 28 | 29 | 30 | Sliding-Window Live: 31 | ==================== 32 | Displayed in 10-sec intervals 33 | 34 | #MSEQ:0 35 | #DSEQ:0 36 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 37 | 0 10 N 0 0 38 | 39 | 40 | #MSEQ:0 41 | #DSEQ:0 42 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 43 | 0 10 N 0 0 44 | 1 20 N 0 1 45 | 46 | 47 | #MSEQ:0 48 | #DSEQ:0 49 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 50 | 0 10 N 0 0 51 | 1 20 N 0 1 52 | 2 30 N 0 2 53 | 54 | 55 | #MSEQ:1 56 | #DSEQ:0 57 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 58 | 1 20 N 0 1 59 | 2 30 N 0 2 60 | 3 40 N 0 3 61 | 62 | 63 | (unchanged!) 64 | #MSEQ:1 65 | #DSEQ:0 66 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 67 | 1 20 N 0 1 68 | 2 30 N 0 2 69 | 3 40 N 0 3 70 | 71 | 72 | #MSEQ:2 73 | #DSEQ:0 74 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 75 | 2 30 N 0 2 76 | 3 40 N 0 3 77 | Y 78 | 5 10 N 1 4 79 | 80 | 81 | #MSEQ:3 82 | #DSEQ:0 83 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 84 | 3 40 N 0 3 85 | Y 86 | 5 10 N 1 4 87 | 6 20 N 1 5 88 | 89 | 90 | (unchanged!) 91 | #MSEQ:3 92 | #DSEQ:0 93 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 94 | 3 40 N 0 3 95 | Y 96 | 5 10 N 1 4 97 | 6 20 N 1 5 98 | 99 | 100 | #MSEQ:4 101 | #DSEQ:0 102 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 103 | Y 104 | 5 10 N 1 4 105 | 6 20 N 1 5 106 | Y 107 | 8 10 N 2 6 108 | 109 | 110 | #MSEQ:5 111 | #DSEQ:1 112 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 113 | 6 20 N 1 5 114 | Y 115 | 8 10 N 2 6 116 | 9 20 N 2 7 117 | 118 | 119 | #MSEQ:6 120 | #DSEQ:1 121 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 122 | Y 123 | 8 10 N 2 6 124 | 9 20 N 2 7 125 | 10 30 N 2 8 126 | 127 | 128 | (unchanged!) 129 | #MSEQ:6 130 | #DSEQ:1 131 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 132 | Y 133 | 8 10 N 2 6 134 | 9 20 N 2 7 135 | 10 30 N 2 8 136 | 137 | 138 | #MSEQ:7 139 | #DSEQ:2 140 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 141 | 9 20 N 2 7 142 | 10 30 N 2 8 143 | Y 144 | 12 10 N 3 9 145 | 146 | 147 | #MSEQ:8 148 | #DSEQ:2 149 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 150 | 10 30 N 2 8 151 | Y 152 | 12 10 N 3 9 153 | 13 20 N 3 10 154 | 155 | 156 | #MSEQ:9 157 | #DSEQ:2 158 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 159 | Y 160 | 12 10 N 3 9 161 | 13 20 N 3 10 162 | 14 30 N 3 11 163 | 164 | 165 | #MSEQ:10 166 | #DSEQ:3 167 | SEQ ELAPSED DISCONTINUITY DSEQ MSEQ 168 | 13 20 N 3 10 169 | 14 30 N 3 11 170 | 15 40 N 3 12 171 | #EXT-X-ENDLIST 172 | --------------------------------------------------------------------------------