Permalink
Newer
100644
5356 lines (4717 sloc)
170 KB
1
(function(e){if("function"==typeof bootstrap)bootstrap("webrtc",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeWebRTC=e}else"undefined"!=typeof window?window.WebRTC=e():global.WebRTC=e()})(function(){var define,ses,bootstrap,module,exports;
2
return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
9
10
11
function WebRTC(opts) {
12
var self = this;
13
var options = opts || {};
14
var config = this.config = {
16
// makes the entire PC config overridable
17
peerConnectionConfig: {
18
iceServers: [{"url": "stun:stun.l.google.com:19302"}]
19
},
34
35
// expose screensharing check
36
this.screenSharingSupport = webrtc.screenSharing;
37
38
// We also allow a 'logger' option. It can be any object that implements
39
// log, warn, and error methods.
40
// We log nothing by default, following "the rule of silence":
41
// http://www.linfo.org/rule_of_silence.html
42
this.logger = function () {
43
// we assume that if you're in debug mode and you didn't
44
// pass in a logger, you actually want to log as much as
45
// possible.
46
if (opts.debug) {
47
return opts.logger || console;
48
} else {
49
// or we'll use your logger which should have its own logic
50
// for output. Or we'll return the no-op.
51
return opts.logger || mockconsole;
52
}
53
}();
54
55
// set options
56
for (item in options) {
57
this.config[item] = options[item];
58
}
59
60
// check for support
61
if (!webrtc.support) {
62
this.logger.error('Your browser doesn\'t seem to support WebRTC');
63
}
73
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
74
self.peers.forEach(function (peer) {
75
if (peer.enableDataChannels) {
76
var dc = peer.getDataChannel('hark');
77
if (dc.readyState != 'open') return;
78
dc.send(JSON.stringify({type: 'speaking'}));
79
}
80
});
85
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
86
self.peers.forEach(function (peer) {
87
if (peer.enableDataChannels) {
88
var dc = peer.getDataChannel('hark');
89
if (dc.readyState != 'open') return;
90
dc.send(JSON.stringify({type: 'stoppedSpeaking'}));
91
}
92
});
95
this.on('volumeChange', function (volume, treshold) {
96
if (!self.hardMuted) {
97
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
98
self.peers.forEach(function (peer) {
99
if (peer.enableDataChannels) {
100
var dc = peer.getDataChannel('hark');
101
if (dc.readyState != 'open') return;
102
dc.send(JSON.stringify({type: 'volume', volume: volume }));
103
}
104
});
105
}
106
});
111
var logger;
112
// if you didn't pass in a logger and you explicitly turning on debug
113
// we're just going to assume you're wanting log output with console
114
if (self.config.logger === mockconsole) {
115
logger = console;
116
} else {
117
logger = self.logger;
118
}
119
logger.log('event:', event, val1, val2);
125
126
WebRTC.prototype.createPeer = function (opts) {
127
var peer;
128
opts.parent = this;
129
peer = new Peer(opts);
130
this.peers.push(peer);
131
return peer;
132
};
133
134
// removes peers
135
WebRTC.prototype.removePeers = function (id, type) {
136
this.getPeers(id, type).forEach(function (peer) {
137
peer.end();
138
});
139
};
140
141
// fetches all Peer objects by session id and/or type
142
WebRTC.prototype.getPeers = function (sessionId, type) {
143
return this.peers.filter(function (peer) {
144
return (!sessionId || peer.id === sessionId) && (!type || peer.type === type);
145
});
146
};
147
148
// sends message to all
149
WebRTC.prototype.sendToAll = function (message, payload) {
150
this.peers.forEach(function (peer) {
151
peer.send(message, payload);
152
});
153
};
154
157
WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) {
158
this.peers.forEach(function (peer) {
159
if (peer.enableDataChannels) {
160
peer.sendDirectly(channel, message, payload);
161
}
167
},{"./peer":3,"localmedia":7,"mockconsole":6,"util":2,"webrtcsupport":5,"wildemitter":4}],2:[function(require,module,exports){
170
exports.isArray = isArray;
171
exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'};
172
exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'};
175
exports.print = function () {};
176
exports.puts = function () {};
177
exports.debug = function() {};
182
var stylize = function(str, styleType) {
183
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
184
var styles =
185
{ 'bold' : [1, 22],
186
'italic' : [3, 23],
187
'underline' : [4, 24],
188
'inverse' : [7, 27],
189
'white' : [37, 39],
190
'grey' : [90, 39],
191
'black' : [30, 39],
192
'blue' : [34, 39],
193
'cyan' : [36, 39],
194
'green' : [32, 39],
195
'magenta' : [35, 39],
196
'red' : [31, 39],
197
'yellow' : [33, 39] };
199
var style =
200
{ 'special': 'cyan',
201
'number': 'blue',
202
'boolean': 'yellow',
203
'undefined': 'grey',
204
'null': 'bold',
205
'string': 'green',
206
'date': 'magenta',
207
// "name": intentionally not styling
208
'regexp': 'red' }[styleType];
210
if (style) {
211
return '\u001b[' + styles[style][0] + 'm' + str +
212
'\u001b[' + styles[style][1] + 'm';
213
} else {
214
return str;
216
};
217
if (! colors) {
218
stylize = function(str, styleType) { return str; };
219
}
221
function format(value, recurseTimes) {
222
// Provide a hook for user-specified inspect functions.
223
// Check that value is an object with an inspect function on it
224
if (value && typeof value.inspect === 'function' &&
225
// Filter out the util module, it's inspect function is special
226
value !== exports &&
227
// Also filter out any prototype objects using the circular check.
228
!(value.constructor && value.constructor.prototype === value)) {
229
return value.inspect(recurseTimes);
230
}
232
// Primitive types cannot have properties
233
switch (typeof value) {
234
case 'undefined':
235
return stylize('undefined', 'undefined');
237
case 'string':
238
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
239
.replace(/'/g, "\\'")
240
.replace(/\\"/g, '"') + '\'';
241
return stylize(simple, 'string');
246
case 'boolean':
247
return stylize('' + value, 'boolean');
248
}
249
// For some reason typeof null is "object", so special case here.
250
if (value === null) {
251
return stylize('null', 'null');
254
// Look up the keys of the object.
255
var visible_keys = Object_keys(value);
256
var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys;
258
// Functions without properties can be shortcutted.
259
if (typeof value === 'function' && keys.length === 0) {
260
if (isRegExp(value)) {
261
return stylize('' + value, 'regexp');
262
} else {
263
var name = value.name ? ': ' + value.name : '';
264
return stylize('[Function' + name + ']', 'special');
265
}
268
// Dates without properties can be shortcutted
269
if (isDate(value) && keys.length === 0) {
270
return stylize(value.toUTCString(), 'date');
271
}
273
var base, type, braces;
274
// Determine the object type
275
if (isArray(value)) {
276
type = 'Array';
277
braces = ['[', ']'];
283
// Make functions say that they are functions
284
if (typeof value === 'function') {
285
var n = value.name ? ': ' + value.name : '';
286
base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
287
} else {
288
base = '';
291
// Make dates with properties first say the date
292
if (isDate(value)) {
293
base = ' ' + value.toUTCString();
300
if (recurseTimes < 0) {
301
if (isRegExp(value)) {
302
return stylize('' + value, 'regexp');
303
} else {
304
return stylize('[Object]', 'special');
305
}
306
}
310
var output = keys.map(function(key) {
311
var name, str;
312
if (value.__lookupGetter__) {
313
if (value.__lookupGetter__(key)) {
314
if (value.__lookupSetter__(key)) {
315
str = stylize('[Getter/Setter]', 'special');
316
} else {
317
str = stylize('[Getter]', 'special');
318
}
319
} else {
320
if (value.__lookupSetter__(key)) {
321
str = stylize('[Setter]', 'special');
322
}
324
}
325
if (visible_keys.indexOf(key) < 0) {
326
name = '[' + key + ']';
327
}
328
if (!str) {
329
if (seen.indexOf(value[key]) < 0) {
330
if (recurseTimes === null) {
331
str = format(value[key]);
332
} else {
333
str = format(value[key], recurseTimes - 1);
334
}
335
if (str.indexOf('\n') > -1) {
336
if (isArray(value)) {
337
str = str.split('\n').map(function(line) {
338
return ' ' + line;
339
}).join('\n').substr(2);
341
str = '\n' + str.split('\n').map(function(line) {
342
return ' ' + line;
343
}).join('\n');
349
}
350
if (typeof name === 'undefined') {
351
if (type === 'Array' && key.match(/^\d+$/)) {
352
return str;
353
}
354
name = JSON.stringify('' + key);
355
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
356
name = name.substr(1, name.length - 2);
357
name = stylize(name, 'name');
358
} else {
359
name = name.replace(/'/g, "\\'")
360
.replace(/\\"/g, '"')
361
.replace(/(^"|"$)/g, "'");
362
name = stylize(name, 'string');
363
}
364
}
371
var numLinesEst = 0;
372
var length = output.reduce(function(prev, cur) {
373
numLinesEst++;
374
if (cur.indexOf('\n') >= 0) numLinesEst++;
375
return prev + cur.length + 1;
376
}, 0);
377
378
if (length > 50) {
379
output = braces[0] +
380
(base === '' ? '' : base + '\n ') +
381
' ' +
382
output.join(',\n ') +
383
' ' +
384
braces[1];
385
386
} else {
387
output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
389
390
return output;
391
}
392
return format(obj, (typeof depth === 'undefined' ? 2 : depth));
396
function isArray(ar) {
397
return Array.isArray(ar) ||
398
(typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]');
399
}
402
function isRegExp(re) {
403
typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]';
404
}
407
function isDate(d) {
408
return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]';
409
}
415
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
416
'Oct', 'Nov', 'Dec'];
418
// 26 Feb 16:19:34
419
function timestamp() {
420
var d = new Date();
421
var time = [pad(d.getHours()),
422
pad(d.getMinutes()),
423
pad(d.getSeconds())].join(':');
424
return [d.getDate(), months[d.getMonth()], time].join(' ');
425
}
431
var Object_keys = Object.keys || function (obj) {
432
var res = [];
433
for (var key in obj) res.push(key);
434
return res;
435
};
437
var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) {
438
var res = [];
439
for (var key in obj) {
440
if (Object.hasOwnProperty.call(obj, key)) res.push(key);
441
}
442
return res;
443
};
445
var Object_create = Object.create || function (prototype, properties) {
446
// from es5-shim
447
var object;
448
if (prototype === null) {
449
object = { '__proto__' : null };
451
else {
452
if (typeof prototype !== 'object') {
453
throw new TypeError(
454
'typeof prototype[' + (typeof prototype) + '] != \'object\''
455
);
456
}
457
var Type = function () {};
458
Type.prototype = prototype;
459
object = new Type();
460
object.__proto__ = prototype;
461
}
462
if (typeof properties !== 'undefined' && Object.defineProperties) {
463
Object.defineProperties(object, properties);
464
}
465
return object;
466
};
468
exports.inherits = function(ctor, superCtor) {
469
ctor.super_ = superCtor;
470
ctor.prototype = Object_create(superCtor.prototype, {
471
constructor: {
472
value: ctor,
473
enumerable: false,
474
writable: true,
475
configurable: true
476
}
477
});
478
};
479
480
var formatRegExp = /%[sdj%]/g;
481
exports.format = function(f) {
482
if (typeof f !== 'string') {
483
var objects = [];
484
for (var i = 0; i < arguments.length; i++) {
485
objects.push(exports.inspect(arguments[i]));
486
}
487
return objects.join(' ');
488
}
489
490
var i = 1;
491
var args = arguments;
492
var len = args.length;
493
var str = String(f).replace(formatRegExp, function(x) {
494
if (x === '%%') return '%';
495
if (i >= len) return x;
496
switch (x) {
497
case '%s': return String(args[i++]);
498
case '%d': return Number(args[i++]);
499
case '%j': return JSON.stringify(args[i++]);
500
default:
501
return x;
502
}
503
});
504
for(var x = args[i]; i < len; x = args[++i]){
505
if (x === null || typeof x !== 'object') {
506
str += ' ' + x;
507
} else {
508
str += ' ' + exports.inspect(x);
509
}
510
}
511
return str;
512
};
513
515
/*
516
WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
517
on @visionmedia's Emitter from UI Kit.
521
I also wanted support for wildcard emitters like this:
522
523
emitter.on('*', function (eventName, other, event, payloads) {
524
525
});
526
527
emitter.on('somenamespace*', function (eventName, payloads) {
528
529
});
530
531
Please note that callbacks triggered by wildcard registered events also get
532
the event name as the first argument.
533
*/
534
module.exports = WildEmitter;
535
536
function WildEmitter() {
537
this.callbacks = {};
538
}
539
540
// Listen on the given `event` with `fn`. Store a group name if present.
541
WildEmitter.prototype.on = function (event, groupName, fn) {
542
var hasGroup = (arguments.length === 3),
543
group = hasGroup ? arguments[1] : undefined,
544
func = hasGroup ? arguments[2] : arguments[1];
545
func._groupName = group;
546
(this.callbacks[event] = this.callbacks[event] || []).push(func);
547
return this;
548
};
549
550
// Adds an `event` listener that will be invoked a single
551
// time then automatically removed.
552
WildEmitter.prototype.once = function (event, groupName, fn) {
553
var self = this,
554
hasGroup = (arguments.length === 3),
555
group = hasGroup ? arguments[1] : undefined,
556
func = hasGroup ? arguments[2] : arguments[1];
557
function on() {
558
self.off(event, on);
559
func.apply(this, arguments);
560
}
561
this.on(event, group, on);
562
return this;
563
};
564
565
// Unbinds an entire group
566
WildEmitter.prototype.releaseGroup = function (groupName) {
567
var item, i, len, handlers;
568
for (item in this.callbacks) {
569
handlers = this.callbacks[item];
570
for (i = 0, len = handlers.length; i < len; i++) {
571
if (handlers[i]._groupName === groupName) {
572
//console.log('removing');
573
// remove it and shorten the array we're looping through
574
handlers.splice(i, 1);
575
i--;
576
len--;
577
}
578
}
579
}
580
return this;
581
};
582
583
// Remove the given callback for `event` or all
584
// registered callbacks.
585
WildEmitter.prototype.off = function (event, fn) {
586
var callbacks = this.callbacks[event],
587
i;
588
589
if (!callbacks) return this;
590
591
// remove all handlers
592
if (arguments.length === 1) {
593
delete this.callbacks[event];
594
return this;
595
}
596
597
// remove specific handler
598
i = callbacks.indexOf(fn);
599
callbacks.splice(i, 1);
600
return this;
601
};
602
603
/// Emit `event` with the given args.
604
// also calls any `*` handlers
605
WildEmitter.prototype.emit = function (event) {
606
var args = [].slice.call(arguments, 1),
607
callbacks = this.callbacks[event],
608
specialCallbacks = this.getWildcardCallbacks(event),
609
i,
610
len,
611
item,
612
listeners;
613
614
if (callbacks) {
615
listeners = callbacks.slice();
616
for (i = 0, len = listeners.length; i < len; ++i) {
617
if (listeners[i]) {
618
listeners[i].apply(this, args);
619
} else {
620
break;
621
}
622
}
623
}
624
625
if (specialCallbacks) {
626
len = specialCallbacks.length;
627
listeners = specialCallbacks.slice();
628
for (i = 0, len = listeners.length; i < len; ++i) {
629
if (listeners[i]) {
630
listeners[i].apply(this, [event].concat(args));
631
} else {
632
break;
633
}
634
}
635
}
636
637
return this;
638
};
639
640
// Helper for for finding special wildcard event handlers that match the event
641
WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
642
var item,
643
split,
644
result = [];
645
646
for (item in this.callbacks) {
647
split = item.split('*');
648
if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {
649
result = result.concat(this.callbacks[item]);
650
}
651
}
652
return result;
653
};
654
656
// created by @HenrikJoreteg
657
var prefix;
658
659
if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) {
660
prefix = 'moz';
661
} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) {
662
prefix = 'webkit';
663
}
664
665
var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
666
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
667
var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
668
var MediaStream = window.webkitMediaStream || window.MediaStream;
669
var screenSharing = window.location.protocol === 'https:' &&
670
((window.navigator.userAgent.match('Chrome') && parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10) >= 26) ||
671
(window.navigator.userAgent.match('Firefox') && parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10) >= 33));
672
var AudioContext = window.AudioContext || window.webkitAudioContext;
673
var supportVp8 = document.createElement('video').canPlayType('video/webm; codecs="vp8", vorbis') === "probably";
674
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia;
675
676
// export support flags and constructors.prototype && PC
677
module.exports = {
678
prefix: prefix,
679
support: !!PC && supportVp8 && !!getUserMedia,
680
// new support style
681
supportRTCPeerConnection: !!PC,
682
supportVp8: supportVp8,
683
supportGetUserMedia: !!getUserMedia,
684
supportDataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel),
685
supportWebAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource),
686
supportMediaStream: !!(MediaStream && MediaStream.prototype.removeTrack),
687
supportScreenSharing: !!screenSharing,
688
// old deprecated style. Dont use this anymore
689
dataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel),
690
webAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource),
691
mediaStream: !!(MediaStream && MediaStream.prototype.removeTrack),
692
screenSharing: !!screenSharing,
693
// constructors
694
AudioContext: AudioContext,
695
PeerConnection: PC,
696
SessionDescription: SessionDescription,
697
IceCandidate: IceCandidate,
698
MediaStream: MediaStream,
699
getUserMedia: getUserMedia
700
};
701
702
},{}],6:[function(require,module,exports){
703
var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(",");
704
var l = methods.length;
705
var fn = function () {};
706
var mockconsole = {};
719
process.nextTick = (function () {
720
var canSetImmediate = typeof window !== 'undefined'
721
&& window.setImmediate;
722
var canPost = typeof window !== 'undefined'
723
&& window.postMessage && window.addEventListener
724
;
726
if (canSetImmediate) {
727
return function (f) { return window.setImmediate(f) };
728
}
730
if (canPost) {
731
var queue = [];
732
window.addEventListener('message', function (ev) {
733
var source = ev.source;
734
if ((source === window || source === null) && ev.data === 'process-tick') {
735
ev.stopPropagation();
736
if (queue.length > 0) {
737
var fn = queue.shift();
738
fn();
739
}
740
}
741
}, true);
743
return function nextTick(fn) {
744
queue.push(fn);
745
window.postMessage('process-tick', '*');
746
};
747
}
754
process.title = 'browser';
755
process.browser = true;
756
process.env = {};
757
process.argv = [];
759
process.binding = function (name) {
760
throw new Error('process.binding is not supported');
761
}
763
// TODO(shtylman)
764
process.cwd = function () { return '/' };
765
process.chdir = function (dir) {
766
throw new Error('process.chdir is not supported');
767
};
768
769
},{}],8:[function(require,module,exports){
770
var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {};
771
772
var EventEmitter = exports.EventEmitter = process.EventEmitter;
773
var isArray = typeof Array.isArray === 'function'
774
? Array.isArray
775
: function (xs) {
776
return Object.prototype.toString.call(xs) === '[object Array]'
777
}
778
;
779
function indexOf (xs, x) {
780
if (xs.indexOf) return xs.indexOf(x);
781
for (var i = 0; i < xs.length; i++) {
782
if (x === xs[i]) return i;
783
}
784
return -1;
785
}
786
787
// By default EventEmitters will print a warning if more than
788
// 10 listeners are added to it. This is a useful default which
789
// helps finding memory leaks.
790
//
791
// Obviously not all Emitters should be limited to 10. This function allows
792
// that to be increased. Set to zero for unlimited.
793
var defaultMaxListeners = 10;
794
EventEmitter.prototype.setMaxListeners = function(n) {
795
if (!this._events) this._events = {};
796
this._events.maxListeners = n;
797
};
798
799
800
EventEmitter.prototype.emit = function(type) {
801
// If there is no 'error' event listener then throw.
802
if (type === 'error') {
803
if (!this._events || !this._events.error ||
804
(isArray(this._events.error) && !this._events.error.length))
805
{
806
if (arguments[1] instanceof Error) {
807
throw arguments[1]; // Unhandled 'error' event
808
} else {
809
throw new Error("Uncaught, unspecified 'error' event.");
810
}
811
return false;
812
}
813
}
814
815
if (!this._events) return false;
816
var handler = this._events[type];
817
if (!handler) return false;
819
if (typeof handler == 'function') {
820
switch (arguments.length) {
821
// fast cases
822
case 1:
823
handler.call(this);
824
break;
825
case 2:
826
handler.call(this, arguments[1]);
827
break;
828
case 3:
829
handler.call(this, arguments[1], arguments[2]);
830
break;
831
// slower
832
default:
833
var args = Array.prototype.slice.call(arguments, 1);
834
handler.apply(this, args);
838
} else if (isArray(handler)) {
839
var args = Array.prototype.slice.call(arguments, 1);
841
var listeners = handler.slice();
842
for (var i = 0, l = listeners.length; i < l; i++) {
843
listeners[i].apply(this, args);
852
// EventEmitter is defined in src/node_events.cc
853
// EventEmitter.prototype.emit() is also defined there.
854
EventEmitter.prototype.addListener = function(type, listener) {
855
if ('function' !== typeof listener) {
856
throw new Error('addListener only takes instances of Function');
857
}
861
// To avoid recursion in the case that type == "newListeners"! Before
862
// adding it to the listeners, first emit "newListeners".
863
this.emit('newListener', type, listener);
865
if (!this._events[type]) {
866
// Optimize the case of one listener. Don't need the extra array object.
867
this._events[type] = listener;
868
} else if (isArray(this._events[type])) {
870
// Check for listener leak
871
if (!this._events[type].warned) {
872
var m;
873
if (this._events.maxListeners !== undefined) {
874
m = this._events.maxListeners;
875
} else {
876
m = defaultMaxListeners;
877
}
879
if (m && m > 0 && this._events[type].length > m) {
880
this._events[type].warned = true;
881
console.error('(node) warning: possible EventEmitter memory ' +
882
'leak detected. %d listeners added. ' +
883
'Use emitter.setMaxListeners() to increase limit.',
884
this._events[type].length);
885
console.trace();
886
}
889
// If we've already got an array, just append.
890
this._events[type].push(listener);
891
} else {
892
// Adding the second element, need to change to array.
893
this._events[type] = [this._events[type], listener];
901
EventEmitter.prototype.once = function(type, listener) {
902
var self = this;
903
self.on(type, function g() {
904
self.removeListener(type, g);
905
listener.apply(this, arguments);
906
});
911
EventEmitter.prototype.removeListener = function(type, listener) {
912
if ('function' !== typeof listener) {
913
throw new Error('removeListener only takes instances of Function');
914
}
916
// does not use listeners(), so no side effect of creating _events[type]
917
if (!this._events || !this._events[type]) return this;
921
if (isArray(list)) {
922
var i = indexOf(list, listener);
923
if (i < 0) return this;
924
list.splice(i, 1);
925
if (list.length == 0)
926
delete this._events[type];
927
} else if (this._events[type] === listener) {
928
delete this._events[type];
929
}
934
EventEmitter.prototype.removeAllListeners = function(type) {
935
if (arguments.length === 0) {
936
this._events = {};
937
return this;
938
}
940
// does not use listeners(), so no side effect of creating _events[type]
941
if (type && this._events && this._events[type]) this._events[type] = null;
942
return this;
945
EventEmitter.prototype.listeners = function(type) {
946
if (!this._events) this._events = {};
947
if (!this._events[type]) this._events[type] = [];
948
if (!isArray(this._events[type])) {
949
this._events[type] = [this._events[type]];
954
EventEmitter.listenerCount = function(emitter, type) {
955
var ret;
956
if (!emitter._events || !emitter._events[type])
957
ret = 0;
958
else if (typeof emitter._events[type] === 'function')
959
ret = 1;
960
else
961
ret = emitter._events[type].length;
962
return ret;
972
// the inband-v1 protocol is sending metadata inband in a serialized JSON object
973
// followed by the actual data. Receiver closes the datachannel upon completion
974
var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1';
979
this.id = options.id;
980
this.parent = options.parent;
981
this.type = options.type || 'video';
982
this.oneway = options.oneway || false;
983
this.sharemyscreen = options.sharemyscreen || false;
984
this.browserPrefix = options.prefix;
985
this.stream = options.stream;
986
this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels;
987
this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia;
988
this.channels = {};
989
this.sid = options.sid || Date.now().toString();
990
// Create an RTCPeerConnection via the polyfill
991
this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints);
992
this.pc.on('ice', this.onIceCandidate.bind(this));
993
this.pc.on('offer', function (offer) {
994
self.send('offer', offer);
995
});
996
this.pc.on('answer', function (offer) {
997
self.send('answer', offer);
998
});
999
this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this));
1000
this.pc.on('addChannel', this.handleDataChannelAdded.bind(this));
1001
this.pc.on('removeStream', this.handleStreamRemoved.bind(this));
1002
// Just fire negotiation needed events for now
1003
// When browser re-negotiation handling seems to work
1004
// we can use this as the trigger for starting the offer/answer process
1005
// automatically. We'll just leave it be for now while this stabalizes.
1006
this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded'));
1007
this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange'));
1008
this.pc.on('iceConnectionStateChange', function () {
1009
switch (self.pc.iceConnectionState) {
1010
case 'failed':
1011
// currently, in chrome only the initiator goes to failed
1012
// so we need to signal this to the peer
1013
if (self.pc.pc.peerconnection.localDescription.type === 'offer') {
1014
self.parent.emit('iceFailed', self);
1015
self.send('connectivityError');
1016
}
1017
break;
1018
}
1019
});
1020
this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange'));
1021
this.logger = this.parent.logger;
1023
// handle screensharing/broadcast mode
1024
if (options.type === 'screen') {
1025
if (this.parent.localScreen && this.sharemyscreen) {
1026
this.logger.log('adding local screen stream to peer connection');
1027
this.pc.addStream(this.parent.localScreen);
1028
this.broadcaster = options.broadcaster;
1029
}
1030
} else {
1031
this.parent.localStreams.forEach(function (stream) {
1032
self.pc.addStream(stream);
1033
});
1039
this.on('channelOpen', function (channel) {
1040
if (channel.protocol === INBAND_FILETRANSFER_V1) {
1041
channel.onmessage = function (event) {
1042
var metadata = JSON.parse(event.data);
1043
var receiver = new FileTransfer.Receiver();
1044
receiver.receive(metadata, channel);
1045
self.emit('fileTransfer', metadata, receiver);
1046
receiver.on('receivedFile', function (file, metadata) {
1047
receiver.channel.close();
1048
});
1049
};
1050
}
1051
});
1052
1053
// proxy events to parent
1054
this.on('*', function () {
1055
self.parent.emit.apply(self.parent, arguments);
1056
});
1068
if (message.type === 'offer') {
1069
// workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1064247
1070
message.payload.sdp = message.payload.sdp.replace('a=fmtp:0 profile-level-id=0x42e00c;packetization-mode=1\r\n', '');
1071
this.pc.handleOffer(message.payload, function (err) {
1072
if (err) {
1073
return;
1074
}
1075
// auto-accept
1076
self.pc.answer(self.receiveMedia, function (err, sessionDescription) {
1077
//self.send('answer', sessionDescription);
1078
});
1079
});
1080
} else if (message.type === 'answer') {
1081
this.pc.handleAnswer(message.payload);
1082
} else if (message.type === 'candidate') {
1083
this.pc.processIce(message.payload);
1084
} else if (message.type === 'connectivityError') {
1085
this.parent.emit('connectivityError', self);
1086
} else if (message.type === 'mute') {
1087
this.parent.emit('mute', {id: message.from, name: message.payload.name});
1088
} else if (message.type === 'unmute') {
1089
this.parent.emit('unmute', {id: message.from, name: message.payload.name});
1090
}
1091
};
1092
1093
// send via signalling channel
1094
Peer.prototype.send = function (messageType, payload) {
1095
var message = {
1096
to: this.id,
1097
sid: this.sid,
1098
broadcaster: this.broadcaster,
1099
roomType: this.type,
1100
type: messageType,
1101
payload: payload,
1102
prefix: webrtc.prefix
1103
};
1104
this.logger.log('sending', messageType, message);
1105
this.parent.emit('message', message);
1106
};
1107
1108
// send via data channel
1109
// returns true when message was sent and false if channel is not open
1110
Peer.prototype.sendDirectly = function (channel, messageType, payload) {
1111
var message = {
1112
type: messageType,
1113
payload: payload
1114
};
1115
this.logger.log('sending via datachannel', channel, messageType, message);
1116
var dc = this.getDataChannel(channel);
1117
if (dc.readyState != 'open') return false;
1118
dc.send(JSON.stringify(message));
1119
return true;
1120
};
1121
1122
// Internal method registering handlers for a data channel and emitting events on the peer
1123
Peer.prototype._observeDataChannel = function (channel) {
1124
var self = this;
1125
channel.onclose = this.emit.bind(this, 'channelClose', channel);
1126
channel.onerror = this.emit.bind(this, 'channelError', channel);
1127
channel.onmessage = function (event) {
1128
self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
1129
};
1130
channel.onopen = this.emit.bind(this, 'channelOpen', channel);
1131
};
1132
1133
// Fetch or create a data channel by the given name
1134
Peer.prototype.getDataChannel = function (name, opts) {
1135
if (!webrtc.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported'));
1136
var channel = this.channels[name];
1137
opts || (opts = {});
1138
if (channel) return channel;
1139
// if we don't have one by this label, create it
1140
channel = this.channels[name] = this.pc.createDataChannel(name, opts);
1141
this._observeDataChannel(channel);
1142
return channel;
1143
};
1144
1145
Peer.prototype.onIceCandidate = function (candidate) {
1146
if (this.closed) return;
1147
if (candidate) {
1148
this.send('candidate', candidate);
1149
} else {
1150
this.logger.log("End of candidates.");
1151
}
1152
};
1153
1154
Peer.prototype.start = function () {
1155
var self = this;
1156
1157
// well, the webrtc api requires that we either
1158
// a) create a datachannel a priori
1159
// b) do a renegotiation later to add the SCTP m-line
1160
// Let's do (a) first...
1161
if (this.enableDataChannels) {
1162
this.getDataChannel('simplewebrtc');
1163
}
1164
1165
this.pc.offer(this.receiveMedia, function (err, sessionDescription) {
1166
//self.send('offer', sessionDescription);
1167
});
1168
};
1169
1170
Peer.prototype.icerestart = function () {
1171
var constraints = this.receiveMedia;
1172
constraints.mandatory.IceRestart = true;
1173
this.pc.offer(constraints, function (err, success) { });
1174
};
1175
1176
Peer.prototype.end = function () {
1177
if (this.closed) return;
1178
this.pc.close();
1179
this.handleStreamRemoved();
1180
};
1181
1182
Peer.prototype.handleRemoteStreamAdded = function (event) {
1183
var self = this;
1184
if (this.stream) {
1185
this.logger.warn('Already have a remote stream');
1186
} else {
1187
this.stream = event.stream;
1188
// FIXME: addEventListener('ended', ...) would be nicer
1189
// but does not work in firefox
1190
this.stream.onended = function () {
1191
self.end();
1192
};
1193
this.parent.emit('peerStreamAdded', this);
1194
}
1195
};
1196
1197
Peer.prototype.handleStreamRemoved = function () {
1198
this.parent.peers.splice(this.parent.peers.indexOf(this), 1);
1199
this.closed = true;
1200
this.parent.emit('peerStreamRemoved', this);
1201
};
1202
1203
Peer.prototype.handleDataChannelAdded = function (channel) {
1204
this.channels[channel.label] = channel;
1205
this._observeDataChannel(channel);
1206
};
1207
1208
Peer.prototype.sendFile = function (file) {
1209
var sender = new FileTransfer.Sender();
1210
var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), {
1211
protocol: INBAND_FILETRANSFER_V1
1212
});
1213
// override onopen
1214
dc.onopen = function () {
1215
dc.send(JSON.stringify({
1216
size: file.size,
1217
name: file.name
1218
}));
1219
sender.send(file, dc);
1220
};
1221
// override onclose
1222
dc.onclose = function () {
1223
console.log('sender received transfer');
1224
sender.emit('complete');
1225
};
1226
return sender;
1227
};
1228
1231
},{"filetransfer":11,"rtcpeerconnection":10,"util":2,"webrtcsupport":5,"wildemitter":4}],12:[function(require,module,exports){
1232
// getUserMedia helper by @HenrikJoreteg
1233
var func = (window.navigator.getUserMedia ||
1234
window.navigator.webkitGetUserMedia ||
1235
window.navigator.mozGetUserMedia ||
1236
window.navigator.msGetUserMedia);
1237
1238
1239
module.exports = function (constraints, cb) {
1240
var options, error;
1241
var haveOpts = arguments.length === 2;
1242
var defaultOpts = {video: true, audio: true};
1243
var denied = 'PermissionDeniedError';
1244
var notSatisfied = 'ConstraintNotSatisfiedError';
1245
1246
// make constraints optional
1247
if (!haveOpts) {
1248
cb = constraints;
1249
constraints = defaultOpts;
1250
}
1251
1252
// treat lack of browser support like an error
1253
if (!func) {
1254
// throw proper error per spec
1255
error = new Error('MediaStreamError');
1256
error.name = 'NotSupportedError';
1257
1258
// keep all callbacks async
1259
return window.setTimeout(function () {
1260
cb(error);
1261
}, 0);
1262
}
1263
1264
// make requesting media from non-http sources trigger an error
1265
// current browsers silently drop the request instead
1266
var protocol = window.location.protocol;
1267
if (protocol !== 'http:' && protocol !== 'https:') {
1268
error = new Error('MediaStreamError');
1269
error.name = 'NotSupportedError';
1270
1271
// keep all callbacks async
1272
return window.setTimeout(function () {
1273
cb(error);
1274
}, 0);
1275
}
1276
1277
// normalize error handling when no media types are requested
1278
if (!constraints.audio && !constraints.video) {
1279
error = new Error('MediaStreamError');
1280
error.name = 'NoMediaRequestedError';
1281
1282
// keep all callbacks async
1283
return window.setTimeout(function () {
1284
cb(error);
1285
}, 0);
1286
}
1287
1288
if (localStorage && localStorage.useFirefoxFakeDevice === "true") {
1289
constraints.fake = true;
1290
}
1291
1292
func.call(window.navigator, constraints, function (stream) {
1293
cb(null, stream);
1294
}, function (err) {
1295
var error;
1296
// coerce into an error object since FF gives us a string
1297
// there are only two valid names according to the spec
1298
// we coerce all non-denied to "constraint not satisfied".
1299
if (typeof err === 'string') {
1300
error = new Error('MediaStreamError');
1301
if (err === denied) {
1302
error.name = denied;
1303
} else {
1304
error.name = notSatisfied;
1305
}
1306
} else {
1307
// if we get an error object make sure '.name' property is set
1308
// according to spec: http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermediaerror-and-navigatorusermediaerrorcallback
1309
error = err;
1310
if (!error.name) {
1311
// this is likely chrome which
1312
// sets a property called "ERROR_DENIED" on the error object
1313
// if so we make sure to set a name
1314
if (error[denied]) {
1315
err.name = denied;
1316
} else {
1317
err.name = notSatisfied;
1318
}
1319
}
1320
}
1321
1322
cb(error);
1323
});
1324
};
1325
1326
},{}],7:[function(require,module,exports){
1327
var util = require('util');
1328
var hark = require('hark');
1329
var webrtc = require('webrtcsupport');
1330
var getUserMedia = require('getusermedia');
1331
var getScreenMedia = require('getscreenmedia');
1332
var WildEmitter = require('wildemitter');
1333
var GainController = require('mediastream-gain');
1334
var mockconsole = require('mockconsole');
1335
1336
1337
function LocalMedia(opts) {
1338
WildEmitter.call(this);
1339
1340
var config = this.config = {
1341
autoAdjustMic: false,
1342
detectSpeakingEvents: true,
1343
media: {
1344
audio: true,
1345
video: true
1346
},
1347
logger: mockconsole
1348
};
1349
1350
var item;
1351
for (item in opts) {
1352
this.config[item] = opts[item];
1353
}
1354
1355
this.logger = config.logger;
1356
this._log = this.logger.log.bind(this.logger, 'LocalMedia:');
1357
this._logerror = this.logger.error.bind(this.logger, 'LocalMedia:');
1358
1359
this.screenSharingSupport = webrtc.screenSharing;
1360
1361
this.localStreams = [];
1362
this.localScreens = [];
1363
1364
if (!webrtc.support) {
1365
this._logerror('Your browser does not support local media capture.');
1366
}
1367
}
1368
1369
util.inherits(LocalMedia, WildEmitter);
1370
1371
1372
LocalMedia.prototype.start = function (mediaConstraints, cb) {
1373
var self = this;
1374
var constraints = mediaConstraints || this.config.media;
1375
1376
getUserMedia(constraints, function (err, stream) {
1377
if (!err) {
1378
if (constraints.audio && self.config.detectSpeakingEvents) {
1379
self.setupAudioMonitor(stream, self.config.harkOptions);
1380
}
1381
self.localStreams.push(stream);
1382
1383
if (self.config.autoAdjustMic) {
1384
self.gainController = new GainController(stream);
1385
// start out somewhat muted if we can track audio
1386
self.setMicIfEnabled(0.5);
1387
}
1388
1389
// TODO: might need to migrate to the video tracks onended
1390
// FIXME: firefox does not seem to trigger this...
1391
stream.onended = function () {
1392
/*
1393
var idx = self.localStreams.indexOf(stream);
1394
if (idx > -1) {
1395
self.localScreens.splice(idx, 1);
1396
}
1397
self.emit('localStreamStopped', stream);
1398
*/
1401
self.emit('localStream', stream);
1402
}
1403
if (cb) {
1404
return cb(err, stream);
1405
}
1406
});
1407
};
1409
LocalMedia.prototype.stop = function (stream) {
1410
var self = this;
1411
// FIXME: duplicates cleanup code until fixed in FF
1412
if (stream) {
1413
stream.stop();
1414
self.emit('localStreamStopped', stream);
1415
var idx = self.localStreams.indexOf(stream);
1416
if (idx > -1) {
1417
self.localStreams = self.localStreams.splice(idx, 1);
1418
}
1419
} else {
1420
if (this.audioMonitor) {
1421
this.audioMonitor.stop();
1422
delete this.audioMonitor;
1423
}
1424
this.localStreams.forEach(function (stream) {
1425
stream.stop();
1426
self.emit('localStreamStopped', stream);
1427
});
1428
this.localStreams = [];
1432
LocalMedia.prototype.startScreenShare = function (cb) {
1433
var self = this;
1434
getScreenMedia(function (err, stream) {
1435
if (!err) {
1436
self.localScreens.push(stream);
1438
// TODO: might need to migrate to the video tracks onended
1439
// Firefox does not support .onended but it does not support
1440
// screensharing either
1441
stream.onended = function () {
1442
var idx = self.localScreens.indexOf(stream);
1443
if (idx > -1) {
1444
self.localScreens.splice(idx, 1);
1445
}
1446
self.emit('localScreenStopped', stream);
1447
};
1448
self.emit('localScreen', stream);
1449
}
1458
LocalMedia.prototype.stopScreenShare = function (stream) {
1459
if (stream) {
1460
stream.stop();
1461
} else {
1462
this.localScreens.forEach(function (stream) {
1463
stream.stop();
1464
});
1465
this.localScreens = [];
1469
// Audio controls
1470
LocalMedia.prototype.mute = function () {
1471
this._audioEnabled(false);
1472
this.hardMuted = true;
1473
this.emit('audioOff');
1474
};
1476
LocalMedia.prototype.unmute = function () {
1477
this._audioEnabled(true);
1478
this.hardMuted = false;
1479
this.emit('audioOn');
1480
};
1481
1482
LocalMedia.prototype.setupAudioMonitor = function (stream, harkOptions) {
1483
this._log('Setup audio');
1485
var self = this;
1486
var timeout;
1487
1488
audio.on('speaking', function () {
1489
self.emit('speaking');
1490
if (self.hardMuted) {
1491
return;
1492
}
1493
self.setMicIfEnabled(1);
1496
audio.on('stopped_speaking', function () {
1497
if (timeout) {
1498
clearTimeout(timeout);
1499
}
1501
timeout = setTimeout(function () {
1502
self.emit('stoppedSpeaking');
1503
if (self.hardMuted) {
1504
return;
1505
}
1506
self.setMicIfEnabled(0.5);
1507
}, 1000);
1508
});
1509
audio.on('volume_change', function (volume, treshold) {
1510
self.emit('volumeChange', volume, treshold);
1511
});
1512
};
1514
// We do this as a seperate method in order to
1515
// still leave the "setMicVolume" as a working
1516
// method.
1517
LocalMedia.prototype.setMicIfEnabled = function (volume) {
1518
if (!this.config.autoAdjustMic) {
1519
return;
1524
// Video controls
1525
LocalMedia.prototype.pauseVideo = function () {
1526
this._videoEnabled(false);
1527
this.emit('videoOff');
1528
};
1529
LocalMedia.prototype.resumeVideo = function () {
1530
this._videoEnabled(true);
1531
this.emit('videoOn');
1532
};
1544
// Internal methods for enabling/disabling audio/video
1545
LocalMedia.prototype._audioEnabled = function (bool) {
1546
// work around for chrome 27 bug where disabling tracks
1547
// doesn't seem to work (works in canary, remove when working)
1548
this.setMicIfEnabled(bool ? 1 : 0);
1549
this.localStreams.forEach(function (stream) {
1550
stream.getAudioTracks().forEach(function (track) {
1551
track.enabled = !!bool;
1552
});
1553
});
1554
};
1555
LocalMedia.prototype._videoEnabled = function (bool) {
1556
this.localStreams.forEach(function (stream) {
1557
stream.getVideoTracks().forEach(function (track) {
1558
track.enabled = !!bool;
1559
});
1560
});
1561
};
1563
// check if all audio streams are enabled
1564
LocalMedia.prototype.isAudioEnabled = function () {
1565
var enabled = true;
1566
this.localStreams.forEach(function (stream) {
1567
stream.getAudioTracks().forEach(function (track) {
1568
enabled = enabled && track.enabled;
1569
});
1570
});
1571
return enabled;
1572
};
1574
// check if all video streams are enabled
1575
LocalMedia.prototype.isVideoEnabled = function () {
1576
var enabled = true;
1577
this.localStreams.forEach(function (stream) {
1578
stream.getVideoTracks().forEach(function (track) {
1579
enabled = enabled && track.enabled;
1580
});
1581
});
1582
return enabled;
1583
};
1584
1585
// Backwards Compat
1586
LocalMedia.prototype.startLocalMedia = LocalMedia.prototype.start;
1587
LocalMedia.prototype.stopLocalMedia = LocalMedia.prototype.stop;
1588
1589
// fallback for old .localStream behaviour
1590
Object.defineProperty(LocalMedia.prototype, 'localStream', {
1591
get: function () {
1592
return this.localStreams.length > 0 ? this.localStreams[0] : null;
1593
}
1594
});
1595
// fallback for old .localScreen behaviour
1596
Object.defineProperty(LocalMedia.prototype, 'localScreen', {
1597
get: function () {
1598
return this.localScreens.length > 0 ? this.localScreens[0] : null;
1599
}
1600
});
1601
1602
module.exports = LocalMedia;
1603
1604
},{"getscreenmedia":14,"getusermedia":12,"hark":13,"mediastream-gain":15,"mockconsole":6,"util":2,"webrtcsupport":5,"wildemitter":4}],16:[function(require,module,exports){
1605
// Underscore.js 1.8.2
1606
// http://underscorejs.org
1607
// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
1608
// Underscore may be freely distributed under the MIT license.
1615
// Establish the root object, `window` in the browser, or `exports` on the server.
1616
var root = this;
1618
// Save the previous value of the `_` variable.
1619
var previousUnderscore = root._;
1621
// Save bytes in the minified (but not gzipped) version:
1622
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
1624
// Create quick reference variables for speed access to core prototypes.
1625
var
1626
push = ArrayProto.push,
1627
slice = ArrayProto.slice,
1628
toString = ObjProto.toString,
1629
hasOwnProperty = ObjProto.hasOwnProperty;
1631
// All **ECMAScript 5** native function implementations that we hope to use
1632
// are declared here.
1633
var
1634
nativeIsArray = Array.isArray,
1635
nativeKeys = Object.keys,
1636
nativeBind = FuncProto.bind,
1637
nativeCreate = Object.create;
1639
// Naked function reference for surrogate-prototype-swapping.
1640
var Ctor = function(){};
1642
// Create a safe reference to the Underscore object for use below.
1643
var _ = function(obj) {
1644
if (obj instanceof _) return obj;
1645
if (!(this instanceof _)) return new _(obj);
1646
this._wrapped = obj;
1647
};
1649
// Export the Underscore object for **Node.js**, with
1650
// backwards-compatibility for the old `require()` API. If we're in
1651
// the browser, add `_` as a global object.
1652
if (typeof exports !== 'undefined') {
1653
if (typeof module !== 'undefined' && module.exports) {
1654
exports = module.exports = _;
1655
}
1656
exports._ = _;
1657
} else {
1658
root._ = _;
1659
}
1661
// Current version.
1662
_.VERSION = '1.8.2';
1663
1664
// Internal function that returns an efficient (for current engines) version
1665
// of the passed-in callback, to be repeatedly applied in other Underscore
1666
// functions.
1667
var optimizeCb = function(func, context, argCount) {
1668
if (context === void 0) return func;
1669
switch (argCount == null ? 3 : argCount) {
1670
case 1: return function(value) {
1671
return func.call(context, value);
1672
};
1673
case 2: return function(value, other) {
1674
return func.call(context, value, other);
1675
};
1676
case 3: return function(value, index, collection) {
1677
return func.call(context, value, index, collection);
1678
};
1679
case 4: return function(accumulator, value, index, collection) {
1680
return func.call(context, accumulator, value, index, collection);
1681
};
1682
}
1683
return function() {
1684
return func.apply(context, arguments);
1685
};
1686
};
1688
// A mostly-internal function to generate callbacks that can be applied
1689
// to each element in a collection, returning the desired result — either
1690
// identity, an arbitrary callback, a property matcher, or a property accessor.
1691
var cb = function(value, context, argCount) {
1692
if (value == null) return _.identity;
1693
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
1694
if (_.isObject(value)) return _.matcher(value);
1695
return _.property(value);
1696
};
1697
_.iteratee = function(value, context) {
1698
return cb(value, context, Infinity);
1699
};
1701
// An internal function for creating assigner functions.
1702
var createAssigner = function(keysFunc, undefinedOnly) {
1703
return function(obj) {
1704
var length = arguments.length;
1705
if (length < 2 || obj == null) return obj;
1706
for (var index = 1; index < length; index++) {
1707
var source = arguments[index],
1708
keys = keysFunc(source),
1709
l = keys.length;
1710
for (var i = 0; i < l; i++) {
1711
var key = keys[i];
1712
if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
1713
}
1714
}
1715
return obj;
1719
// An internal function for creating a new object that inherits from another.
1720
var baseCreate = function(prototype) {
1721
if (!_.isObject(prototype)) return {};
1722
if (nativeCreate) return nativeCreate(prototype);
1723
Ctor.prototype = prototype;
1724
var result = new Ctor;
1725
Ctor.prototype = null;
1726
return result;
1727
};
1729
// Helper for collection methods to determine whether a collection
1730
// should be iterated as an array or as an object
1731
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
1732
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
1733
var isArrayLike = function(collection) {
1734
var length = collection && collection.length;
1735
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
1736
};
1738
// Collection Functions
1739
// --------------------
1740
1741
// The cornerstone, an `each` implementation, aka `forEach`.
1742
// Handles raw objects in addition to array-likes. Treats all
1743
// sparse array-likes as if they were dense.
1744
_.each = _.forEach = function(obj, iteratee, context) {
1745
iteratee = optimizeCb(iteratee, context);
1746
var i, length;
1747
if (isArrayLike(obj)) {
1748
for (i = 0, length = obj.length; i < length; i++) {
1749
iteratee(obj[i], i, obj);
1750
}
1751
} else {
1752
var keys = _.keys(obj);
1753
for (i = 0, length = keys.length; i < length; i++) {
1754
iteratee(obj[keys[i]], keys[i], obj);
1755
}
1760
// Return the results of applying the iteratee to each element.
1761
_.map = _.collect = function(obj, iteratee, context) {
1762
iteratee = cb(iteratee, context);
1763
var keys = !isArrayLike(obj) && _.keys(obj),
1764
length = (keys || obj).length,
1765
results = Array(length);
1766
for (var index = 0; index < length; index++) {
1767
var currentKey = keys ? keys[index] : index;
1768
results[index] = iteratee(obj[currentKey], currentKey, obj);
1773
// Create a reducing function iterating left or right.
1774
function createReduce(dir) {
1775
// Optimized iterator function as using arguments.length
1776
// in the main function will deoptimize the, see #1991.
1777
function iterator(obj, iteratee, memo, keys, index, length) {
1778
for (; index >= 0 && index < length; index += dir) {
1779
var currentKey = keys ? keys[index] : index;
1780
memo = iteratee(memo, obj[currentKey], currentKey, obj);
1781
}
1782
return memo;
1783
}
1785
return function(obj, iteratee, memo, context) {
1786
iteratee = optimizeCb(iteratee, context, 4);
1787
var keys = !isArrayLike(obj) && _.keys(obj),
1788
length = (keys || obj).length,
1789
index = dir > 0 ? 0 : length - 1;
1790
// Determine the initial value if none is provided.
1791
if (arguments.length < 3) {
1792
memo = obj[keys ? keys[index] : index];
1793
index += dir;
1794
}
1795
return iterator(obj, iteratee, memo, keys, index, length);
1796
};
1797
}
1799
// **Reduce** builds up a single result from a list of values, aka `inject`,
1800
// or `foldl`.
1801
_.reduce = _.foldl = _.inject = createReduce(1);
1802
1803
// The right-associative version of reduce, also known as `foldr`.
1804
_.reduceRight = _.foldr = createReduce(-1);
1805
1806
// Return the first value which passes a truth test. Aliased as `detect`.
1807
_.find = _.detect = function(obj, predicate, context) {
1808
var key;
1809
if (isArrayLike(obj)) {
1810
key = _.findIndex(obj, predicate, context);
1811
} else {
1812
key = _.findKey(obj, predicate, context);
1814
if (key !== void 0 && key !== -1) return obj[key];
1815
};
1816
1817
// Return all the elements that pass a truth test.
1818
// Aliased as `select`.
1819
_.filter = _.select = function(obj, predicate, context) {
1820
var results = [];
1821
predicate = cb(predicate, context);
1822
_.each(obj, function(value, index, list) {
1823
if (predicate(value, index, list)) results.push(value);
1824
});
1825
return results;
1826
};
1827
1828
// Return all the elements for which a truth test fails.
1829
_.reject = function(obj, predicate, context) {
1830
return _.filter(obj, _.negate(cb(predicate)), context);
1831
};
1832
1833
// Determine whether all of the elements match a truth test.
1834
// Aliased as `all`.
1835
_.every = _.all = function(obj, predicate, context) {
1836
predicate = cb(predicate, context);
1837
var keys = !isArrayLike(obj) && _.keys(obj),
1838
length = (keys || obj).length;
1839
for (var index = 0; index < length; index++) {
1840
var currentKey = keys ? keys[index] : index;
1841
if (!predicate(obj[currentKey], currentKey, obj)) return false;
1846
// Determine if at least one element in the object matches a truth test.
1847
// Aliased as `any`.
1848
_.some = _.any = function(obj, predicate, context) {
1849
predicate = cb(predicate, context);
1850
var keys = !isArrayLike(obj) && _.keys(obj),
1851
length = (keys || obj).length;
1852
for (var index = 0; index < length; index++) {
1853
var currentKey = keys ? keys[index] : index;
1854
if (predicate(obj[currentKey], currentKey, obj)) return true;
1855
}
1856
return false;
1857
};
1859
// Determine if the array or object contains a given value (using `===`).
1860
// Aliased as `includes` and `include`.
1861
_.contains = _.includes = _.include = function(obj, target, fromIndex) {
1862
if (!isArrayLike(obj)) obj = _.values(obj);
1863
return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0;
1864
};
1866
// Invoke a method (with arguments) on every item in a collection.
1867
_.invoke = function(obj, method) {
1868
var args = slice.call(arguments, 2);
1869
var isFunc = _.isFunction(method);
1870
return _.map(obj, function(value) {
1871
var func = isFunc ? method : value[method];
1872
return func == null ? func : func.apply(value, args);
1873
});
1874
};
1876
// Convenience version of a common use case of `map`: fetching a property.
1877
_.pluck = function(obj, key) {
1878
return _.map(obj, _.property(key));
1879
};
1881
// Convenience version of a common use case of `filter`: selecting only objects
1882
// containing specific `key:value` pairs.
1883
_.where = function(obj, attrs) {
1884
return _.filter(obj, _.matcher(attrs));
1885
};
1887
// Convenience version of a common use case of `find`: getting the first object
1888
// containing specific `key:value` pairs.
1889
_.findWhere = function(obj, attrs) {
1890
return _.find(obj, _.matcher(attrs));
1891
};
1892
1893
// Return the maximum element (or element-based computation).
1894
_.max = function(obj, iteratee, context) {
1895
var result = -Infinity, lastComputed = -Infinity,
1896
value, computed;
1897
if (iteratee == null && obj != null) {
1898
obj = isArrayLike(obj) ? obj : _.values(obj);
1899
for (var i = 0, length = obj.length; i < length; i++) {
1900
value = obj[i];
1901
if (value > result) {
1902
result = value;
1903
}
1904
}
1906
iteratee = cb(iteratee, context);
1907
_.each(obj, function(value, index, list) {
1908
computed = iteratee(value, index, list);
1909
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
1910
result = value;
1911
lastComputed = computed;
1918
// Return the minimum element (or element-based computation).
1919
_.min = function(obj, iteratee, context) {
1920
var result = Infinity, lastComputed = Infinity,
1921
value, computed;
1922
if (iteratee == null && obj != null) {
1923
obj = isArrayLike(obj) ? obj : _.values(obj);
1924
for (var i = 0, length = obj.length; i < length; i++) {
1925
value = obj[i];
1926
if (value < result) {
1927
result = value;
1929
}
1930
} else {
1931
iteratee = cb(iteratee, context);
1932
_.each(obj, function(value, index, list) {
1933
computed = iteratee(value, index, list);
1934
if (computed < lastComputed || computed === Infinity && result === Infinity) {
1935
result = value;
1936
lastComputed = computed;
1943
// Shuffle a collection, using the modern version of the
1944
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
1945
_.shuffle = function(obj) {
1946
var set = isArrayLike(obj) ? obj : _.values(obj);
1947
var length = set.length;
1948
var shuffled = Array(length);
1949
for (var index = 0, rand; index < length; index++) {
1950
rand = _.random(0, index);
1951
if (rand !== index) shuffled[index] = shuffled[rand];
1952
shuffled[rand] = set[index];
1953
}
1954
return shuffled;
1955
};
1957
// Sample **n** random values from a collection.
1958
// If **n** is not specified, returns a single random element.
1959
// The internal `guard` argument allows it to work with `map`.
1960
_.sample = function(obj, n, guard) {
1961
if (n == null || guard) {
1962
if (!isArrayLike(obj)) obj = _.values(obj);
1963
return obj[_.random(obj.length - 1)];
1964
}
1965
return _.shuffle(obj).slice(0, Math.max(0, n));
1966
};
1968
// Sort the object's values by a criterion produced by an iteratee.
1969
_.sortBy = function(obj, iteratee, context) {
1970
iteratee = cb(iteratee, context);
1971
return _.pluck(_.map(obj, function(value, index, list) {
1972
return {
1973
value: value,
1974
index: index,
1975
criteria: iteratee(value, index, list)
1976
};
1977
}).sort(function(left, right) {
1978
var a = left.criteria;
1979
var b = right.criteria;
1980
if (a !== b) {
1981
if (a > b || a === void 0) return 1;
1982
if (a < b || b === void 0) return -1;
1983
}
1984
return left.index - right.index;
1985
}), 'value');
1986
};
1988
// An internal function used for aggregate "group by" operations.
1989
var group = function(behavior) {
1990
return function(obj, iteratee, context) {
1991
var result = {};
1992
iteratee = cb(iteratee, context);
1993
_.each(obj, function(value, index) {
1994
var key = iteratee(value, index, obj);
1995
behavior(result, value, key);
1996
});
1997
return result;
1998
};
1999
};
2001
// Groups the object's values by a criterion. Pass either a string attribute
2002
// to group by, or a function that returns the criterion.
2003
_.groupBy = group(function(result, value, key) {
2004
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
2005
});
2007
// Indexes the object's values by a criterion, similar to `groupBy`, but for
2008
// when you know that your index values will be unique.
2009
_.indexBy = group(function(result, value, key) {
2010
result[key] = value;
2011
});
2013
// Counts instances of an object that group by a certain criterion. Pass
2014
// either a string attribute to count by, or a function that returns the
2015
// criterion.
2016
_.countBy = group(function(result, value, key) {
2017
if (_.has(result, key)) result[key]++; else result[key] = 1;
2018
});
2020
// Safely create a real, live array from anything iterable.
2021
_.toArray = function(obj) {
2022
if (!obj) return [];
2023
if (_.isArray(obj)) return slice.call(obj);
2024
if (isArrayLike(obj)) return _.map(obj, _.identity);
2025
return _.values(obj);
2026
};
2028
// Return the number of elements in an object.
2029
_.size = function(obj) {
2030
if (obj == null) return 0;
2031
return isArrayLike(obj) ? obj.length : _.keys(obj).length;
2032
};
2034
// Split a collection into two arrays: one whose elements all satisfy the given
2035
// predicate, and one whose elements all do not satisfy the predicate.
2036
_.partition = function(obj, predicate, context) {
2037
predicate = cb(predicate, context);
2038
var pass = [], fail = [];
2039
_.each(obj, function(value, key, obj) {
2040
(predicate(value, key, obj) ? pass : fail).push(value);
2041
});
2042
return [pass, fail];
2043
};
2048
// Get the first element of an array. Passing **n** will return the first N
2049
// values in the array. Aliased as `head` and `take`. The **guard** check
2050
// allows it to work with `_.map`.
2051
_.first = _.head = _.take = function(array, n, guard) {
2052
if (array == null) return void 0;
2053
if (n == null || guard) return array[0];
2054
return _.initial(array, array.length - n);
2055
};
2057
// Returns everything but the last entry of the array. Especially useful on
2058
// the arguments object. Passing **n** will return all the values in
2059
// the array, excluding the last N.
2060
_.initial = function(array, n, guard) {
2061
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
2062
};
2064
// Get the last element of an array. Passing **n** will return the last N
2065
// values in the array.
2066
_.last = function(array, n, guard) {
2067
if (array == null) return void 0;
2068
if (n == null || guard) return array[array.length - 1];
2069
return _.rest(array, Math.max(0, array.length - n));
2070
};
2072
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
2073
// Especially useful on the arguments object. Passing an **n** will return
2074
// the rest N values in the array.
2075
_.rest = _.tail = _.drop = function(array, n, guard) {
2076
return slice.call(array, n == null || guard ? 1 : n);
2077
};
2079
// Trim out all falsy values from an array.
2080
_.compact = function(array) {
2081
return _.filter(array, _.identity);
2084
// Internal implementation of a recursive `flatten` function.
2085
var flatten = function(input, shallow, strict, startIndex) {
2086
var output = [], idx = 0;
2087
for (var i = startIndex || 0, length = input && input.length; i < length; i++) {
2088
var value = input[i];
2089
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
2090
//flatten current level of array or arguments object
2091
if (!shallow) value = flatten(value, shallow, strict);
2092
var j = 0, len = value.length;
2093
output.length += len;
2094
while (j < len) {
2095
output[idx++] = value[j++];
2096
}
2097
} else if (!strict) {
2098
output[idx++] = value;
2099
}
2104
// Flatten out an array, either recursively (by default), or just one level.
2105
_.flatten = function(array, shallow) {
2106
return flatten(array, shallow, false);
2107
};
2109
// Return a version of the array that does not contain the specified value(s).
2110
_.without = function(array) {
2111
return _.difference(array, slice.call(arguments, 1));
2112
};
2114
// Produce a duplicate-free version of the array. If the array has already
2115
// been sorted, you have the option of using a faster algorithm.
2116
// Aliased as `unique`.
2117
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
2118
if (array == null) return [];
2119
if (!_.isBoolean(isSorted)) {
2120
context = iteratee;
2121
iteratee = isSorted;
2122
isSorted = false;
2123
}
2124
if (iteratee != null) iteratee = cb(iteratee, context);
2125
var result = [];
2126
var seen = [];
2127
for (var i = 0, length = array.length; i < length; i++) {
2128
var value = array[i],
2129
computed = iteratee ? iteratee(value, i, array) : value;
2130
if (isSorted) {
2131
if (!i || seen !== computed) result.push(value);
2132
seen = computed;
2133
} else if (iteratee) {
2134
if (!_.contains(seen, computed)) {
2135
seen.push(computed);
2136
result.push(value);
2137
}
2138
} else if (!_.contains(result, value)) {
2139
result.push(value);
2141
}
2142
return result;
2143
};
2144
2145
// Produce an array that contains the union: each distinct element from all of
2146
// the passed-in arrays.
2147
_.union = function() {
2148
return _.uniq(flatten(arguments, true, true));
2149
};
2150
2151
// Produce an array that contains every item shared between all the
2152
// passed-in arrays.
2153
_.intersection = function(array) {
2154
if (array == null) return [];
2155
var result = [];
2156
var argsLength = arguments.length;
2157
for (var i = 0, length = array.length; i < length; i++) {
2158
var item = array[i];
2159
if (_.contains(result, item)) continue;
2160
for (var j = 1; j < argsLength; j++) {
2161
if (!_.contains(arguments[j], item)) break;
2168
// Take the difference between one array and a number of other arrays.
2169
// Only the elements present in just the first array will remain.
2170
_.difference = function(array) {
2171
var rest = flatten(arguments, true, true, 1);
2172
return _.filter(array, function(value){
2173
return !_.contains(rest, value);
2177
// Zip together multiple lists into a single array -- elements that share
2178
// an index go together.
2179
_.zip = function() {
2180
return _.unzip(arguments);
2181
};
2182
2183
// Complement of _.zip. Unzip accepts an array of arrays and groups
2184
// each array's elements on shared indices
2185
_.unzip = function(array) {
2186
var length = array && _.max(array, 'length').length || 0;
2187
var result = Array(length);
2189
for (var index = 0; index < length; index++) {
2190
result[index] = _.pluck(array, index);
2191
}
2192
return result;
2193
};
2194
2195
// Converts lists into objects. Pass either a single array of `[key, value]`
2196
// pairs, or two parallel arrays of the same length -- one of keys, and one of
2197
// the corresponding values.
2198
_.object = function(list, values) {
2199
var result = {};
2200
for (var i = 0, length = list && list.length; i < length; i++) {
2201
if (values) {
2202
result[list[i]] = values[i];
2210
// Return the position of the first occurrence of an item in an array,
2211
// or -1 if the item is not included in the array.
2212
// If the array is large and already in sort order, pass `true`
2213
// for **isSorted** to use binary search.
2214
_.indexOf = function(array, item, isSorted) {
2215
var i = 0, length = array && array.length;
2216
if (typeof isSorted == 'number') {
2217
i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
2218
} else if (isSorted && length) {
2219
i = _.sortedIndex(array, item);
2220
return array[i] === item ? i : -1;
2222
if (item !== item) {
2223
return _.findIndex(slice.call(array, i), _.isNaN);
2224
}
2225
for (; i < length; i++) if (array[i] === item) return i;
2226
return -1;
2229
_.lastIndexOf = function(array, item, from) {
2230
var idx = array ? array.length : 0;
2231
if (typeof from == 'number') {
2232
idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
2233
}
2234
if (item !== item) {
2235
return _.findLastIndex(slice.call(array, 0, idx), _.isNaN);
2236
}
2237
while (--idx >= 0) if (array[idx] === item) return idx;
2238
return -1;
2239
};
2240
2241
// Generator function to create the findIndex and findLastIndex functions
2242
function createIndexFinder(dir) {
2243
return function(array, predicate, context) {
2244
predicate = cb(predicate, context);
2245
var length = array != null && array.length;
2246
var index = dir > 0 ? 0 : length - 1;
2247
for (; index >= 0 && index < length; index += dir) {
2248
if (predicate(array[index], index, array)) return index;
2250
return -1;
2251
};
2252
}
2253
2254
// Returns the first index on an array-like that passes a predicate test
2255
_.findIndex = createIndexFinder(1);
2256
2257
_.findLastIndex = createIndexFinder(-1);
2258
2259
// Use a comparator function to figure out the smallest index at which
2260
// an object should be inserted so as to maintain order. Uses binary search.
2261
_.sortedIndex = function(array, obj, iteratee, context) {
2262
iteratee = cb(iteratee, context, 1);
2263
var value = iteratee(obj);
2264
var low = 0, high = array.length;
2265
while (low < high) {
2266
var mid = Math.floor((low + high) / 2);
2267
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
2268
}
2269
return low;
2272
// Generate an integer Array containing an arithmetic progression. A port of
2273
// the native Python `range()` function. See
2274
// [the Python documentation](http://docs.python.org/library/functions.html#range).
2275
_.range = function(start, stop, step) {
2276
if (arguments.length <= 1) {
2277
stop = start || 0;
2278
start = 0;
2279
}
2280
step = step || 1;
2281
2282
var length = Math.max(Math.ceil((stop - start) / step), 0);
2283
var range = Array(length);
2284
2285
for (var idx = 0; idx < length; idx++, start += step) {
2286
range[idx] = start;
2287
}
2288
2289
return range;
2292
// Function (ahem) Functions
2293
// ------------------
2294
2295
// Determines whether to execute a function as a constructor
2296
// or a normal function with the provided arguments
2297
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
2298
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
2299
var self = baseCreate(sourceFunc.prototype);
2300
var result = sourceFunc.apply(self, args);
2301
if (_.isObject(result)) return result;
2302
return self;
2305
// Create a function bound to a given object (assigning `this`, and arguments,
2306
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
2307
// available.
2308
_.bind = function(func, context) {
2309
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
2310
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
2311
var args = slice.call(arguments, 2);
2312
var bound = function() {
2313
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
2314
};
2315
return bound;
2318
// Partially apply a function by creating a version that has had some of its
2319
// arguments pre-filled, without changing its dynamic `this` context. _ acts
2320
// as a placeholder, allowing any combination of arguments to be pre-filled.
2321
_.partial = function(func) {
2322
var boundArgs = slice.call(arguments, 1);
2323
var bound = function() {
2324
var position = 0, length = boundArgs.length;
2325
var args = Array(length);
2326
for (var i = 0; i < length; i++) {
2327
args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
2328
}
2329
while (position < arguments.length) args.push(arguments[position++]);
2330
return executeBound(func, bound, this, this, args);
2331
};
2332
return bound;
2335
// Bind a number of an object's methods to that object. Remaining arguments
2336
// are the method names to be bound. Useful for ensuring that all callbacks
2337
// defined on an object belong to it.
2338
_.bindAll = function(obj) {
2339
var i, length = arguments.length, key;
2340
if (length <= 1) throw new Error('bindAll must be passed function names');
2341
for (i = 1; i < length; i++) {
2342
key = arguments[i];
2343
obj[key] = _.bind(obj[key], obj);
2344
}
2345
return obj;
2348
// Memoize an expensive function by storing its results.
2349
_.memoize = function(func, hasher) {
2350
var memoize = function(key) {
2351
var cache = memoize.cache;
2352
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
2353
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
2354
return cache[address];
2355
};
2356
memoize.cache = {};
2357
return memoize;
2358
};
2359
2360
// Delays a function for the given number of milliseconds, and then calls
2361
// it with the arguments supplied.
2362
_.delay = function(func, wait) {
2369
// Defers a function, scheduling it to run after the current call stack has
2370
// cleared.
2371
_.defer = _.partial(_.delay, _, 1);
2372
2373
// Returns a function, that, when invoked, will only be triggered at most once
2374
// during a given window of time. Normally, the throttled function will run
2375
// as much as it can, without ever going more than once per `wait` duration;
2376
// but if you'd like to disable the execution on the leading edge, pass
2377
// `{leading: false}`. To disable execution on the trailing edge, ditto.
2378
_.throttle = function(func, wait, options) {
2379
var context, args, result;
2380
var timeout = null;
2381
var previous = 0;
2382
if (!options) options = {};
2383
var later = function() {
2384
previous = options.leading === false ? 0 : _.now();
2385
timeout = null;
2386
result = func.apply(context, args);
2387
if (!timeout) context = args = null;
2388
};
2389
return function() {
2390
var now = _.now();
2391
if (!previous && options.leading === false) previous = now;
2392
var remaining = wait - (now - previous);
2393
context = this;
2394
args = arguments;
2395
if (remaining <= 0 || remaining > wait) {
2396
if (timeout) {
2397
clearTimeout(timeout);
2398
timeout = null;
2399
}
2400
previous = now;
2401
result = func.apply(context, args);
2402
if (!timeout) context = args = null;
2403
} else if (!timeout && options.trailing !== false) {
2404
timeout = setTimeout(later, remaining);
2405
}
2406
return result;
2407
};
2410
// Returns a function, that, as long as it continues to be invoked, will not
2411
// be triggered. The function will be called after it stops being called for
2412
// N milliseconds. If `immediate` is passed, trigger the function on the
2413
// leading edge, instead of the trailing.
2414
_.debounce = function(func, wait, immediate) {
2415
var timeout, args, context, timestamp, result;
2420
if (last < wait && last >= 0) {
2421
timeout = setTimeout(later, wait - last);
2422
} else {
2423
timeout = null;
2424
if (!immediate) {
2425
result = func.apply(context, args);
2426
if (!timeout) context = args = null;
2427
}
2431
return function() {
2432
context = this;
2433
args = arguments;
2434
timestamp = _.now();
2435
var callNow = immediate && !timeout;
2436
if (!timeout) timeout = setTimeout(later, wait);
2437
if (callNow) {
2438
result = func.apply(context, args);
2439
context = args = null;
2446
// Returns the first function passed as an argument to the second,
2447
// allowing you to adjust arguments, run code before and after, and
2448
// conditionally execute the original function.
2449
_.wrap = function(func, wrapper) {
2450
return _.partial(wrapper, func);
2453
// Returns a negated version of the passed-in predicate.
2454
_.negate = function(predicate) {
2455
return function() {
2456
return !predicate.apply(this, arguments);
2457
};
2460
// Returns a function that is the composition of a list of functions, each
2461
// consuming the return value of the function that follows.
2462
_.compose = function() {
2463
var args = arguments;
2464
var start = args.length - 1;
2465
return function() {
2466
var i = start;
2467
var result = args[start].apply(this, arguments);
2468
while (i--) result = args[i].call(this, result);
2473
// Returns a function that will only be executed on and after the Nth call.
2474
_.after = function(times, func) {
2475
return function() {
2476
if (--times < 1) {
2477
return func.apply(this, arguments);
2478
}
2479
};
2482
// Returns a function that will only be executed up to (but not including) the Nth call.
2483
_.before = function(times, func) {
2484
var memo;
2485
return function() {
2486
if (--times > 0) {
2487
memo = func.apply(this, arguments);
2488
}
2489
if (times <= 1) func = null;
2490
return memo;
2491
};
2494
// Returns a function that will be executed at most one time, no matter how
2495
// often you call it. Useful for lazy initialization.
2496
_.once = _.partial(_.before, 2);
2501
// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
2502
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
2503
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
2504
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
2506
function collectNonEnumProps(obj, keys) {
2507
var nonEnumIdx = nonEnumerableProps.length;
2508
var constructor = obj.constructor;
2509
var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
2511
// Constructor is a special case.
2512
var prop = 'constructor';
2513
if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
2515
while (nonEnumIdx--) {
2516
prop = nonEnumerableProps[nonEnumIdx];
2517
if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
2518
keys.push(prop);
2523
// Retrieve the names of an object's own properties.
2524
// Delegates to **ECMAScript 5**'s native `Object.keys`
2525
_.keys = function(obj) {
2526
if (!_.isObject(obj)) return [];
2527
if (nativeKeys) return nativeKeys(obj);
2528
var keys = [];
2529
for (var key in obj) if (_.has(obj, key)) keys.push(key);
2530
// Ahem, IE < 9.
2531
if (hasEnumBug) collectNonEnumProps(obj, keys);
2532
return keys;
2535
// Retrieve all the property names of an object.
2536
_.allKeys = function(obj) {
2537
if (!_.isObject(obj)) return [];
2538
var keys = [];
2539
for (var key in obj) keys.push(key);
2540
// Ahem, IE < 9.
2541
if (hasEnumBug) collectNonEnumProps(obj, keys);
2542
return keys;
2545
// Retrieve the values of an object's properties.
2546
_.values = function(obj) {
2547
var keys = _.keys(obj);
2548
var length = keys.length;
2549
var values = Array(length);
2550
for (var i = 0; i < length; i++) {
2551
values[i] = obj[keys[i]];
2556
// Returns the results of applying the iteratee to each element of the object
2557
// In contrast to _.map it returns an object
2558
_.mapObject = function(obj, iteratee, context) {
2559
iteratee = cb(iteratee, context);
2560
var keys = _.keys(obj),
2561
length = keys.length,
2562
results = {},
2563
currentKey;
2564
for (var index = 0; index < length; index++) {
2565
currentKey = keys[index];
2566
results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
2567
}
2568
return results;
2571
// Convert an object into a list of `[key, value]` pairs.
2572
_.pairs = function(obj) {
2573
var keys = _.keys(obj);
2574
var length = keys.length;
2575
var pairs = Array(length);
2576
for (var i = 0; i < length; i++) {
2577
pairs[i] = [keys[i], obj[keys[i]]];
2578
}
2579
return pairs;
2582
// Invert the keys and values of an object. The values must be serializable.
2583
_.invert = function(obj) {
2584
var result = {};
2585
var keys = _.keys(obj);
2586
for (var i = 0, length = keys.length; i < length; i++) {
2587
result[obj[keys[i]]] = keys[i];
2588
}
2589
return result;
2592
// Return a sorted list of the function names available on the object.
2593
// Aliased as `methods`
2594
_.functions = _.methods = function(obj) {
2595
var names = [];
2596
for (var key in obj) {
2597
if (_.isFunction(obj[key])) names.push(key);
2602
// Extend a given object with all the properties in passed-in object(s).
2603
_.extend = createAssigner(_.allKeys);
2604
2605
// Assigns a given object with all the own properties in the passed-in object(s)
2606
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
2607
_.extendOwn = _.assign = createAssigner(_.keys);
2608
2609
// Returns the first key on an object that passes a predicate test
2610
_.findKey = function(obj, predicate, context) {
2611
predicate = cb(predicate, context);
2612
var keys = _.keys(obj), key;
2613
for (var i = 0, length = keys.length; i < length; i++) {
2614
key = keys[i];
2615
if (predicate(obj[key], key, obj)) return key;
2619
// Return a copy of the object only containing the whitelisted properties.
2620
_.pick = function(object, oiteratee, context) {
2621
var result = {}, obj = object, iteratee, keys;
2622
if (obj == null) return result;
2623
if (_.isFunction(oiteratee)) {
2624
keys = _.allKeys(obj);
2625
iteratee = optimizeCb(oiteratee, context);
2626
} else {
2627
keys = flatten(arguments, false, false, 1);
2628
iteratee = function(value, key, obj) { return key in obj; };
2629
obj = Object(obj);
2631
for (var i = 0, length = keys.length; i < length; i++) {
2632
var key = keys[i];
2633
var value = obj[key];
2634
if (iteratee(value, key, obj)) result[key] = value;
2635
}
2636
return result;
2639
// Return a copy of the object without the blacklisted properties.
2640
_.omit = function(obj, iteratee, context) {
2641
if (_.isFunction(iteratee)) {
2642
iteratee = _.negate(iteratee);
2643
} else {
2644
var keys = _.map(flatten(arguments, false, false, 1), String);
2645
iteratee = function(value, key) {
2646
return !_.contains(keys, key);
2647
};
2652
// Fill in a given object with default properties.
2653
_.defaults = createAssigner(_.allKeys, true);
2654
2655
// Create a (shallow-cloned) duplicate of an object.
2656
_.clone = function(obj) {
2657
if (!_.isObject(obj)) return obj;
2658
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
2659
};
2660
2661
// Invokes interceptor with the obj, and then returns obj.
2662
// The primary purpose of this method is to "tap into" a method chain, in
2663
// order to perform operations on intermediate results within the chain.
2664
_.tap = function(obj, interceptor) {
2665
interceptor(obj);
2666
return obj;
2667
};
2668
2669
// Returns whether an object has a given set of `key:value` pairs.
2670
_.isMatch = function(object, attrs) {
2671
var keys = _.keys(attrs), length = keys.length;
2672
if (object == null) return !length;
2673
var obj = Object(object);
2674
for (var i = 0; i < length; i++) {
2675
var key = keys[i];
2676
if (attrs[key] !== obj[key] || !(key in obj)) return false;
2682
// Internal recursive comparison function for `isEqual`.
2683
var eq = function(a, b, aStack, bStack) {
2684
// Identical objects are equal. `0 === -0`, but they aren't identical.
2685
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
2686
if (a === b) return a !== 0 || 1 / a === 1 / b;
2687
// A strict comparison is necessary because `null == undefined`.
2688
if (a == null || b == null) return a === b;
2689
// Unwrap any wrapped objects.
2690
if (a instanceof _) a = a._wrapped;
2691
if (b instanceof _) b = b._wrapped;
2692
// Compare `[[Class]]` names.
2693
var className = toString.call(a);
2694
if (className !== toString.call(b)) return false;
2695
switch (className) {
2696
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
2697
case '[object RegExp]':
2698
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
2699
case '[object String]':
2700
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
2701
// equivalent to `new String("5")`.
2702
return '' + a === '' + b;
2703
case '[object Number]':
2704
// `NaN`s are equivalent, but non-reflexive.
2705
// Object(NaN) is equivalent to NaN
2706
if (+a !== +a) return +b !== +b;
2707
// An `egal` comparison is performed for other numeric values.
2708
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
2709
case '[object Date]':
2710
case '[object Boolean]':
2711
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
2712
// millisecond representations. Note that invalid dates with millisecond representations
2713
// of `NaN` are not equivalent.
2714
return +a === +b;
2717
var areArrays = className === '[object Array]';
2718
if (!areArrays) {
2719
if (typeof a != 'object' || typeof b != 'object') return false;
2721
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
2722
// from different frames are.
2723
var aCtor = a.constructor, bCtor = b.constructor;
2724
if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
2725
_.isFunction(bCtor) && bCtor instanceof bCtor)
2726
&& ('constructor' in a && 'constructor' in b)) {
2727
return false;
2728
}
2729
}
2730
// Assume equality for cyclic structures. The algorithm for detecting cyclic
2731
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
2732
2733
// Initializing stack of traversed objects.
2734
// It's done here since we only need them for objects and arrays comparison.
2735
aStack = aStack || [];
2736
bStack = bStack || [];
2737
var length = aStack.length;
2738
while (length--) {
2739
// Linear search. Performance is inversely proportional to the number of
2740
// unique nested structures.
2741
if (aStack[length] === a) return bStack[length] === b;
2742
}
2744
// Add the first object to the stack of traversed objects.
2745
aStack.push(a);
2746
bStack.push(b);
2748
// Recursively compare objects and arrays.
2749
if (areArrays) {
2750
// Compare array lengths to determine if a deep comparison is necessary.
2751
length = a.length;
2752
if (length !== b.length) return false;
2753
// Deep compare the contents, ignoring non-numeric properties.
2754
while (length--) {
2755
if (!eq(a[length], b[length], aStack, bStack)) return false;
2756
}
2757
} else {
2758
// Deep compare objects.
2759
var keys = _.keys(a), key;
2760
length = keys.length;
2761
// Ensure that both objects contain the same number of properties before comparing deep equality.
2762
if (_.keys(b).length !== length) return false;
2763
while (length--) {
2764
// Deep compare each member
2765
key = keys[length];
2766
if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
2767
}
2768
}
2769
// Remove the first object from the stack of traversed objects.
2770
aStack.pop();
2771
bStack.pop();
2772
return true;
2775
// Perform a deep comparison to check if two objects are equal.
2776
_.isEqual = function(a, b) {
2777
return eq(a, b);
2780
// Is a given array, string, or object empty?
2781
// An "empty" object has no enumerable own-properties.
2782
_.isEmpty = function(obj) {
2783
if (obj == null) return true;
2784
if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
2785
return _.keys(obj).length === 0;
2788
// Is a given value a DOM element?
2789
_.isElement = function(obj) {
2790
return !!(obj && obj.nodeType === 1);
2793
// Is a given value an array?
2794
// Delegates to ECMA5's native Array.isArray
2795
_.isArray = nativeIsArray || function(obj) {
2796
return toString.call(obj) === '[object Array]';
2799
// Is a given variable an object?
2800
_.isObject = function(obj) {
2801
var type = typeof obj;
2802
return type === 'function' || type === 'object' && !!obj;
2805
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
2806
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
2807
_['is' + name] = function(obj) {
2808
return toString.call(obj) === '[object ' + name + ']';
2810
});
2811
2812
// Define a fallback version of the method in browsers (ahem, IE < 9), where
2813
// there isn't any inspectable "Arguments" type.
2814
if (!_.isArguments(arguments)) {
2815
_.isArguments = function(obj) {
2816
return _.has(obj, 'callee');
2817
};
2818
}
2819
2820
// Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
2821
// IE 11 (#1621), and in Safari 8 (#1929).
2822
if (typeof /./ != 'function' && typeof Int8Array != 'object') {
2823
_.isFunction = function(obj) {
2824
return typeof obj == 'function' || false;
2826
}
2827
2828
// Is a given object a finite number?
2829
_.isFinite = function(obj) {
2830
return isFinite(obj) && !isNaN(parseFloat(obj));
2833
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
2834
_.isNaN = function(obj) {
2835
return _.isNumber(obj) && obj !== +obj;
2836
};
2838
// Is a given value a boolean?
2839
_.isBoolean = function(obj) {
2840
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
2841
};
2843
// Is a given value equal to null?
2844
_.isNull = function(obj) {
2845
return obj === null;
2846
};
2848
// Is a given variable undefined?
2849
_.isUndefined = function(obj) {
2850
return obj === void 0;
2853
// Shortcut function for checking if an object has a given property directly
2854
// on itself (in other words, not on a prototype).
2855
_.has = function(obj, key) {
2856
return obj != null && hasOwnProperty.call(obj, key);
2859
// Utility Functions
2860
// -----------------
2861
2862
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
2863
// previous owner. Returns a reference to the Underscore object.
2864
_.noConflict = function() {
2865
root._ = previousUnderscore;
2866
return this;
2869
// Keep the identity function around for default iteratees.
2870
_.identity = function(value) {
2871
return value;
2874
// Predicate-generating functions. Often useful outside of Underscore.
2875
_.constant = function(value) {
2883
_.property = function(key) {
2884
return function(obj) {
2885
return obj == null ? void 0 : obj[key];
2886
};
2889
// Generates a function for a given object that returns a given property.
2890
_.propertyOf = function(obj) {
2891
return obj == null ? function(){} : function(key) {
2892
return obj[key];
2893
};
2896
// Returns a predicate for checking whether an object has a given set of
2897
// `key:value` pairs.
2898
_.matcher = _.matches = function(attrs) {
2899
attrs = _.extendOwn({}, attrs);
2900
return function(obj) {
2901
return _.isMatch(obj, attrs);
2902
};
2905
// Run a function **n** times.
2906
_.times = function(n, iteratee, context) {
2907
var accum = Array(Math.max(0, n));
2908
iteratee = optimizeCb(iteratee, context, 1);
2909
for (var i = 0; i < n; i++) accum[i] = iteratee(i);
2910
return accum;
2913
// Return a random integer between min and max (inclusive).
2914
_.random = function(min, max) {
2915
if (max == null) {
2916
max = min;
2917
min = 0;
2922
// A (possibly faster) way to get the current timestamp as an integer.
2923
_.now = Date.now || function() {
2924
return new Date().getTime();
2927
// List of HTML entities for escaping.
2928
var escapeMap = {
2929
'&': '&',
2930
'<': '<',
2931
'>': '>',
2932
'"': '"',
2933
"'": ''',
2934
'`': '`'
2938
// Functions for escaping and unescaping strings to/from HTML interpolation.
2939
var createEscaper = function(map) {
2940
var escaper = function(match) {
2941
return map[match];
2942
};
2943
// Regexes for identifying a key that needs to be escaped
2944
var source = '(?:' + _.keys(map).join('|') + ')';
2945
var testRegexp = RegExp(source);
2946
var replaceRegexp = RegExp(source, 'g');
2947
return function(string) {
2948
string = string == null ? '' : '' + string;
2949
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
2950
};
2951
};
2952
_.escape = createEscaper(escapeMap);
2953
_.unescape = createEscaper(unescapeMap);
2954
2955
// If the value of the named `property` is a function then invoke it with the
2956
// `object` as context; otherwise, return it.
2957
_.result = function(object, property, fallback) {
2958
var value = object == null ? void 0 : object[property];
2959
if (value === void 0) {
2960
value = fallback;
2965
// Generate a unique integer id (unique within the entire client session).
2966
// Useful for temporary DOM ids.
2967
var idCounter = 0;
2968
_.uniqueId = function(prefix) {
2969
var id = ++idCounter + '';
2970
return prefix ? prefix + id : id;
2973
// By default, Underscore uses ERB-style template delimiters, change the
2974
// following template settings to use alternative delimiters.
2975
_.templateSettings = {
2976
evaluate : /<%([\s\S]+?)%>/g,
2977
interpolate : /<%=([\s\S]+?)%>/g,
2978
escape : /<%-([\s\S]+?)%>/g
2981
// When customizing `templateSettings`, if you don't want to define an
2982
// interpolation, evaluation or escaping regex, we need one that is
2983
// guaranteed not to match.
2984
var noMatch = /(.)^/;
2985
2986
// Certain characters need to be escaped so that they can be put into a
2987
// string literal.
2988
var escapes = {
2989
"'": "'",
2990
'\\': '\\',
2991
'\r': 'r',
2992
'\n': 'n',
2993
'\u2028': 'u2028',
2994
'\u2029': 'u2029'
2997
var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
2998
2999
var escapeChar = function(match) {
3000
return '\\' + escapes[match];
3001
};
3002
3003
// JavaScript micro-templating, similar to John Resig's implementation.
3004
// Underscore templating handles arbitrary delimiters, preserves whitespace,
3005
// and correctly escapes quotes within interpolated code.
3006
// NB: `oldSettings` only exists for backwards compatibility.
3007
_.template = function(text, settings, oldSettings) {
3008
if (!settings && oldSettings) settings = oldSettings;
3009
settings = _.defaults({}, settings, _.templateSettings);
3010
3011
// Combine delimiters into one regular expression via alternation.
3012
var matcher = RegExp([
3013
(settings.escape || noMatch).source,
3014
(settings.interpolate || noMatch).source,
3015
(settings.evaluate || noMatch).source
3016
].join('|') + '|$', 'g');
3017
3018
// Compile the template source, escaping string literals appropriately.
3019
var index = 0;
3020
var source = "__p+='";
3021
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
3022
source += text.slice(index, offset).replace(escaper, escapeChar);
3023
index = offset + match.length;
3024
3025
if (escape) {
3026
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
3027
} else if (interpolate) {
3028
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
3029
} else if (evaluate) {
3030
source += "';\n" + evaluate + "\n__p+='";
3032
3033
// Adobe VMs need the match returned to produce the correct offest.
3034
return match;
3035
});
3036
source += "';\n";
3037
3038
// If a variable is not specified, place data values in local scope.
3039
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
3040
3041
source = "var __t,__p='',__j=Array.prototype.join," +
3042
"print=function(){__p+=__j.call(arguments,'');};\n" +
3043
source + 'return __p;\n';
3044
3045
try {
3046
var render = new Function(settings.variable || 'obj', '_', source);
3047
} catch (e) {
3048
e.source = source;
3049
throw e;
3051
3052
var template = function(data) {
3053
return render.call(this, data, _);
3054
};
3055
3056
// Provide the compiled source as a convenience for precompilation.
3057
var argument = settings.variable || 'obj';
3058
template.source = 'function(' + argument + '){\n' + source + '}';
3059
3060
return template;
3063
// Add a "chain" function. Start chaining a wrapped Underscore object.
3064
_.chain = function(obj) {
3065
var instance = _(obj);
3066
instance._chain = true;
3067
return instance;
3070
// OOP
3071
// ---------------
3072
// If Underscore is called as a function, it returns a wrapped object that
3073
// can be used OO-style. This wrapper holds altered versions of all the
3074
// underscore functions. Wrapped objects may be chained.
3075
3076
// Helper function to continue chaining intermediate results.
3077
var result = function(instance, obj) {
3078
return instance._chain ? _(obj).chain() : obj;
3079
};
3080
3081
// Add your own custom functions to the Underscore object.
3082
_.mixin = function(obj) {
3083
_.each(_.functions(obj), function(name) {
3084
var func = _[name] = obj[name];
3085
_.prototype[name] = function() {
3086
var args = [this._wrapped];
3087
push.apply(args, arguments);
3088
return result(this, func.apply(_, args));
3089
};
3090
});
3093
// Add all of the Underscore functions to the wrapper object.
3094
_.mixin(_);
3095
3096
// Add all mutator Array functions to the wrapper.
3097
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
3098
var method = ArrayProto[name];
3099
_.prototype[name] = function() {
3100
var obj = this._wrapped;
3101
method.apply(obj, arguments);
3102
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
3103
return result(this, obj);
3104
};
3105
});
3106
3107
// Add all accessor Array functions to the wrapper.
3108
_.each(['concat', 'join', 'slice'], function(name) {
3109
var method = ArrayProto[name];
3110
_.prototype[name] = function() {
3111
return result(this, method.apply(this._wrapped, arguments));
3112
};
3113
});
3114
3115
// Extracts the result from a wrapped and chained object.
3116
_.prototype.value = function() {
3117
return this._wrapped;
3120
// Provide unwrapping proxy for some methods used in engine operations
3121
// such as arithmetic and JSON stringification.
3122
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
3123
3124
_.prototype.toString = function() {
3125
return '' + this._wrapped;
3128
// AMD registration happens at the end for compatibility with AMD loaders
3129
// that may not enforce next-turn semantics on modules. Even though general
3130
// practice for AMD registration is to be anonymous, underscore registers
3131
// as a named module because, like jQuery, it is a base library that is
3132
// popular enough to be bundled in a third party lib, but not be part of
3133
// an AMD load request. Those cases could generate an error when an
3134
// anonymous define() is called outside of a loader request.
3135
if (typeof define === 'function' && define.amd) {
3136
define('underscore', [], function() {
3137
return _;
3138
});
3139
}
3140
}.call(this));
3143
var WildEmitter = require('wildemitter');
3144
var util = require('util');
3145
3146
function Sender(opts) {
3147
WildEmitter.call(this);
3148
var options = opts || {};
3149
this.config = {
3150
chunksize: 16384,
3151
pacing: 0
3153
// set our config from options
3154
var item;
3155
for (item in options) {
3156
this.config[item] = options[item];
3157
}
3159
this.file = null;
3160
this.channel = null;
3161
}
3162
util.inherits(Sender, WildEmitter);
3163
3164
Sender.prototype.send = function (file, channel) {
3165
var self = this;
3166
this.file = file;
3167
this.channel = channel;
3168
var sliceFile = function(offset) {
3169
var reader = new window.FileReader();
3170
reader.onload = (function() {
3171
return function(e) {
3172
self.channel.send(e.target.result);
3173
self.emit('progress', offset, file.size, e.target.result);
3174
if (file.size > offset + e.target.result.byteLength) {
3175
window.setTimeout(sliceFile, self.config.pacing, offset + self.config.chunksize);
3176
} else {
3177
self.emit('progress', file.size, file.size, null);
3178
self.emit('sentFile');
3179
}
3180
};
3181
})(file);
3182
var slice = file.slice(offset, offset + self.config.chunksize);
3183
reader.readAsArrayBuffer(slice);
3188
function Receiver() {
3189
WildEmitter.call(this);
3190
3191
this.receiveBuffer = [];
3192
this.received = 0;
3193
this.metadata = {};
3194
this.channel = null;
3195
}
3196
util.inherits(Receiver, WildEmitter);
3197
3198
Receiver.prototype.receive = function (metadata, channel) {
3199
var self = this;
3200
3201
if (metadata) {
3202
this.metadata = metadata;
3203
}
3204
this.channel = channel;
3205
// chrome only supports arraybuffers and those make it easier to calc the hash
3206
channel.binaryType = 'arraybuffer';
3207
this.channel.onmessage = function (event) {
3208
var len = event.data.byteLength;
3209
self.received += len;
3210
self.receiveBuffer.push(event.data);
3211
3212
self.emit('progress', self.received, self.metadata.size, event.data);
3213
if (self.received === self.metadata.size) {
3214
self.emit('receivedFile', new window.Blob(self.receiveBuffer), self.metadata);
3215
self.receiveBuffer = []; // discard receivebuffer
3216
} else if (self.received > self.metadata.size) {
3217
// FIXME
3218
console.error('received more than expected, discarding...');
3219
self.receiveBuffer = []; // just discard...
3220
3221
}
3225
module.exports = {};
3226
module.exports.support = window && window.File && window.FileReader && window.Blob;
3227
module.exports.Sender = Sender;
3228
module.exports.Receiver = Receiver;
3231
var _ = require('underscore');
3232
var util = require('util');
3233
var webrtc = require('webrtcsupport');
3234
var SJJ = require('sdp-jingle-json');
3235
var WildEmitter = require('wildemitter');
3236
var peerconn = require('traceablepeerconnection');
3238
function PeerConnection(config, constraints) {
3239
var self = this;
3240
var item;
3241
WildEmitter.call(this);
3246
// make sure this only gets enabled in Google Chrome
3247
// EXPERIMENTAL FLAG, might get removed without notice
3248
this.enableChromeNativeSimulcast = false;
3249
if (constraints && constraints.optional &&
3250
webrtc.prefix === 'webkit' &&
3251
navigator.appVersion.match(/Chromium\//) === null) {
3252
constraints.optional.forEach(function (constraint, idx) {
3253
if (constraint.enableChromeNativeSimulcast) {
3254
self.enableChromeNativeSimulcast = true;
3255
}
3256
});
3257
}
3259
// EXPERIMENTAL FLAG, might get removed without notice
3260
this.enableMultiStreamHacks = false;
3261
if (constraints && constraints.optional) {
3262
constraints.optional.forEach(function (constraint, idx) {
3263
if (constraint.enableMultiStreamHacks) {
3264
self.enableMultiStreamHacks = true;
3265
}
3266
});
3267
}
3268
// EXPERIMENTAL FLAG, might get removed without notice
3269
this.restrictBandwidth = 0;
3270
if (constraints && constraints.optional) {
3271
constraints.optional.forEach(function (constraint, idx) {
3272
if (constraint.andyetRestrictBandwidth) {
3273
self.restrictBandwidth = constraint.andyetRestrictBandwidth;
3274
}
3275
});
3276
}
3277
3278
// EXPERIMENTAL FLAG, might get removed without notice
3279
// bundle up ice candidates, only works for jingle mode
3280
// number > 0 is the delay to wait for additional candidates
3281
// ~20ms seems good
3282
this.batchIceCandidates = 0;
3283
if (constraints && constraints.optional) {
3284
constraints.optional.forEach(function (constraint, idx) {
3285
if (constraint.andyetBatchIce) {
3286
self.batchIceCandidates = constraint.andyetBatchIce;
3287
}
3288
});
3289
}
3290
this.batchedIceCandidates = [];
3291
3292
// EXPERIMENTAL FLAG, might get removed without notice
3293
this.assumeSetLocalSuccess = false;
3294
if (constraints && constraints.optional) {
3295
constraints.optional.forEach(function (constraint, idx) {
3296
if (constraint.andyetAssumeSetLocalSuccess) {
3297
self.assumeSetLocalSuccess = constraint.andyetAssumeSetLocalSuccess;
3298
}
3299
});
3300
}
3301
3305
this.getLocalStreams = this.pc.getLocalStreams.bind(this.pc);
3306
this.getRemoteStreams = this.pc.getRemoteStreams.bind(this.pc);
3307
this.addStream = this.pc.addStream.bind(this.pc);
3308
this.removeStream = this.pc.removeStream.bind(this.pc);
3310
// proxy events
3311
this.pc.on('*', function () {
3312
self.emit.apply(self, arguments);
3313
});
3315
// proxy some events directly
3316
this.pc.onremovestream = this.emit.bind(this, 'removeStream');
3318
this.pc.onnegotiationneeded = this.emit.bind(this, 'negotiationNeeded');
3319
this.pc.oniceconnectionstatechange = this.emit.bind(this, 'iceConnectionStateChange');
3320
this.pc.onsignalingstatechange = this.emit.bind(this, 'signalingStateChange');
3321
3323
this.pc.onicecandidate = this._onIce.bind(this);
3324
this.pc.ondatachannel = this._onDataChannel.bind(this);
3325
3326
this.localDescription = {
3327
contents: []
3328
};
3329
this.remoteDescription = {
3330
contents: []
3333
this.config = {
3334
debug: false,
3335
ice: {},
3336
sid: '',
3337
isInitiator: true,
3338
sdpSessionID: Date.now(),
3339
useJingle: false
3342
// apply our config
3343
for (item in config) {
3344
this.config[item] = config[item];
3347
if (this.config.debug) {
3348
this.on('*', function (eventName, event) {
3349
var logger = config.logger || console;
3350
logger.log('PeerConnection event:', arguments);
3351
});
3353
this.hadLocalStunCandidate = false;
3354
this.hadRemoteStunCandidate = false;
3355
this.hadLocalRelayCandidate = false;
3356
this.hadRemoteRelayCandidate = false;
3357
3358
this.hadLocalIPv6Candidate = false;
3359
this.hadRemoteIPv6Candidate = false;
3360
3361
// keeping references for all our data channels
3362
// so they dont get garbage collected
3363
// can be removed once the following bugs have been fixed
3364
// https://crbug.com/405545
3365
// https://bugzilla.mozilla.org/show_bug.cgi?id=964092
3366
// to be filed for opera
3367
this._remoteDataChannels = [];
3368
this._localDataChannels = [];
3369
}
3370
3371
util.inherits(PeerConnection, WildEmitter);
3372
3373
Object.defineProperty(PeerConnection.prototype, 'signalingState', {
3374
get: function () {
3375
return this.pc.signalingState;
3376
}
3377
});
3378
Object.defineProperty(PeerConnection.prototype, 'iceConnectionState', {
3379
get: function () {
3380
return this.pc.iceConnectionState;
3381
}
3382
});
3383
3384
PeerConnection.prototype._role = function () {
3385
return this.isInitiator ? 'initiator' : 'responder';
3386
};
3387
3388
// Add a stream to the peer connection object
3389
PeerConnection.prototype.addStream = function (stream) {
3390
this.localStream = stream;
3391
this.pc.addStream(stream);
3392
};
3393
3394
// helper function to check if a remote candidate is a stun/relay
3395
// candidate or an ipv6 candidate
3396
PeerConnection.prototype._checkLocalCandidate = function (candidate) {
3397
var cand = SJJ.toCandidateJSON(candidate);
3398
if (cand.type == 'srflx') {
3399
this.hadLocalStunCandidate = true;
3400
} else if (cand.type == 'relay') {
3401
this.hadLocalRelayCandidate = true;
3402
}
3403
if (cand.ip.indexOf(':') != -1) {
3404
this.hadLocalIPv6Candidate = true;
3405
}
3406
};
3407
3408
// helper function to check if a remote candidate is a stun/relay
3409
// candidate or an ipv6 candidate
3410
PeerConnection.prototype._checkRemoteCandidate = function (candidate) {
3411
var cand = SJJ.toCandidateJSON(candidate);
3412
if (cand.type == 'srflx') {
3413
this.hadRemoteStunCandidate = true;
3414
} else if (cand.type == 'relay') {
3415
this.hadRemoteRelayCandidate = true;
3416
}
3417
if (cand.ip.indexOf(':') != -1) {
3418
this.hadRemoteIPv6Candidate = true;
3419
}
3420
};
3421
3422
3423
// Init and add ice candidate object with correct constructor
3424
PeerConnection.prototype.processIce = function (update, cb) {
3425
cb = cb || function () {};
3426
var self = this;
3427
3428
// ignore any added ice candidates to avoid errors. why does the
3429
// spec not do this?
3430
if (this.pc.signalingState === 'closed') return cb();
3431
3432
if (update.contents || (update.jingle && update.jingle.contents)) {
3435
3436
contents.forEach(function (content) {
3437
var transport = content.transport || {};
3438
var candidates = transport.candidates || [];
3439
var mline = contentNames.indexOf(content.name);
3440
var mid = content.name;
3441
3442
candidates.forEach(
3443
function (candidate) {
3444
var iceCandidate = SJJ.toCandidateSDP(candidate) + '\r\n';
3445
self.pc.addIceCandidate(
3446
new webrtc.IceCandidate({
3447
candidate: iceCandidate,
3448
sdpMLineIndex: mline,
3449
sdpMid: mid
3450
}), function () {
3451
// well, this success callback is pretty meaningless
3452
},
3453
function (err) {
3454
self.emit('error', err);
3455
}
3456
);
3457
self._checkRemoteCandidate(iceCandidate);
3458
});
3459
});
3460
} else {
3461
// working around https://code.google.com/p/webrtc/issues/detail?id=3669
3463
update.candidate.candidate = 'a=' + update.candidate.candidate;
3464
}
3465
3466
self.pc.addIceCandidate(
3467
new webrtc.IceCandidate(update.candidate),
3468
function () { },
3469
function (err) {
3470
self.emit('error', err);
3471
}
3472
);
3473
self._checkRemoteCandidate(update.candidate.candidate);
3474
}
3475
cb();
3476
};
3477
3478
// Generate and emit an offer with the given constraints
3479
PeerConnection.prototype.offer = function (constraints, cb) {
3480
var self = this;
3481
var hasConstraints = arguments.length === 2;
3482
var mediaConstraints = hasConstraints ? constraints : {
3483
mandatory: {
3484
OfferToReceiveAudio: true,
3485
OfferToReceiveVideo: true
3486
}
3487
};
3488
cb = hasConstraints ? cb : constraints;
3489
cb = cb || function () {};
3496
// does not work for jingle, but jingle.js doesn't need
3497
// this hack...
3498
if (self.assumeSetLocalSuccess) {
3499
self.emit('offer', offer);
3500
cb(null, offer);
3501
}
3502
self.pc.setLocalDescription(offer,
3503
function () {
3504
var jingle;
3505
var expandedOffer = {
3506
type: 'offer',
3507
sdp: offer.sdp
3508
};
3509
if (self.config.useJingle) {
3510
jingle = SJJ.toSessionJSON(offer.sdp, {
3512
direction: 'outgoing'
3513
});
3514
jingle.sid = self.config.sid;
3515
self.localDescription = jingle;
3517
// Save ICE credentials
3518
_.each(jingle.contents, function (content) {
3519
var transport = content.transport || {};
3520
if (transport.ufrag) {
3521
self.config.ice[content.name] = {
3522
ufrag: transport.ufrag,
3523
pwd: transport.pwd
3524
};
3525
}
3526
});
3528
expandedOffer.jingle = jingle;
3529
}
3530
expandedOffer.sdp.split('\r\n').forEach(function (line) {
3531
if (line.indexOf('a=candidate:') === 0) {
3532
self._checkLocalCandidate(line);
3533
}
3534
});
3536
if (!self.assumeSetLocalSuccess) {
3537
self.emit('offer', expandedOffer);
3538
cb(null, expandedOffer);
3539
}
3540
},
3541
function (err) {
3542
self.emit('error', err);
3543
cb(err);
3544
}
3545
);
3546
},
3547
function (err) {
3548
self.emit('error', err);
3549
cb(err);
3550
},
3551
mediaConstraints
3552
);
3553
};
3556
// Process an incoming offer so that ICE may proceed before deciding
3557
// to answer the request.
3558
PeerConnection.prototype.handleOffer = function (offer, cb) {
3559
cb = cb || function () {};
3560
var self = this;
3561
offer.type = 'offer';
3562
if (offer.jingle) {
3563
if (this.enableChromeNativeSimulcast) {
3564
offer.jingle.contents.forEach(function (content) {
3565
if (content.name === 'video') {
3566
content.description.googConferenceFlag = true;
3567
}
3568
});
3569
}
3570
/*
3571
if (this.enableMultiStreamHacks) {
3572
// add a mixed video stream as first stream
3573
offer.jingle.contents.forEach(function (content) {
3574
if (content.name === 'video') {
3575
var sources = content.description.sources || [];
3576
if (sources.length === 0 || sources[0].ssrc !== "3735928559") {
3577
sources.unshift({
3578
ssrc: "3735928559", // 0xdeadbeef
3579
parameters: [
3580
{
3581
key: "cname",
3582
value: "deadbeef"
3583
},
3584
{
3585
key: "msid",
3586
value: "mixyourfecintothis please"
3587
}
3588
]
3589
});
3590
content.description.sources = sources;
3591
}
3592
}
3593
});
3594
}
3595
*/
3596
if (self.restrictBandwidth > 0) {
3597
offer.jingle = SJJ.toSessionJSON(offer.sdp, {
3598
role: self._role(),
3599
direction: 'incoming'
3600
});
3601
if (offer.jingle.contents.length >= 2 && offer.jingle.contents[1].name === 'video') {
3602
var content = offer.jingle.contents[1];
3603
var hasBw = content.description && content.description.bandwidth;
3604
if (!hasBw) {
3605
offer.jingle.contents[1].description.bandwidth = { type:'AS', bandwidth: self.restrictBandwidth.toString() };
3606
offer.sdp = SJJ.toSessionSDP(offer.jingle, {
3607
sid: self.config.sdpSessionID,
3608
role: self._role(),
3609
direction: 'outgoing'
3610
});
3611
}
3612
}
3613
}
3617
direction: 'incoming'
3618
});
3619
self.remoteDescription = offer.jingle;
3620
}
3621
offer.sdp.split('\r\n').forEach(function (line) {
3622
if (line.indexOf('a=candidate:') === 0) {
3623
self._checkRemoteCandidate(line);
3624
}
3626
self.pc.setRemoteDescription(new webrtc.SessionDescription(offer),
3627
function () {
3628
cb();
3629
},
3630
cb
3631
);
3632
};
3634
// Answer an offer with audio only
3635
PeerConnection.prototype.answerAudioOnly = function (cb) {
3636
var mediaConstraints = {
3637
mandatory: {
3638
OfferToReceiveAudio: true,
3639
OfferToReceiveVideo: false
3640
}
3641
};
3642
this._answer(mediaConstraints, cb);
3643
};
3645
// Answer an offer without offering to recieve
3646
PeerConnection.prototype.answerBroadcastOnly = function (cb) {
3647
var mediaConstraints = {
3648
mandatory: {
3649
OfferToReceiveAudio: false,
3650
OfferToReceiveVideo: false
3651
}
3652
};
3653
this._answer(mediaConstraints, cb);
3654
};
3656
// Answer an offer with given constraints default is audio/video
3657
PeerConnection.prototype.answer = function (constraints, cb) {
3658
var self = this;
3659
var hasConstraints = arguments.length === 2;
3660
var callback = hasConstraints ? cb : constraints;
3661
var mediaConstraints = hasConstraints ? constraints : {
3662
mandatory: {
3663
OfferToReceiveAudio: true,
3664
OfferToReceiveVideo: true
3665
}
3666
};
3671
// Process an answer
3672
PeerConnection.prototype.handleAnswer = function (answer, cb) {
3673
cb = cb || function () {};
3674
var self = this;
3675
if (answer.jingle) {
3676
answer.sdp = SJJ.toSessionSDP(answer.jingle, {
3677
sid: self.config.sdpSessionID,
3679
direction: 'incoming'
3680
});
3681
self.remoteDescription = answer.jingle;
3682
}
3683
answer.sdp.split('\r\n').forEach(function (line) {
3684
if (line.indexOf('a=candidate:') === 0) {
3685
self._checkRemoteCandidate(line);
3686
}
3688
self.pc.setRemoteDescription(
3689
new webrtc.SessionDescription(answer),
3690
function () {
3691
cb(null);
3692
},
3693
cb
3694
);
3695
};
3697
// Close the peer connection
3698
PeerConnection.prototype.close = function () {
3699
this.pc.close();
3704
this.emit('close');
3705
};
3706
3707
// Internal code sharing for various types of answer methods
3708
PeerConnection.prototype._answer = function (constraints, cb) {
3709
cb = cb || function () {};
3710
var self = this;
3711
if (!this.pc.remoteDescription) {
3712
// the old API is used, call handleOffer
3713
throw new Error('remoteDescription not set');
3718
self.pc.createAnswer(
3719
function (answer) {
3720
var sim = [];
3721
var rtx = [];
3722
if (self.enableChromeNativeSimulcast) {
3723
// native simulcast part 1: add another SSRC
3724
answer.jingle = SJJ.toSessionJSON(answer.sdp, {
3727
});
3728
if (answer.jingle.contents.length >= 2 && answer.jingle.contents[1].name === 'video') {
3729
var hasSimgroup = false;
3730
var groups = answer.jingle.contents[1].description.sourceGroups || [];
3731
var hasSim = false;
3732
groups.forEach(function (group) {
3733
if (group.semantics == 'SIM') hasSim = true;
3734
});
3735
if (!hasSim &&
3736
answer.jingle.contents[1].description.sources.length) {
3737
var newssrc = JSON.parse(JSON.stringify(answer.jingle.contents[1].description.sources[0]));
3738
newssrc.ssrc = '' + Math.floor(Math.random() * 0xffffffff); // FIXME: look for conflicts
3739
answer.jingle.contents[1].description.sources.push(newssrc);
3740
3741
sim.push(answer.jingle.contents[1].description.sources[0].ssrc);
3742
sim.push(newssrc.ssrc);
3743
groups.push({
3744
semantics: 'SIM',
3745
sources: sim
3746
});
3748
// also create an RTX one for the SIM one
3749
var rtxssrc = JSON.parse(JSON.stringify(newssrc));
3750
rtxssrc.ssrc = '' + Math.floor(Math.random() * 0xffffffff); // FIXME: look for conflicts
3751
answer.jingle.contents[1].description.sources.push(rtxssrc);
3752
groups.push({
3753
semantics: 'FID',
3754
sources: [newssrc.ssrc, rtxssrc.ssrc]
3755
});
3757
answer.jingle.contents[1].description.sourceGroups = groups;
3758
answer.sdp = SJJ.toSessionSDP(answer.jingle, {
3759
sid: self.config.sdpSessionID,
3766
if (self.assumeSetLocalSuccess) {
3767
// not safe to do when doing simulcast mangling
3768
self.emit('answer', answer);
3769
cb(null, answer);
3770
}
3771
self.pc.setLocalDescription(answer,
3772
function () {
3773
var expandedAnswer = {
3774
type: 'answer',
3775
sdp: answer.sdp
3776
};
3777
if (self.config.useJingle) {
3778
var jingle = SJJ.toSessionJSON(answer.sdp, {
3780
direction: 'outgoing'
3781
});
3782
jingle.sid = self.config.sid;
3783
self.localDescription = jingle;
3784
expandedAnswer.jingle = jingle;
3785
}
3786
if (self.enableChromeNativeSimulcast) {
3787
// native simulcast part 2:
3788
// signal multiple tracks to the receiver
3789
// for anything in the SIM group
3790
if (!expandedAnswer.jingle) {
3791
expandedAnswer.jingle = SJJ.toSessionJSON(answer.sdp, {
3793
direction: 'outgoing'
3794
});
3795
}
3796
var groups = expandedAnswer.jingle.contents[1].description.sourceGroups || [];
3797
expandedAnswer.jingle.contents[1].description.sources.forEach(function (source, idx) {
3798
// the floor idx/2 is a hack that relies on a particular order
3799
// of groups, alternating between sim and rtx
3800
source.parameters = source.parameters.map(function (parameter) {
3801
if (parameter.key === 'msid') {
3802
parameter.value += '-' + Math.floor(idx / 2);
3803
}
3804
return parameter;
3805
});
3806
});
3807
expandedAnswer.sdp = SJJ.toSessionSDP(expandedAnswer.jingle, {
3808
sid: self.sdpSessionID,
3810
direction: 'outgoing'
3811
});
3812
}
3813
expandedAnswer.sdp.split('\r\n').forEach(function (line) {
3814
if (line.indexOf('a=candidate:') === 0) {
3815
self._checkLocalCandidate(line);
3816
}
3817
});
3818
if (!self.assumeSetLocalSuccess) {
3819
self.emit('answer', expandedAnswer);
3820
cb(null, expandedAnswer);
3821
}
3822
},
3823
function (err) {
3824
self.emit('error', err);
3825
cb(err);
3826
}
3827
);
3828
},
3829
function (err) {
3830
self.emit('error', err);
3831
cb(err);
3832
},
3833
constraints
3834
);
3835
};
3837
// Internal method for emitting ice candidates on our peer object
3838
PeerConnection.prototype._onIce = function (event) {
3839
var self = this;
3840
if (event.candidate) {
3841
var ice = event.candidate;
3848
var cand = SJJ.toCandidateJSON(ice.candidate);
3849
if (self.config.useJingle) {
3850
if (!ice.sdpMid) { // firefox doesn't set this
3851
ice.sdpMid = self.localDescription.contents[ice.sdpMLineIndex].name;
3852
}
3853
if (!self.config.ice[ice.sdpMid]) {
3854
var jingle = SJJ.toSessionJSON(self.pc.localDescription.sdp, {
3857
});
3858
_.each(jingle.contents, function (content) {
3859
var transport = content.transport || {};
3860
if (transport.ufrag) {
3861
self.config.ice[content.name] = {
3862
ufrag: transport.ufrag,
3863
pwd: transport.pwd
3864
};
3865
}
3866
});
3867
}
3868
expandedCandidate.jingle = {
3869
contents: [{
3870
name: ice.sdpMid,
3872
transport: {
3873
transType: 'iceUdp',
3874
ufrag: self.config.ice[ice.sdpMid].ufrag,
3875
pwd: self.config.ice[ice.sdpMid].pwd,
3876
candidates: [
3877
cand
3878
]
3879
}
3880
}]
3881
};
3882
if (self.batchIceCandidates > 0) {
3883
if (self.batchedIceCandidates.length === 0) {
3884
window.setTimeout(function () {
3885
var contents = {};
3886
self.batchedIceCandidates.forEach(function (content) {
3887
content = content.contents[0];
3888
if (!contents[content.name]) contents[content.name] = content;
3889
contents[content.name].transport.candidates.push(content.transport.candidates[0]);
3890
});
3891
var newCand = {
3892
jingle: {
3893
contents: []
3894
}
3895
};
3896
Object.keys(contents).forEach(function (name) {
3897
newCand.jingle.contents.push(contents[name]);
3898
});
3899
self.batchedIceCandidates = [];
3900
self.emit('ice', newCand);
3901
}, self.batchIceCandidates);
3902
}
3903
self.batchedIceCandidates.push(expandedCandidate.jingle);
3904
return;
3905
}
3906
3907
}
3908
this.emit('ice', expandedCandidate);
3909
} else {
3910
this.emit('endOfCandidates');
3911
}
3912
};
3914
// Internal method for processing a new data channel being added by the
3915
// other peer.
3916
PeerConnection.prototype._onDataChannel = function (event) {
3917
// make sure we keep a reference so this doesn't get garbage collected
3918
var channel = event.channel;
3919
this._remoteDataChannels.push(channel);
3924
// Create a data channel spec reference:
3925
// http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCDataChannelInit
3926
PeerConnection.prototype.createDataChannel = function (name, opts) {
3927
var channel = this.pc.createDataChannel(name, opts);
3929
// make sure we keep a reference so this doesn't get garbage collected
3930
this._localDataChannels.push(channel);
3935
// a wrapper around getStats which hides the differences (where possible)
3936
PeerConnection.prototype.getStats = function (cb) {
3937
if (webrtc.prefix === 'moz') {
3938
this.pc.getStats(
3939
function (res) {
3940
var items = [];
3941
for (var result in res) {
3942
if (typeof res[result] === 'object') {
3943
items.push(res[result]);
3944
}
3945
}
3946
cb(null, items);
3947
},
3948
cb
3949
);
3950
} else {
3951
this.pc.getStats(function (res) {
3952
var items = [];
3953
res.result().forEach(function (result) {
3954
var item = {};
3955
result.names().forEach(function (name) {
3956
item[name] = result.stat(name);
3957
});
3958
item.id = result.id;
3959
item.type = result.type;
3960
item.timestamp = result.timestamp;
3961
items.push(item);
3962
});
3963
cb(null, items);
3964
});
3970
},{"sdp-jingle-json":17,"traceablepeerconnection":18,"underscore":16,"util":2,"webrtcsupport":5,"wildemitter":4}],17:[function(require,module,exports){
3971
var toSDP = require('./lib/tosdp');
3972
var toJSON = require('./lib/tojson');
3973
3974
3975
// Converstion from JSON to SDP
3976
3977
exports.toIncomingSDPOffer = function (session) {
3978
return toSDP.toSessionSDP(session, {
3979
role: 'responder',
3980
direction: 'incoming'
3981
});
3982
};
3983
exports.toOutgoingSDPOffer = function (session) {
3984
return toSDP.toSessionSDP(session, {
3985
role: 'initiator',
3986
direction: 'outgoing'
3987
});
3988
};
3989
exports.toIncomingSDPAnswer = function (session) {
3990
return toSDP.toSessionSDP(session, {
3991
role: 'initiator',
3992
direction: 'incoming'
3993
});
3994
};
3995
exports.toOutgoingSDPAnswer = function (session) {
3996
return toSDP.toSessionSDP(session, {
3997
role: 'responder',
3998
direction: 'outgoing'
3999
});
4000
};
4001
exports.toIncomingMediaSDPOffer = function (media) {
4002
return toSDP.toMediaSDP(media, {
4003
role: 'responder',
4004
direction: 'incoming'
4005
});
4006
};
4007
exports.toOutgoingMediaSDPOffer = function (media) {
4008
return toSDP.toMediaSDP(media, {
4009
role: 'initiator',
4010
direction: 'outgoing'
4011
});
4012
};
4013
exports.toIncomingMediaSDPAnswer = function (media) {
4014
return toSDP.toMediaSDP(media, {
4015
role: 'initiator',
4016
direction: 'incoming'
4017
});
4018
};
4019
exports.toOutgoingMediaSDPAnswer = function (media) {
4020
return toSDP.toMediaSDP(media, {
4021
role: 'responder',
4022
direction: 'outgoing'
4023
});
4024
};
4025
exports.toCandidateSDP = toSDP.toCandidateSDP;
4026
exports.toMediaSDP = toSDP.toMediaSDP;
4027
exports.toSessionSDP = toSDP.toSessionSDP;
4032
exports.toIncomingJSONOffer = function (sdp, creators) {
4033
return toJSON.toSessionJSON(sdp, {
4034
role: 'responder',
4035
direction: 'incoming',
4036
creators: creators
4037
});
4038
};
4039
exports.toOutgoingJSONOffer = function (sdp, creators) {
4040
return toJSON.toSessionJSON(sdp, {
4041
role: 'initiator',
4042
direction: 'outgoing',
4043
creators: creators
4044
});
4045
};
4046
exports.toIncomingJSONAnswer = function (sdp, creators) {
4047
return toJSON.toSessionJSON(sdp, {
4048
role: 'initiator',
4049
direction: 'incoming',
4050
creators: creators
4051
});
4052
};
4053
exports.toOutgoingJSONAnswer = function (sdp, creators) {
4054
return toJSON.toSessionJSON(sdp, {
4055
role: 'responder',
4056
direction: 'outgoing',
4057
creators: creators
4058
});
4059
};
4060
exports.toIncomingMediaJSONOffer = function (sdp, creator) {
4061
return toJSON.toMediaJSON(sdp, {
4062
role: 'responder',
4063
direction: 'incoming',
4064
creator: creator
4065
});
4066
};
4067
exports.toOutgoingMediaJSONOffer = function (sdp, creator) {
4068
return toJSON.toMediaJSON(sdp, {
4069
role: 'initiator',
4070
direction: 'outgoing',
4071
creator: creator
4072
});
4073
};
4074
exports.toIncomingMediaJSONAnswer = function (sdp, creator) {
4075
return toJSON.toMediaJSON(sdp, {
4076
role: 'initiator',
4077
direction: 'incoming',
4078
creator: creator
4079
});
4080
};
4081
exports.toOutgoingMediaJSONAnswer = function (sdp, creator) {
4082
return toJSON.toMediaJSON(sdp, {
4083
role: 'responder',
4084
direction: 'outgoing',
4085
creator: creator
4086
});
4087
};
4088
exports.toCandidateJSON = toJSON.toCandidateJSON;
4089
exports.toMediaJSON = toJSON.toMediaJSON;
4090
exports.toSessionJSON = toJSON.toSessionJSON;
4093
// getScreenMedia helper by @HenrikJoreteg
4094
var getUserMedia = require('getusermedia');
4099
module.exports = function (constraints, cb) {
4100
var hasConstraints = arguments.length === 2;
4101
var callback = hasConstraints ? cb : constraints;
4102
var error;
4104
if (typeof window === 'undefined' || window.location.protocol === 'http:') {
4105
error = new Error('NavigatorUserMediaError');
4106
error.name = 'HTTPS_REQUIRED';
4107
return callback(error);
4108
}
4111
var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
4112
var maxver = 33;
4114
// "known" crash in chrome 34 and 35 on linux
4115
if (window.navigator.userAgent.match('Linux')) maxver = 35;
4117
// chrome 26 - chrome 33 way to do it -- requires bad chrome://flags
4118
// note: this is basically in maintenance mode and will go away soon
4120
video: {
4121
mandatory: {
4122
googLeakyBucket: true,
4123
maxWidth: window.screen.width,
4124
maxHeight: window.screen.height,
4125
maxFrameRate: 3,
4126
chromeMediaSource: 'screen'
4127
}
4129
};
4130
getUserMedia(constraints, callback);
4131
} else {
4132
// chrome 34+ way requiring an extension
4133
var pending = window.setTimeout(function () {
4134
error = new Error('NavigatorUserMediaError');
4135
error.name = 'EXTENSION_UNAVAILABLE';
4136
return callback(error);
4137
}, 1000);
4138
cache[pending] = [callback, hasConstraints ? constraint : null];
4139
window.postMessage({ type: 'getScreen', id: pending }, '*');
4140
}
4141
} else if (window.navigator.userAgent.match('Firefox')) {
4142
var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10);
4143
if (ffver >= 33) {
4144
constraints = (hasConstraints && constraints) || {
4145
video: {
4146
mozMediaSource: 'window',
4147
mediaSource: 'window'
4149
}
4150
getUserMedia(constraints, function (err, stream) {
4151
callback(err, stream);
4152
// workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810
4153
if (!err) {
4154
var lastTime = stream.currentTime;
4155
var polly = window.setInterval(function () {
4156
if (!stream) window.clearInterval(polly);
4157
if (stream.currentTime == lastTime) {
4158
window.clearInterval(polly);
4159
if (stream.onended) {
4160
stream.onended();
4161
}
4162
}
4163
lastTime = stream.currentTime;
4164
}, 500);
4167
} else {
4168
error = new Error('NavigatorUserMediaError');
4169
error.name = 'EXTENSION_UNAVAILABLE'; // does not make much sense but...
4175
if (event.origin != window.location.origin) {
4176
return;
4177
}
4178
if (event.data.type == 'gotScreen' && cache[event.data.id]) {
4179
var data = cache[event.data.id];
4180
var constraints = data[1];
4181
var callback = data[0];
4182
delete cache[event.data.id];
4184
if (event.data.sourceId === '') { // user canceled
4185
var error = new Error('NavigatorUserMediaError');
4186
error.name = 'PERMISSION_DENIED';
4187
callback(error);
4188
} else {
4189
constraints = constraints || {audio: false, video: {
4190
mandatory: {
4191
chromeMediaSource: 'desktop',
4192
maxWidth: window.screen.width,
4193
maxHeight: window.screen.height,
4194
maxFrameRate: 3
4196
optional: [
4197
{googLeakyBucket: true},
4198
{googTemporalLayeredScreencast: true}
4199
]
4200
}};
4201
constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId;
4202
getUserMedia(constraints, callback);
4203
}
4204
} else if (event.data.type == 'getScreenPending') {
4205
window.clearTimeout(event.data.id);
4212
function getMaxVolume (analyser, fftBins) {
4213
var maxVolume = -Infinity;
4214
analyser.getFloatFrequencyData(fftBins);
4216
for(var i=4, ii=fftBins.length; i < ii; i++) {
4217
if (fftBins[i] > maxVolume && fftBins[i] < 0) {
4218
maxVolume = fftBins[i];
4219
}
4220
};
4227
// use a single audio context due to hardware limits
4228
var audioContext = null;
4229
module.exports = function(stream, options) {
4230
var harker = new WildEmitter();
4233
// make it not break in non-supported browsers
4234
if (!audioContextType) return harker;
4236
//Config
4237
var options = options || {},
4238
smoothing = (options.smoothing || 0.1),
4239
interval = (options.interval || 50),
4240
threshold = options.threshold,
4241
play = options.play,
4242
history = options.history || 10,
4243
running = true;
4245
//Setup Audio Context
4246
if (!audioContext) {
4247
audioContext = new audioContextType();
4248
}
4249
var sourceNode, fftBins, analyser;
4251
analyser = audioContext.createAnalyser();
4252
analyser.fftSize = 512;
4253
analyser.smoothingTimeConstant = smoothing;
4254
fftBins = new Float32Array(analyser.fftSize);
4256
if (stream.jquery) stream = stream[0];
4257
if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
4258
//Audio Tag
4259
sourceNode = audioContext.createMediaElementSource(stream);
4260
if (typeof play === 'undefined') play = true;
4261
threshold = threshold || -50;
4262
} else {
4263
//WebRTC Stream
4264
sourceNode = audioContext.createMediaStreamSource(stream);
4265
threshold = threshold || -50;
4266
}
4268
sourceNode.connect(analyser);
4269
if (play) analyser.connect(audioContext.destination);
4277
harker.setInterval = function(i) {
4278
interval = i;
4279
};
4280
4281
harker.stop = function() {
4282
running = false;
4283
harker.emit('volume_change', -100, threshold);
4284
if (harker.speaking) {
4285
harker.speaking = false;
4286
harker.emit('stopped_speaking');
4288
};
4289
harker.speakingHistory = [];
4290
for (var i = 0; i < history; i++) {
4291
harker.speakingHistory.push(0);
4292
}
4293
4294
// Poll the analyser node to determine if speaking
4295
// and emit events if changed
4296
var looper = function() {
4297
setTimeout(function() {
4298
4299
//check if stop has been called
4300
if(!running) {
4301
return;
4302
}
4303
4304
var currentVolume = getMaxVolume(analyser, fftBins);
4308
var history = 0;
4309
if (currentVolume > threshold && !harker.speaking) {
4310
// trigger quickly, short history
4311
for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
4312
history += harker.speakingHistory[i];
4313
}
4314
if (history >= 2) {
4315
harker.speaking = true;
4316
harker.emit('speaking');
4317
}
4318
} else if (currentVolume < threshold && harker.speaking) {
4319
for (var i = 0; i < harker.speakingHistory.length; i++) {
4320
history += harker.speakingHistory[i];
4321
}
4322
if (history == 0) {
4323
harker.speaking = false;
4324
harker.emit('stopped_speaking');
4325
}
4326
}
4327
harker.speakingHistory.shift();
4328
harker.speakingHistory.push(0 + (currentVolume > threshold));
4339
},{"wildemitter":4}],15:[function(require,module,exports){
4340
var support = require('webrtcsupport');
4341
4342
4343
function GainController(stream) {
4344
this.support = support.webAudio && support.mediaStream;
4345
4346
// set our starting value
4347
this.gain = 1;
4348
4349
if (this.support) {
4350
var context = this.context = new support.AudioContext();
4351
this.microphone = context.createMediaStreamSource(stream);
4352
this.gainFilter = context.createGain();
4353
this.destination = context.createMediaStreamDestination();
4354
this.outputStream = this.destination.stream;
4355
this.microphone.connect(this.gainFilter);
4356
this.gainFilter.connect(this.destination);
4357
stream.addTrack(this.outputStream.getAudioTracks()[0]);
4358
stream.removeTrack(stream.getAudioTracks()[0]);
4359
}
4360
this.stream = stream;
4361
}
4362
4363
// setting
4364
GainController.prototype.setGain = function (val) {
4365
// check for support
4366
if (!this.support) return;
4367
this.gainFilter.gain.value = val;
4368
this.gain = val;
4369
};
4370
4371
GainController.prototype.getGain = function () {
4372
return this.gain;
4373
};
4374
4375
GainController.prototype.off = function () {
4376
return this.setGain(0);
4377
};
4378
4379
GainController.prototype.on = function () {
4380
this.setGain(1);
4381
};
4382
4383
4384
module.exports = GainController;
4385
4386
},{"webrtcsupport":5}],20:[function(require,module,exports){
4390
exports.toSessionSDP = function (session, opts) {
4391
var role = opts.role || 'initiator';
4392
var direction = opts.direction || 'outgoing';
4393
var sid = opts.sid || session.sid || Date.now();
4394
var time = opts.time || Date.now();
4395
4399
's=-',
4400
't=0 0'
4401
];
4402
4403
var groups = session.groups || [];
4404
groups.forEach(function (group) {
4405
sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' '));
4406
});
4407
4408
var contents = session.contents || [];
4409
contents.forEach(function (content) {
4419
var role = opts.role || 'initiator';
4420
var direction = opts.direction || 'outgoing';
4421
4422
var desc = content.description;
4423
var transport = content.transport;
4424
var payloads = desc.payloads || [];
4425
var fingerprints = (transport && transport.fingerprints) || [];
4426
4427
var mline = [];
4428
if (desc.descType == 'datachannel') {
4429
mline.push('application');
4430
mline.push('1');
4431
mline.push('DTLS/SCTP');
4432
if (transport.sctp) {
4433
transport.sctp.forEach(function (map) {
4434
mline.push(map.number);
4435
});
4436
}
4437
} else {
4438
mline.push(desc.media);
4439
mline.push('1');
4440
if ((desc.encryption && desc.encryption.length > 0) || (fingerprints.length > 0)) {
4441
mline.push('RTP/SAVPF');
4442
} else {
4443
mline.push('RTP/AVPF');
4444
}
4445
payloads.forEach(function (payload) {
4446
mline.push(payload.id);
4447
});
4448
}
4449
4450
4451
sdp.push('m=' + mline.join(' '));
4452
4453
sdp.push('c=IN IP4 0.0.0.0');
4454
if (desc.bandwidth && desc.bandwidth.type && desc.bandwidth.bandwidth) {
4455
sdp.push('b=' + desc.bandwidth.type + ':' + desc.bandwidth.bandwidth);
4456
}
4457
if (desc.descType == 'rtp') {
4458
sdp.push('a=rtcp:1 IN IP4 0.0.0.0');
4459
}
4460
4461
if (transport) {
4462
if (transport.ufrag) {
4463
sdp.push('a=ice-ufrag:' + transport.ufrag);
4464
}
4465
if (transport.pwd) {
4466
sdp.push('a=ice-pwd:' + transport.pwd);
4467
}
4468
4469
var pushedSetup = false;
4470
fingerprints.forEach(function (fingerprint) {
4471
sdp.push('a=fingerprint:' + fingerprint.hash + ' ' + fingerprint.value);
4472
if (fingerprint.setup && !pushedSetup) {
4473
sdp.push('a=setup:' + fingerprint.setup);
4474
}
4475
});
4476
4477
if (transport.sctp) {
4478
transport.sctp.forEach(function (map) {
4479
sdp.push('a=sctpmap:' + map.number + ' ' + map.protocol + ' ' + map.streams);
4480
});
4481
}
4482
}
4483
4484
if (desc.descType == 'rtp') {
4486
}
4487
sdp.push('a=mid:' + content.name);
4488
4489
if (desc.mux) {
4490
sdp.push('a=rtcp-mux');
4491
}
4492
4493
var encryption = desc.encryption || [];
4494
encryption.forEach(function (crypto) {
4495
sdp.push('a=crypto:' + crypto.tag + ' ' + crypto.cipherSuite + ' ' + crypto.keyParams + (crypto.sessionParams ? ' ' + crypto.sessionParams : ''));
4496
});
4500
4501
payloads.forEach(function (payload) {
4502
var rtpmap = 'a=rtpmap:' + payload.id + ' ' + payload.name + '/' + payload.clockrate;
4503
if (payload.channels && payload.channels != '1') {
4504
rtpmap += '/' + payload.channels;
4505
}
4506
sdp.push(rtpmap);
4507
4508
if (payload.parameters && payload.parameters.length) {
4509
var fmtp = ['a=fmtp:' + payload.id];
4515
sdp.push(fmtp.join(' '));
4516
}
4517
4518
if (payload.feedback) {
4519
payload.feedback.forEach(function (fb) {
4520
if (fb.type === 'trr-int') {
4522
} else {
4523
sdp.push('a=rtcp-fb:' + payload.id + ' ' + fb.type + (fb.subtype ? ' ' + fb.subtype : ''));
4524
}
4525
});
4526
}
4527
});
4528
4529
if (desc.feedback) {
4530
desc.feedback.forEach(function (fb) {
4531
if (fb.type === 'trr-int') {
4533
} else {
4534
sdp.push('a=rtcp-fb:* ' + fb.type + (fb.subtype ? ' ' + fb.subtype : ''));
4535
}
4536
});
4537
}
4538
4539
var hdrExts = desc.headerExtensions || [];
4540
hdrExts.forEach(function (hdr) {
4541
sdp.push('a=extmap:' + hdr.id + (hdr.senders ? '/' + SENDERS[role][direction][hdr.senders] : '') + ' ' + hdr.uri);
4542
});
4543
4544
var ssrcGroups = desc.sourceGroups || [];
4545
ssrcGroups.forEach(function (ssrcGroup) {
4546
sdp.push('a=ssrc-group:' + ssrcGroup.semantics + ' ' + ssrcGroup.sources.join(' '));
4547
});
4548
4549
var ssrcs = desc.sources || [];
4550
ssrcs.forEach(function (ssrc) {
4551
for (var i = 0; i < ssrc.parameters.length; i++) {
4552
var param = ssrc.parameters[i];
4553
sdp.push('a=ssrc:' + (ssrc.ssrc || desc.ssrc) + ' ' + param.key + (param.value ? (':' + param.value) : ''));
4554
}
4555
});
4556
4557
var candidates = transport.candidates || [];
4558
candidates.forEach(function (candidate) {
4559
sdp.push(exports.toCandidateSDP(candidate));
4560
});
4561
4562
return sdp.join('\r\n');
4563
};
4564
4565
exports.toCandidateSDP = function (candidate) {
4566
var sdp = [];
4567
4568
sdp.push(candidate.foundation);
4569
sdp.push(candidate.component);
4570
sdp.push(candidate.protocol.toUpperCase());
4571
sdp.push(candidate.priority);
4572
sdp.push(candidate.ip);
4573
sdp.push(candidate.port);
4574
4575
var type = candidate.type;
4576
sdp.push('typ');
4577
sdp.push(type);
4578
if (type === 'srflx' || type === 'prflx' || type === 'relay') {
4579
if (candidate.relAddr && candidate.relPort) {
4580
sdp.push('raddr');
4581
sdp.push(candidate.relAddr);
4582
sdp.push('rport');
4583
sdp.push(candidate.relPort);
4584
}
4585
}
4586
if (candidate.tcpType && candidate.protocol.toUpperCase() == 'TCP') {
4587
sdp.push('tcptype');
4588
sdp.push(candidate.tcpType);
4591
sdp.push('generation');
4592
sdp.push(candidate.generation || '0');
4593
4594
// FIXME: apparently this is wrong per spec
4595
// but then, we need this when actually putting this into
4596
// SDP so it's going to stay.
4597
// decision needs to be revisited when browsers dont
4598
// accept this any longer
4599
return 'a=candidate:' + sdp.join(' ');
4600
};
4612
exports.toSessionJSON = function (sdp, opts) {
4613
var i;
4614
var creators = opts.creators || [];
4615
var role = opts.role || 'initiator';
4616
var direction = opts.direction || 'outgoing';
4617
4618
4619
// Divide the SDP into session and media sections.
4620
var media = sdp.split('\r\nm=');
4622
media[i] = 'm=' + media[i];
4623
if (i !== media.length - 1) {
4624
media[i] += '\r\n';
4627
var session = media.shift() + '\r\n';
4628
var sessionLines = parsers.lines(session);
4629
var parsed = {};
4632
for (i = 0; i < media.length; i++) {
4633
contents.push(exports.toMediaJSON(media[i], session, {
4634
role: role,
4635
direction: direction,
4636
creator: creators[i] || 'initiator'
4637
}));
4638
}
4641
var groupLines = parsers.findLines('a=group:', sessionLines);
4642
if (groupLines.length) {
4643
parsed.groups = parsers.groups(groupLines);
4649
exports.toMediaJSON = function (media, session, opts) {
4650
var creator = opts.creator || 'initiator';
4651
var role = opts.role || 'initiator';
4652
var direction = opts.direction || 'outgoing';
4653
4654
var lines = parsers.lines(media);
4655
var sessionLines = parsers.lines(session);
4656
var mline = parsers.mline(lines[0]);
4658
var content = {
4659
creator: creator,
4660
name: mline.media,
4661
description: {
4662
descType: 'rtp',
4663
media: mline.media,
4664
payloads: [],
4665
encryption: [],
4666
feedback: [],
4667
headerExtensions: []
4668
},
4669
transport: {
4670
transType: 'iceUdp',
4671
candidates: [],
4675
if (mline.media == 'application') {
4676
// FIXME: the description is most likely to be independent
4677
// of the SDP and should be processed by other parts of the library
4678
content.description = {
4679
descType: 'datachannel'
4680
};
4681
content.transport.sctp = [];
4682
}
4686
// If we have a mid, use that for the content name instead.
4687
var mid = parsers.findLine('a=mid:', lines);
4688
if (mid) {
4689
content.name = mid.substr(6);
4690
}
4692
if (parsers.findLine('a=sendrecv', lines, sessionLines)) {
4693
content.senders = 'both';
4694
} else if (parsers.findLine('a=sendonly', lines, sessionLines)) {
4698
} else if (parsers.findLine('a=inactive', lines, sessionLines)) {
4699
content.senders = 'none';
4703
var bandwidth = parsers.findLine('b=', lines);
4704
if (bandwidth) {
4705
desc.bandwidth = parsers.bandwidth(bandwidth);
4706
}
4707
4708
var ssrc = parsers.findLine('a=ssrc:', lines);
4709
if (ssrc) {
4710
desc.ssrc = ssrc.substr(7).split(' ')[0];
4711
}
4713
var rtpmapLines = parsers.findLines('a=rtpmap:', lines);
4714
rtpmapLines.forEach(function (line) {
4715
var payload = parsers.rtpmap(line);
4721
fmtpLines.forEach(function (line) {
4722
payload.parameters = parsers.fmtp(line);
4723
});
4725
var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines);
4726
fbLines.forEach(function (line) {
4727
payload.feedback.push(parsers.rtcpfb(line));
4728
});
4733
var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines);
4734
cryptoLines.forEach(function (line) {
4735
desc.encryption.push(parsers.crypto(line));
4736
});
4742
var fbLines = parsers.findLines('a=rtcp-fb:*', lines);
4743
fbLines.forEach(function (line) {
4744
desc.feedback.push(parsers.rtcpfb(line));
4745
});
4747
var extLines = parsers.findLines('a=extmap:', lines);
4748
extLines.forEach(function (line) {
4749
var ext = parsers.extmap(line);
4753
desc.headerExtensions.push(ext);
4754
});
4755
4756
var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines);
4757
desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []);
4759
var ssrcLines = parsers.findLines('a=ssrc:', lines);
4760
desc.sources = parsers.sources(ssrcLines || []);
4761
4762
if (parsers.findLine('a=x-google-flag:conference', lines, sessionLines)) {
4763
desc.googConferenceFlag = true;
4764
}
4770
fingerprintLines.forEach(function (line) {
4771
var fp = parsers.fingerprint(line);
4772
if (setup) {
4773
fp.setup = setup.substr(8);
4778
var ufragLine = parsers.findLine('a=ice-ufrag:', lines, sessionLines);
4779
var pwdLine = parsers.findLine('a=ice-pwd:', lines, sessionLines);
4780
if (ufragLine && pwdLine) {
4781
trans.ufrag = ufragLine.substr(12);
4782
trans.pwd = pwdLine.substr(10);
4783
trans.candidates = [];
4784
4785
var candidateLines = parsers.findLines('a=candidate:', lines, sessionLines);
4786
candidateLines.forEach(function (line) {
4787
trans.candidates.push(exports.toCandidateJSON(line));
4788
});
4789
}
4790
4791
if (desc.descType == 'datachannel') {
4792
var sctpmapLines = parsers.findLines('a=sctpmap:', lines);
4793
sctpmapLines.forEach(function (line) {
4794
var sctp = parsers.sctpmap(line);
4795
trans.sctp.push(sctp);
4796
});
4802
exports.toCandidateJSON = function (line) {
4803
var candidate = parsers.candidate(line.split('\r\n')[0]);
4804
candidate.id = (idCounter++).toString(36).substr(0, 12);
4805
return candidate;
4806
};
4809
exports.lines = function (sdp) {
4810
return sdp.split('\r\n').filter(function (line) {
4811
return line.length > 0;
4812
});
4815
exports.findLine = function (prefix, mediaLines, sessionLines) {
4816
var prefixLength = prefix.length;
4817
for (var i = 0; i < mediaLines.length; i++) {
4818
if (mediaLines[i].substr(0, prefixLength) === prefix) {
4819
return mediaLines[i];
4820
}
4821
}
4822
// Continue searching in parent session section
4823
if (!sessionLines) {
4824
return false;
4825
}
4827
for (var j = 0; j < sessionLines.length; j++) {
4828
if (sessionLines[j].substr(0, prefixLength) === prefix) {
4829
return sessionLines[j];
4830
}
4831
}
4832
4833
return false;
4836
exports.findLines = function (prefix, mediaLines, sessionLines) {
4837
var results = [];
4838
var prefixLength = prefix.length;
4839
for (var i = 0; i < mediaLines.length; i++) {
4840
if (mediaLines[i].substr(0, prefixLength) === prefix) {
4841
results.push(mediaLines[i]);
4843
}
4844
if (results.length || !sessionLines) {
4845
return results;
4846
}
4847
for (var j = 0; j < sessionLines.length; j++) {
4848
if (sessionLines[j].substr(0, prefixLength) === prefix) {
4849
results.push(sessionLines[j]);
4850
}
4851
}
4852
return results;
4855
exports.mline = function (line) {
4856
var parts = line.substr(2).split(' ');
4857
var parsed = {
4858
media: parts[0],
4859
port: parts[1],
4860
proto: parts[2],
4861
formats: []
4862
};
4863
for (var i = 3; i < parts.length; i++) {
4864
if (parts[i]) {
4865
parsed.formats.push(parts[i]);
4871
exports.rtpmap = function (line) {
4872
var parts = line.substr(9).split(' ');
4873
var parsed = {
4874
id: parts.shift()
4875
};
4876
4877
parts = parts[0].split('/');
4878
4879
parsed.name = parts[0];
4880
parsed.clockrate = parts[1];
4881
parsed.channels = parts.length == 3 ? parts[2] : '1';
4882
return parsed;
4885
exports.sctpmap = function (line) {
4886
// based on -05 draft
4887
var parts = line.substr(10).split(' ');
4888
var parsed = {
4889
number: parts.shift(),
4890
protocol: parts.shift(),
4891
streams: parts.shift()
4892
};
4893
return parsed;
4894
};
4895
4896
4897
exports.fmtp = function (line) {
4898
var kv, key, value;
4899
var parts = line.substr(line.indexOf(' ') + 1).split(';');
4900
var parsed = [];
4901
for (var i = 0; i < parts.length; i++) {
4902
kv = parts[i].split('=');
4903
key = kv[0].trim();
4904
value = kv[1];
4905
if (key && value) {
4906
parsed.push({key: key, value: value});
4907
} else if (key) {
4908
parsed.push({key: '', value: key});
4909
}
4910
}
4911
return parsed;
4914
exports.crypto = function (line) {
4915
var parts = line.substr(9).split(' ');
4916
var parsed = {
4917
tag: parts[0],
4918
cipherSuite: parts[1],
4919
keyParams: parts[2],
4920
sessionParams: parts.slice(3).join(' ')
4921
};
4922
return parsed;
4925
exports.fingerprint = function (line) {
4926
var parts = line.substr(14).split(' ');
4927
return {
4928
hash: parts[0],
4929
value: parts[1]
4930
};
4933
exports.extmap = function (line) {
4934
var parts = line.substr(9).split(' ');
4935
var parsed = {};
4936
4937
var idpart = parts.shift();
4938
var sp = idpart.indexOf('/');
4939
if (sp >= 0) {
4940
parsed.id = idpart.substr(0, sp);
4941
parsed.senders = idpart.substr(sp + 1);
4952
exports.rtcpfb = function (line) {
4953
var parts = line.substr(10).split(' ');
4954
var parsed = {};
4955
parsed.id = parts.shift();
4956
parsed.type = parts.shift();
4957
if (parsed.type === 'trr-int') {
4958
parsed.value = parts.shift();
4959
} else {
4960
parsed.subtype = parts.shift() || '';
4961
}
4962
parsed.parameters = parts;
4963
return parsed;
4967
var parts;
4968
if (line.indexOf('a=candidate:') === 0) {
4969
parts = line.substring(12).split(' ');
4970
} else { // no a=candidate
4971
parts = line.substring(10).split(' ');
4972
}
4973
4974
var candidate = {
4975
foundation: parts[0],
4976
component: parts[1],
4977
protocol: parts[2].toLowerCase(),
4978
priority: parts[3],
4979
ip: parts[4],
4980
port: parts[5],
4981
// skip parts[6] == 'typ'
4982
type: parts[7],
4983
generation: '0'
4984
};
4985
4986
for (var i = 8; i < parts.length; i += 2) {
4987
if (parts[i] === 'raddr') {
4988
candidate.relAddr = parts[i + 1];
4989
} else if (parts[i] === 'rport') {
4990
candidate.relPort = parts[i + 1];
4991
} else if (parts[i] === 'generation') {
4992
candidate.generation = parts[i + 1];
5003
exports.sourceGroups = function (lines) {
5004
var parsed = [];
5005
for (var i = 0; i < lines.length; i++) {
5006
var parts = lines[i].substr(13).split(' ');
5007
parsed.push({
5008
semantics: parts.shift(),
5009
sources: parts
5010
});
5011
}
5015
exports.sources = function (lines) {
5016
// http://tools.ietf.org/html/rfc5576
5017
var parsed = [];
5018
var sources = {};
5019
for (var i = 0; i < lines.length; i++) {
5020
var parts = lines[i].substr(7).split(' ');
5021
var ssrc = parts.shift();
5023
if (!sources[ssrc]) {
5024
var source = {
5025
ssrc: ssrc,
5026
parameters: []
5027
};
5028
parsed.push(source);
5029
5030
// Keep an index
5031
sources[ssrc] = source;
5034
parts = parts.join(' ').split(':');
5035
var attribute = parts.shift();
5036
var value = parts.join(':') || null;
5044
return parsed;
5045
};
5046
5047
exports.groups = function (lines) {
5048
// http://tools.ietf.org/html/rfc5888
5049
var parsed = [];
5050
var parts;
5051
for (var i = 0; i < lines.length; i++) {
5052
parts = lines[i].substr(8).split(' ');
5053
parsed.push({
5054
semantics: parts.shift(),
5055
contents: parts
5056
});
5061
exports.bandwidth = function (line) {
5062
var parts = line.substr(2).split(':');
5063
var parsed = {};
5064
parsed.type = parts.shift();
5065
parsed.bandwidth = parts.shift();
5066
return parsed;
5067
};
5068
5069
},{}],21:[function(require,module,exports){
5070
module.exports = {
5071
initiator: {
5072
incoming: {
5073
initiator: 'recvonly',
5074
responder: 'sendonly',
5075
both: 'sendrecv',
5076
none: 'inactive',
5077
recvonly: 'initiator',
5078
sendonly: 'responder',
5079
sendrecv: 'both',
5080
inactive: 'none'
5081
},
5082
outgoing: {
5083
initiator: 'sendonly',
5084
responder: 'recvonly',
5085
both: 'sendrecv',
5086
none: 'inactive',
5087
recvonly: 'responder',
5088
sendonly: 'initiator',
5089
sendrecv: 'both',
5090
inactive: 'none'
5091
}
5092
},
5093
responder: {
5094
incoming: {
5095
initiator: 'sendonly',
5096
responder: 'recvonly',
5097
both: 'sendrecv',
5098
none: 'inactive',
5099
recvonly: 'responder',
5100
sendonly: 'initiator',
5101
sendrecv: 'both',
5102
inactive: 'none'
5103
},
5104
outgoing: {
5105
initiator: 'recvonly',
5106
responder: 'sendonly',
5107
both: 'sendrecv',
5108
none: 'inactive',
5109
recvonly: 'initiator',
5110
sendonly: 'responder',
5111
sendrecv: 'both',
5112
inactive: 'none'
5113
}
5114
}
5115
};
5116
5117
},{}],18:[function(require,module,exports){
5118
// based on https://github.com/ESTOS/strophe.jingle/
5119
// adds wildemitter support
5120
var util = require('util');
5121
var webrtc = require('webrtcsupport');
5122
var WildEmitter = require('wildemitter');
5123
5124
function dumpSDP(description) {
5125
return {
5126
type: description.type,
5127
sdp: description.sdp
5128
};
5129
}
5130
5131
function dumpStream(stream) {
5132
var info = {
5133
label: stream.id,
5134
};
5135
if (stream.getAudioTracks().length) {
5136
info.audio = stream.getAudioTracks().map(function (track) {
5137
return track.id;
5138
});
5139
}
5140
if (stream.getVideoTracks().length) {
5141
info.video = stream.getVideoTracks().map(function (track) {
5142
return track.id;
5143
});
5144
}
5145
return info;
5146
}
5147
5148
function TraceablePeerConnection(config, constraints) {
5149
var self = this;
5150
WildEmitter.call(this);
5151
5152
this.peerconnection = new webrtc.PeerConnection(config, constraints);
5153
5154
this.trace = function (what, info) {
5155
self.emit('PeerConnectionTrace', {
5156
time: new Date(),
5157
type: what,
5158
value: info || ""
5159
});
5160
};
5161
5162
this.onicecandidate = null;
5163
this.peerconnection.onicecandidate = function (event) {
5164
self.trace('onicecandidate', event.candidate);
5165
if (self.onicecandidate !== null) {
5166
self.onicecandidate(event);
5167
}
5168
};
5169
this.onaddstream = null;
5170
this.peerconnection.onaddstream = function (event) {
5171
self.trace('onaddstream', dumpStream(event.stream));
5172
if (self.onaddstream !== null) {
5173
self.onaddstream(event);
5174
}
5175
};
5176
this.onremovestream = null;
5177
this.peerconnection.onremovestream = function (event) {
5178
self.trace('onremovestream', dumpStream(event.stream));
5179
if (self.onremovestream !== null) {
5180
self.onremovestream(event);
5181
}
5182
};
5183
this.onsignalingstatechange = null;
5184
this.peerconnection.onsignalingstatechange = function (event) {
5185
self.trace('onsignalingstatechange', self.signalingState);
5186
if (self.onsignalingstatechange !== null) {
5187
self.onsignalingstatechange(event);
5188
}
5189
};
5190
this.oniceconnectionstatechange = null;
5191
this.peerconnection.oniceconnectionstatechange = function (event) {
5192
self.trace('oniceconnectionstatechange', self.iceConnectionState);
5193
if (self.oniceconnectionstatechange !== null) {
5194
self.oniceconnectionstatechange(event);
5195
}
5196
};
5197
this.onnegotiationneeded = null;
5198
this.peerconnection.onnegotiationneeded = function (event) {
5199
self.trace('onnegotiationneeded');
5200
if (self.onnegotiationneeded !== null) {
5201
self.onnegotiationneeded(event);
5202
}
5203
};
5204
self.ondatachannel = null;
5205
this.peerconnection.ondatachannel = function (event) {
5206
self.trace('ondatachannel', event);
5207
if (self.ondatachannel !== null) {
5208
self.ondatachannel(event);
5209
}
5210
};
5211
this.getLocalStreams = this.peerconnection.getLocalStreams.bind(this.peerconnection);
5212
this.getRemoteStreams = this.peerconnection.getRemoteStreams.bind(this.peerconnection);
5213
}
5214
5215
util.inherits(TraceablePeerConnection, WildEmitter);
5216
5217
Object.defineProperty(TraceablePeerConnection.prototype, 'signalingState', {
5218
get: function () {
5219
return this.peerconnection.signalingState;
5220
}
5221
});
5222
5223
Object.defineProperty(TraceablePeerConnection.prototype, 'iceConnectionState', {
5224
get: function () {
5225
return this.peerconnection.iceConnectionState;
5226
}
5227
});
5228
5229
Object.defineProperty(TraceablePeerConnection.prototype, 'localDescription', {
5230
get: function () {
5231
return this.peerconnection.localDescription;
5232
}
5233
});
5234
5235
Object.defineProperty(TraceablePeerConnection.prototype, 'remoteDescription', {
5236
get: function () {
5237
return this.peerconnection.remoteDescription;
5238
}
5239
});
5240
5241
TraceablePeerConnection.prototype.addStream = function (stream) {
5242
this.trace('addStream', dumpStream(stream));
5243
this.peerconnection.addStream(stream);
5244
};
5245
5246
TraceablePeerConnection.prototype.removeStream = function (stream) {
5247
this.trace('removeStream', dumpStream(stream));
5248
this.peerconnection.removeStream(stream);
5249
};
5250
5251
TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
5252
this.trace('createDataChannel', label, opts);
5253
return this.peerconnection.createDataChannel(label, opts);
5254
};
5255
5256
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
5257
var self = this;
5258
this.trace('setLocalDescription', dumpSDP(description));
5259
this.peerconnection.setLocalDescription(description,
5260
function () {
5261
self.trace('setLocalDescriptionOnSuccess');
5262
successCallback();
5263
},
5264
function (err) {
5265
self.trace('setLocalDescriptionOnFailure', err);
5266
failureCallback(err);
5267
}
5268
);
5269
};
5270
5271
TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
5272
var self = this;
5273
this.trace('setRemoteDescription', dumpSDP(description));
5274
this.peerconnection.setRemoteDescription(description,
5275
function () {
5276
self.trace('setRemoteDescriptionOnSuccess');
5277
successCallback();
5278
},
5279
function (err) {
5280
self.trace('setRemoteDescriptionOnFailure', err);
5281
failureCallback(err);
5282
}
5283
);
5284
};
5285
5286
TraceablePeerConnection.prototype.close = function () {
5287
this.trace('stop');
5288
if (this.statsinterval !== null) {
5289
window.clearInterval(this.statsinterval);
5290
this.statsinterval = null;
5291
}
5292
if (this.peerconnection.signalingState != 'closed') {
5293
this.peerconnection.close();
5294
}
5295
};
5296
5297
TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
5298
var self = this;
5299
this.trace('createOffer', constraints);
5300
this.peerconnection.createOffer(
5301
function (offer) {
5302
self.trace('createOfferOnSuccess', dumpSDP(offer));
5303
successCallback(offer);
5304
},
5305
function (err) {
5306
self.trace('createOfferOnFailure', err);
5307
failureCallback(err);
5308
},
5309
constraints
5310
);
5311
};
5312
5313
TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
5314
var self = this;
5315
this.trace('createAnswer', constraints);
5316
this.peerconnection.createAnswer(
5317
function (answer) {
5318
self.trace('createAnswerOnSuccess', dumpSDP(answer));
5319
successCallback(answer);
5320
},
5321
function (err) {
5322
self.trace('createAnswerOnFailure', err);
5323
failureCallback(err);
5324
},
5325
constraints
5326
);
5327
};
5328
5329
TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
5330
var self = this;
5331
this.trace('addIceCandidate', candidate);
5332
this.peerconnection.addIceCandidate(candidate,
5333
function () {
5334
//self.trace('addIceCandidateOnSuccess');
5335
if (successCallback) successCallback();
5336
},
5337
function (err) {
5338
self.trace('addIceCandidateOnFailure', err);
5339
if (failureCallback) failureCallback(err);
5340
}
5341
);
5342
};
5343
5344
TraceablePeerConnection.prototype.getStats = function (callback, errback) {
5345
if (navigator.mozGetUserMedia) {
5346
this.peerconnection.getStats(null, callback, errback);
5347
} else {
5348
this.peerconnection.getStats(callback);
5349
}
5350
};
5351
5352
module.exports = TraceablePeerConnection;
5353
5354
},{"util":2,"webrtcsupport":5,"wildemitter":4}]},{},[1])(1)