UNPKG

175 kBJavaScriptView Raw
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;
2return (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){
3var util = require('util');
4var webrtc = require('webrtcsupport');
5var WildEmitter = require('wildemitter');
6var mockconsole = require('mockconsole');
7var localMedia = require('localmedia');
8var Peer = require('./peer');
9
10
11function WebRTC(opts) {
12 var self = this;
13 var options = opts || {};
14 var config = this.config = {
15 debug: false,
16 // makes the entire PC config overridable
17 peerConnectionConfig: {
18 iceServers: [{"url": "stun:stun.l.google.com:19302"}]
19 },
20 peerConnectionConstraints: {
21 optional: [
22 {DtlsSrtpKeyAgreement: true}
23 ]
24 },
25 receiveMedia: {
26 mandatory: {
27 OfferToReceiveAudio: true,
28 OfferToReceiveVideo: true
29 }
30 },
31 enableDataChannels: true
32 };
33 var item;
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 }
64
65 // where we'll store our peer connections
66 this.peers = [];
67
68 // call localMedia constructor
69 localMedia.call(this, this.config);
70
71 this.on('speaking', function () {
72 if (!self.hardMuted) {
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 });
81 }
82 });
83 this.on('stoppedSpeaking', function () {
84 if (!self.hardMuted) {
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 });
93 }
94 });
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 });
107
108 // log events in debug mode
109 if (this.config.debug) {
110 this.on('*', function (event, val1, val2) {
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);
120 });
121 }
122}
123
124util.inherits(WebRTC, localMedia);
125
126WebRTC.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
135WebRTC.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
142WebRTC.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
149WebRTC.prototype.sendToAll = function (message, payload) {
150 this.peers.forEach(function (peer) {
151 peer.send(message, payload);
152 });
153};
154
155// sends message to all using a datachannel
156// only sends to anyone who has an open datachannel
157WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) {
158 this.peers.forEach(function (peer) {
159 if (peer.enableDataChannels) {
160 peer.sendDirectly(channel, message, payload);
161 }
162 });
163};
164
165module.exports = WebRTC;
166
167},{"./peer":3,"localmedia":7,"mockconsole":6,"util":2,"webrtcsupport":5,"wildemitter":4}],2:[function(require,module,exports){
168var events = require('events');
169
170exports.isArray = isArray;
171exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'};
172exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'};
173
174
175exports.print = function () {};
176exports.puts = function () {};
177exports.debug = function() {};
178
179exports.inspect = function(obj, showHidden, depth, colors) {
180 var seen = [];
181
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] };
198
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];
209
210 if (style) {
211 return '\u001b[' + styles[style][0] + 'm' + str +
212 '\u001b[' + styles[style][1] + 'm';
213 } else {
214 return str;
215 }
216 };
217 if (! colors) {
218 stylize = function(str, styleType) { return str; };
219 }
220
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 }
231
232 // Primitive types cannot have properties
233 switch (typeof value) {
234 case 'undefined':
235 return stylize('undefined', 'undefined');
236
237 case 'string':
238 var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
239 .replace(/'/g, "\\'")
240 .replace(/\\"/g, '"') + '\'';
241 return stylize(simple, 'string');
242
243 case 'number':
244 return stylize('' + value, 'number');
245
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');
252 }
253
254 // Look up the keys of the object.
255 var visible_keys = Object_keys(value);
256 var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys;
257
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 }
266 }
267
268 // Dates without properties can be shortcutted
269 if (isDate(value) && keys.length === 0) {
270 return stylize(value.toUTCString(), 'date');
271 }
272
273 var base, type, braces;
274 // Determine the object type
275 if (isArray(value)) {
276 type = 'Array';
277 braces = ['[', ']'];
278 } else {
279 type = 'Object';
280 braces = ['{', '}'];
281 }
282
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 = '';
289 }
290
291 // Make dates with properties first say the date
292 if (isDate(value)) {
293 base = ' ' + value.toUTCString();
294 }
295
296 if (keys.length === 0) {
297 return braces[0] + base + braces[1];
298 }
299
300 if (recurseTimes < 0) {
301 if (isRegExp(value)) {
302 return stylize('' + value, 'regexp');
303 } else {
304 return stylize('[Object]', 'special');
305 }
306 }
307
308 seen.push(value);
309
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 }
323 }
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);
340 } else {
341 str = '\n' + str.split('\n').map(function(line) {
342 return ' ' + line;
343 }).join('\n');
344 }
345 }
346 } else {
347 str = stylize('[Circular]', 'special');
348 }
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 }
365
366 return name + ': ' + str;
367 });
368
369 seen.pop();
370
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];
388 }
389
390 return output;
391 }
392 return format(obj, (typeof depth === 'undefined' ? 2 : depth));
393};
394
395
396function isArray(ar) {
397 return Array.isArray(ar) ||
398 (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]');
399}
400
401
402function isRegExp(re) {
403 typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]';
404}
405
406
407function isDate(d) {
408 return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]';
409}
410
411function pad(n) {
412 return n < 10 ? '0' + n.toString(10) : n.toString(10);
413}
414
415var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
416 'Oct', 'Nov', 'Dec'];
417
418// 26 Feb 16:19:34
419function 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}
426
427exports.log = function (msg) {};
428
429exports.pump = null;
430
431var Object_keys = Object.keys || function (obj) {
432 var res = [];
433 for (var key in obj) res.push(key);
434 return res;
435};
436
437var 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};
444
445var Object_create = Object.create || function (prototype, properties) {
446 // from es5-shim
447 var object;
448 if (prototype === null) {
449 object = { '__proto__' : null };
450 }
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};
467
468exports.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
480var formatRegExp = /%[sdj%]/g;
481exports.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
514},{"events":8}],4:[function(require,module,exports){
515/*
516WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
517on @visionmedia's Emitter from UI Kit.
518
519Why? I wanted it standalone.
520
521I also wanted support for wildcard emitters like this:
522
523emitter.on('*', function (eventName, other, event, payloads) {
524
525});
526
527emitter.on('somenamespace*', function (eventName, payloads) {
528
529});
530
531Please note that callbacks triggered by wildcard registered events also get
532the event name as the first argument.
533*/
534module.exports = WildEmitter;
535
536function WildEmitter() {
537 this.callbacks = {};
538}
539
540// Listen on the given `event` with `fn`. Store a group name if present.
541WildEmitter.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.
552WildEmitter.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
566WildEmitter.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.
585WildEmitter.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
605WildEmitter.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
641WildEmitter.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
655},{}],5:[function(require,module,exports){
656// created by @HenrikJoreteg
657var prefix;
658
659if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) {
660 prefix = 'moz';
661} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) {
662 prefix = 'webkit';
663}
664
665var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
666var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
667var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
668var MediaStream = window.webkitMediaStream || window.MediaStream;
669var 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));
672var AudioContext = window.AudioContext || window.webkitAudioContext;
673var supportVp8 = document.createElement('video').canPlayType('video/webm; codecs="vp8", vorbis') === "probably";
674var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia;
675
676// export support flags and constructors.prototype && PC
677module.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){
703var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(",");
704var l = methods.length;
705var fn = function () {};
706var mockconsole = {};
707
708while (l--) {
709 mockconsole[methods[l]] = fn;
710}
711
712module.exports = mockconsole;
713
714},{}],9:[function(require,module,exports){
715// shim for using process in browser
716
717var process = module.exports = {};
718
719process.nextTick = (function () {
720 var canSetImmediate = typeof window !== 'undefined'
721 && window.setImmediate;
722 var canPost = typeof window !== 'undefined'
723 && window.postMessage && window.addEventListener
724 ;
725
726 if (canSetImmediate) {
727 return function (f) { return window.setImmediate(f) };
728 }
729
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);
742
743 return function nextTick(fn) {
744 queue.push(fn);
745 window.postMessage('process-tick', '*');
746 };
747 }
748
749 return function nextTick(fn) {
750 setTimeout(fn, 0);
751 };
752})();
753
754process.title = 'browser';
755process.browser = true;
756process.env = {};
757process.argv = [];
758
759process.binding = function (name) {
760 throw new Error('process.binding is not supported');
761}
762
763// TODO(shtylman)
764process.cwd = function () { return '/' };
765process.chdir = function (dir) {
766 throw new Error('process.chdir is not supported');
767};
768
769},{}],8:[function(require,module,exports){
770var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {};
771
772var EventEmitter = exports.EventEmitter = process.EventEmitter;
773var isArray = typeof Array.isArray === 'function'
774 ? Array.isArray
775 : function (xs) {
776 return Object.prototype.toString.call(xs) === '[object Array]'
777 }
778;
779function 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.
793var defaultMaxListeners = 10;
794EventEmitter.prototype.setMaxListeners = function(n) {
795 if (!this._events) this._events = {};
796 this._events.maxListeners = n;
797};
798
799
800EventEmitter.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;
818
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);
835 }
836 return true;
837
838 } else if (isArray(handler)) {
839 var args = Array.prototype.slice.call(arguments, 1);
840
841 var listeners = handler.slice();
842 for (var i = 0, l = listeners.length; i < l; i++) {
843 listeners[i].apply(this, args);
844 }
845 return true;
846
847 } else {
848 return false;
849 }
850};
851
852// EventEmitter is defined in src/node_events.cc
853// EventEmitter.prototype.emit() is also defined there.
854EventEmitter.prototype.addListener = function(type, listener) {
855 if ('function' !== typeof listener) {
856 throw new Error('addListener only takes instances of Function');
857 }
858
859 if (!this._events) this._events = {};
860
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);
864
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])) {
869
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 }
878
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 }
887 }
888
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];
894 }
895
896 return this;
897};
898
899EventEmitter.prototype.on = EventEmitter.prototype.addListener;
900
901EventEmitter.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 });
907
908 return this;
909};
910
911EventEmitter.prototype.removeListener = function(type, listener) {
912 if ('function' !== typeof listener) {
913 throw new Error('removeListener only takes instances of Function');
914 }
915
916 // does not use listeners(), so no side effect of creating _events[type]
917 if (!this._events || !this._events[type]) return this;
918
919 var list = this._events[type];
920
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 }
930
931 return this;
932};
933
934EventEmitter.prototype.removeAllListeners = function(type) {
935 if (arguments.length === 0) {
936 this._events = {};
937 return this;
938 }
939
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;
943};
944
945EventEmitter.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]];
950 }
951 return this._events[type];
952};
953
954EventEmitter.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;
963};
964
965},{"__browserify_process":9}],3:[function(require,module,exports){
966var util = require('util');
967var webrtc = require('webrtcsupport');
968var PeerConnection = require('rtcpeerconnection');
969var WildEmitter = require('wildemitter');
970var FileTransfer = require('filetransfer');
971
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
974var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1';
975
976function Peer(options) {
977 var self = this;
978
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;
1022
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 });
1034 }
1035
1036 // call emitter constructor
1037 WildEmitter.call(this);
1038
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 });
1057}
1058
1059util.inherits(Peer, WildEmitter);
1060
1061Peer.prototype.handleMessage = function (message) {
1062 var self = this;
1063
1064 this.logger.log('getting', message.type, message);
1065
1066 if (message.prefix) this.browserPrefix = message.prefix;
1067
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
1094Peer.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
1110Peer.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
1123Peer.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
1134Peer.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
1145Peer.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
1154Peer.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
1170Peer.prototype.icerestart = function () {
1171 var constraints = this.receiveMedia;
1172 constraints.mandatory.IceRestart = true;
1173 this.pc.offer(constraints, function (err, success) { });
1174};
1175
1176Peer.prototype.end = function () {
1177 if (this.closed) return;
1178 this.pc.close();
1179 this.handleStreamRemoved();
1180};
1181
1182Peer.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
1197Peer.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
1203Peer.prototype.handleDataChannelAdded = function (channel) {
1204 this.channels[channel.label] = channel;
1205 this._observeDataChannel(channel);
1206};
1207
1208Peer.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
1229module.exports = Peer;
1230
1231},{"filetransfer":11,"rtcpeerconnection":10,"util":2,"webrtcsupport":5,"wildemitter":4}],12:[function(require,module,exports){
1232// getUserMedia helper by @HenrikJoreteg
1233var func = (window.navigator.getUserMedia ||
1234 window.navigator.webkitGetUserMedia ||
1235 window.navigator.mozGetUserMedia ||
1236 window.navigator.msGetUserMedia);
1237
1238
1239module.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){
1327var util = require('util');
1328var hark = require('hark');
1329var webrtc = require('webrtcsupport');
1330var getUserMedia = require('getusermedia');
1331var getScreenMedia = require('getscreenmedia');
1332var WildEmitter = require('wildemitter');
1333var GainController = require('mediastream-gain');
1334var mockconsole = require('mockconsole');
1335
1336
1337function 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
1369util.inherits(LocalMedia, WildEmitter);
1370
1371
1372LocalMedia.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 */
1399 };
1400
1401 self.emit('localStream', stream);
1402 }
1403 if (cb) {
1404 return cb(err, stream);
1405 }
1406 });
1407};
1408
1409LocalMedia.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 = [];
1429 }
1430};
1431
1432LocalMedia.prototype.startScreenShare = function (cb) {
1433 var self = this;
1434 getScreenMedia(function (err, stream) {
1435 if (!err) {
1436 self.localScreens.push(stream);
1437
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 }
1450
1451 // enable the callback
1452 if (cb) {
1453 return cb(err, stream);
1454 }
1455 });
1456};
1457
1458LocalMedia.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 = [];
1466 }
1467};
1468
1469// Audio controls
1470LocalMedia.prototype.mute = function () {
1471 this._audioEnabled(false);
1472 this.hardMuted = true;
1473 this.emit('audioOff');
1474};
1475
1476LocalMedia.prototype.unmute = function () {
1477 this._audioEnabled(true);
1478 this.hardMuted = false;
1479 this.emit('audioOn');
1480};
1481
1482LocalMedia.prototype.setupAudioMonitor = function (stream, harkOptions) {
1483 this._log('Setup audio');
1484 var audio = this.audioMonitor = hark(stream, harkOptions);
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);
1494 });
1495
1496 audio.on('stopped_speaking', function () {
1497 if (timeout) {
1498 clearTimeout(timeout);
1499 }
1500
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};
1513
1514// We do this as a seperate method in order to
1515// still leave the "setMicVolume" as a working
1516// method.
1517LocalMedia.prototype.setMicIfEnabled = function (volume) {
1518 if (!this.config.autoAdjustMic) {
1519 return;
1520 }
1521 this.gainController.setGain(volume);
1522};
1523
1524// Video controls
1525LocalMedia.prototype.pauseVideo = function () {
1526 this._videoEnabled(false);
1527 this.emit('videoOff');
1528};
1529LocalMedia.prototype.resumeVideo = function () {
1530 this._videoEnabled(true);
1531 this.emit('videoOn');
1532};
1533
1534// Combined controls
1535LocalMedia.prototype.pause = function () {
1536 this.mute();
1537 this.pauseVideo();
1538};
1539LocalMedia.prototype.resume = function () {
1540 this.unmute();
1541 this.resumeVideo();
1542};
1543
1544// Internal methods for enabling/disabling audio/video
1545LocalMedia.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};
1555LocalMedia.prototype._videoEnabled = function (bool) {
1556 this.localStreams.forEach(function (stream) {
1557 stream.getVideoTracks().forEach(function (track) {
1558 track.enabled = !!bool;
1559 });
1560 });
1561};
1562
1563// check if all audio streams are enabled
1564LocalMedia.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};
1573
1574// check if all video streams are enabled
1575LocalMedia.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
1586LocalMedia.prototype.startLocalMedia = LocalMedia.prototype.start;
1587LocalMedia.prototype.stopLocalMedia = LocalMedia.prototype.stop;
1588
1589// fallback for old .localStream behaviour
1590Object.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
1596Object.defineProperty(LocalMedia.prototype, 'localScreen', {
1597 get: function () {
1598 return this.localScreens.length > 0 ? this.localScreens[0] : null;
1599 }
1600});
1601
1602module.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.
1609
1610(function() {
1611
1612 // Baseline setup
1613 // --------------
1614
1615 // Establish the root object, `window` in the browser, or `exports` on the server.
1616 var root = this;
1617
1618 // Save the previous value of the `_` variable.
1619 var previousUnderscore = root._;
1620
1621 // Save bytes in the minified (but not gzipped) version:
1622 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
1623
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;
1630
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;
1638
1639 // Naked function reference for surrogate-prototype-swapping.
1640 var Ctor = function(){};
1641
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 };
1648
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 }
1660
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 };
1687
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 };
1700
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;
1716 };
1717 };
1718
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 };
1728
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 };
1737
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 }
1756 }
1757 return obj;
1758 };
1759
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);
1769 }
1770 return results;
1771 };
1772
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 }
1784
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 }
1798
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);
1813 }
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;
1842 }
1843 return true;
1844 };
1845
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 };
1858
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 };
1865
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 };
1875
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 };
1880
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 };
1886
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 }
1905 } else {
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;
1912 }
1913 });
1914 }
1915 return result;
1916 };
1917
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;
1928 }
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;
1937 }
1938 });
1939 }
1940 return result;
1941 };
1942
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 };
1956
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 };
1967
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 };
1987
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 };
2000
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 });
2006
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 });
2012
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 });
2019
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 };
2027
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 };
2033
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 };
2044
2045 // Array Functions
2046 // ---------------
2047
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 };
2056
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 };
2063
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 };
2071
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 };
2078
2079 // Trim out all falsy values from an array.
2080 _.compact = function(array) {
2081 return _.filter(array, _.identity);
2082 };
2083
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 }
2100 }
2101 return output;
2102 };
2103
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 };
2108
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 };
2113
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);
2140 }
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;
2162 }
2163 if (j === argsLength) result.push(item);
2164 }
2165 return result;
2166 };
2167
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);
2174 });
2175 };
2176
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);
2188
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];
2203 } else {
2204 result[list[i][0]] = list[i][1];
2205 }
2206 }
2207 return result;
2208 };
2209
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;
2221 }
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;
2227 };
2228
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;
2249 }
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;
2270 };
2271
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;
2290 };
2291
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;
2303 };
2304
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;
2316 };
2317
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;
2333 };
2334
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;
2346 };
2347
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) {
2363 var args = slice.call(arguments, 2);
2364 return setTimeout(function(){
2365 return func.apply(null, args);
2366 }, wait);
2367 };
2368
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 };
2408 };
2409
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;
2416
2417 var later = function() {
2418 var last = _.now() - timestamp;
2419
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 }
2428 }
2429 };
2430
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;
2440 }
2441
2442 return result;
2443 };
2444 };
2445
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);
2451 };
2452
2453 // Returns a negated version of the passed-in predicate.
2454 _.negate = function(predicate) {
2455 return function() {
2456 return !predicate.apply(this, arguments);
2457 };
2458 };
2459
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);
2469 return result;
2470 };
2471 };
2472
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 };
2480 };
2481
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 };
2492 };
2493
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);
2497
2498 // Object Functions
2499 // ----------------
2500
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'];
2505
2506 function collectNonEnumProps(obj, keys) {
2507 var nonEnumIdx = nonEnumerableProps.length;
2508 var constructor = obj.constructor;
2509 var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
2510
2511 // Constructor is a special case.
2512 var prop = 'constructor';
2513 if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
2514
2515 while (nonEnumIdx--) {
2516 prop = nonEnumerableProps[nonEnumIdx];
2517 if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
2518 keys.push(prop);
2519 }
2520 }
2521 }
2522
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;
2533 };
2534
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;
2543 };
2544
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]];
2552 }
2553 return values;
2554 };
2555
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;
2569 };
2570
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;
2580 };
2581
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;
2590 };
2591
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);
2598 }
2599 return names.sort();
2600 };
2601
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;
2616 }
2617 };
2618
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);
2630 }
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;
2637 };
2638
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 };
2648 }
2649 return _.pick(obj, iteratee, context);
2650 };
2651
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;
2677 }
2678 return true;
2679 };
2680
2681
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;
2715 }
2716
2717 var areArrays = className === '[object Array]';
2718 if (!areArrays) {
2719 if (typeof a != 'object' || typeof b != 'object') return false;
2720
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 }
2743
2744 // Add the first object to the stack of traversed objects.
2745 aStack.push(a);
2746 bStack.push(b);
2747
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;
2773 };
2774
2775 // Perform a deep comparison to check if two objects are equal.
2776 _.isEqual = function(a, b) {
2777 return eq(a, b);
2778 };
2779
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;
2786 };
2787
2788 // Is a given value a DOM element?
2789 _.isElement = function(obj) {
2790 return !!(obj && obj.nodeType === 1);
2791 };
2792
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]';
2797 };
2798
2799 // Is a given variable an object?
2800 _.isObject = function(obj) {
2801 var type = typeof obj;
2802 return type === 'function' || type === 'object' && !!obj;
2803 };
2804
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 + ']';
2809 };
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;
2825 };
2826 }
2827
2828 // Is a given object a finite number?
2829 _.isFinite = function(obj) {
2830 return isFinite(obj) && !isNaN(parseFloat(obj));
2831 };
2832
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 };
2837
2838 // Is a given value a boolean?
2839 _.isBoolean = function(obj) {
2840 return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
2841 };
2842
2843 // Is a given value equal to null?
2844 _.isNull = function(obj) {
2845 return obj === null;
2846 };
2847
2848 // Is a given variable undefined?
2849 _.isUndefined = function(obj) {
2850 return obj === void 0;
2851 };
2852
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);
2857 };
2858
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;
2867 };
2868
2869 // Keep the identity function around for default iteratees.
2870 _.identity = function(value) {
2871 return value;
2872 };
2873
2874 // Predicate-generating functions. Often useful outside of Underscore.
2875 _.constant = function(value) {
2876 return function() {
2877 return value;
2878 };
2879 };
2880
2881 _.noop = function(){};
2882
2883 _.property = function(key) {
2884 return function(obj) {
2885 return obj == null ? void 0 : obj[key];
2886 };
2887 };
2888
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 };
2894 };
2895
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 };
2903 };
2904
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;
2911 };
2912
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;
2918 }
2919 return min + Math.floor(Math.random() * (max - min + 1));
2920 };
2921
2922 // A (possibly faster) way to get the current timestamp as an integer.
2923 _.now = Date.now || function() {
2924 return new Date().getTime();
2925 };
2926
2927 // List of HTML entities for escaping.
2928 var escapeMap = {
2929 '&': '&amp;',
2930 '<': '&lt;',
2931 '>': '&gt;',
2932 '"': '&quot;',
2933 "'": '&#x27;',
2934 '`': '&#x60;'
2935 };
2936 var unescapeMap = _.invert(escapeMap);
2937
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;
2961 }
2962 return _.isFunction(value) ? value.call(object) : value;
2963 };
2964
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;
2971 };
2972
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
2979 };
2980
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'
2995 };
2996
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+='";
3031 }
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;
3050 }
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;
3061 };
3062
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;
3068 };
3069
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 });
3091 };
3092
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;
3118 };
3119
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;
3126 };
3127
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));
3141
3142},{}],11:[function(require,module,exports){
3143var WildEmitter = require('wildemitter');
3144var util = require('util');
3145
3146function Sender(opts) {
3147 WildEmitter.call(this);
3148 var options = opts || {};
3149 this.config = {
3150 chunksize: 16384,
3151 pacing: 0
3152 };
3153 // set our config from options
3154 var item;
3155 for (item in options) {
3156 this.config[item] = options[item];
3157 }
3158
3159 this.file = null;
3160 this.channel = null;
3161}
3162util.inherits(Sender, WildEmitter);
3163
3164Sender.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);
3184 };
3185 window.setTimeout(sliceFile, 0, 0);
3186};
3187
3188function Receiver() {
3189 WildEmitter.call(this);
3190
3191 this.receiveBuffer = [];
3192 this.received = 0;
3193 this.metadata = {};
3194 this.channel = null;
3195}
3196util.inherits(Receiver, WildEmitter);
3197
3198Receiver.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 }
3222 };
3223};
3224
3225module.exports = {};
3226module.exports.support = window && window.File && window.FileReader && window.Blob;
3227module.exports.Sender = Sender;
3228module.exports.Receiver = Receiver;
3229
3230},{"util":2,"wildemitter":4}],10:[function(require,module,exports){
3231var _ = require('underscore');
3232var util = require('util');
3233var webrtc = require('webrtcsupport');
3234var SJJ = require('sdp-jingle-json');
3235var WildEmitter = require('wildemitter');
3236var peerconn = require('traceablepeerconnection');
3237
3238function PeerConnection(config, constraints) {
3239 var self = this;
3240 var item;
3241 WildEmitter.call(this);
3242
3243 config = config || {};
3244 config.iceServers = config.iceServers || [];
3245
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 }
3258
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
3302
3303 this.pc = new peerconn(config, constraints);
3304
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);
3309
3310 // proxy events
3311 this.pc.on('*', function () {
3312 self.emit.apply(self, arguments);
3313 });
3314
3315 // proxy some events directly
3316 this.pc.onremovestream = this.emit.bind(this, 'removeStream');
3317 this.pc.onaddstream = this.emit.bind(this, 'addStream');
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
3322 // handle ice candidate and data channel events
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: []
3331 };
3332
3333 this.config = {
3334 debug: false,
3335 ice: {},
3336 sid: '',
3337 isInitiator: true,
3338 sdpSessionID: Date.now(),
3339 useJingle: false
3340 };
3341
3342 // apply our config
3343 for (item in config) {
3344 this.config[item] = config[item];
3345 }
3346
3347 if (this.config.debug) {
3348 this.on('*', function (eventName, event) {
3349 var logger = config.logger || console;
3350 logger.log('PeerConnection event:', arguments);
3351 });
3352 }
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
3371util.inherits(PeerConnection, WildEmitter);
3372
3373Object.defineProperty(PeerConnection.prototype, 'signalingState', {
3374 get: function () {
3375 return this.pc.signalingState;
3376 }
3377});
3378Object.defineProperty(PeerConnection.prototype, 'iceConnectionState', {
3379 get: function () {
3380 return this.pc.iceConnectionState;
3381 }
3382});
3383
3384PeerConnection.prototype._role = function () {
3385 return this.isInitiator ? 'initiator' : 'responder';
3386};
3387
3388// Add a stream to the peer connection object
3389PeerConnection.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
3396PeerConnection.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
3410PeerConnection.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
3424PeerConnection.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)) {
3433 var contentNames = _.pluck(this.remoteDescription.contents, 'name');
3434 var contents = update.contents || 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
3462 if (update.candidate && update.candidate.candidate.indexOf('a=') !== 0) {
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
3479PeerConnection.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 () {};
3490
3491 if (this.pc.signalingState === 'closed') return cb('Already closed');
3492
3493 // Actually generate the offer
3494 this.pc.createOffer(
3495 function (offer) {
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, {
3511 role: self._role(),
3512 direction: 'outgoing'
3513 });
3514 jingle.sid = self.config.sid;
3515 self.localDescription = jingle;
3516
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 });
3527
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 });
3535
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};
3554
3555
3556// Process an incoming offer so that ICE may proceed before deciding
3557// to answer the request.
3558PeerConnection.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 }
3614 offer.sdp = SJJ.toSessionSDP(offer.jingle, {
3615 sid: self.config.sdpSessionID,
3616 role: self._role(),
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 }
3625 });
3626 self.pc.setRemoteDescription(new webrtc.SessionDescription(offer),
3627 function () {
3628 cb();
3629 },
3630 cb
3631 );
3632};
3633
3634// Answer an offer with audio only
3635PeerConnection.prototype.answerAudioOnly = function (cb) {
3636 var mediaConstraints = {
3637 mandatory: {
3638 OfferToReceiveAudio: true,
3639 OfferToReceiveVideo: false
3640 }
3641 };
3642 this._answer(mediaConstraints, cb);
3643};
3644
3645// Answer an offer without offering to recieve
3646PeerConnection.prototype.answerBroadcastOnly = function (cb) {
3647 var mediaConstraints = {
3648 mandatory: {
3649 OfferToReceiveAudio: false,
3650 OfferToReceiveVideo: false
3651 }
3652 };
3653 this._answer(mediaConstraints, cb);
3654};
3655
3656// Answer an offer with given constraints default is audio/video
3657PeerConnection.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 };
3667
3668 this._answer(mediaConstraints, callback);
3669};
3670
3671// Process an answer
3672PeerConnection.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,
3678 role: self._role(),
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 }
3687 });
3688 self.pc.setRemoteDescription(
3689 new webrtc.SessionDescription(answer),
3690 function () {
3691 cb(null);
3692 },
3693 cb
3694 );
3695};
3696
3697// Close the peer connection
3698PeerConnection.prototype.close = function () {
3699 this.pc.close();
3700
3701 this._localDataChannels = [];
3702 this._remoteDataChannels = [];
3703
3704 this.emit('close');
3705};
3706
3707// Internal code sharing for various types of answer methods
3708PeerConnection.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');
3714 }
3715
3716 if (this.pc.signalingState === 'closed') return cb('Already closed');
3717
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, {
3725 role: self._role(),
3726 direction: 'outgoing'
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 });
3747
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 });
3756
3757 answer.jingle.contents[1].description.sourceGroups = groups;
3758 answer.sdp = SJJ.toSessionSDP(answer.jingle, {
3759 sid: self.config.sdpSessionID,
3760 role: self._role(),
3761 direction: 'outgoing'
3762 });
3763 }
3764 }
3765 }
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, {
3779 role: self._role(),
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, {
3792 role: self._role(),
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,
3809 role: self._role(),
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};
3836
3837// Internal method for emitting ice candidates on our peer object
3838PeerConnection.prototype._onIce = function (event) {
3839 var self = this;
3840 if (event.candidate) {
3841 var ice = event.candidate;
3842
3843 var expandedCandidate = {
3844 candidate: event.candidate
3845 };
3846 this._checkLocalCandidate(ice.candidate);
3847
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, {
3855 role: self._role(),
3856 direction: 'outgoing'
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,
3871 creator: self._role(),
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};
3913
3914// Internal method for processing a new data channel being added by the
3915// other peer.
3916PeerConnection.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);
3920
3921 this.emit('addChannel', channel);
3922};
3923
3924// Create a data channel spec reference:
3925// http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCDataChannelInit
3926PeerConnection.prototype.createDataChannel = function (name, opts) {
3927 var channel = this.pc.createDataChannel(name, opts);
3928
3929 // make sure we keep a reference so this doesn't get garbage collected
3930 this._localDataChannels.push(channel);
3931
3932 return channel;
3933};
3934
3935// a wrapper around getStats which hides the differences (where possible)
3936PeerConnection.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 });
3965 }
3966};
3967
3968module.exports = PeerConnection;
3969
3970},{"sdp-jingle-json":17,"traceablepeerconnection":18,"underscore":16,"util":2,"webrtcsupport":5,"wildemitter":4}],17:[function(require,module,exports){
3971var toSDP = require('./lib/tosdp');
3972var toJSON = require('./lib/tojson');
3973
3974
3975// Converstion from JSON to SDP
3976
3977exports.toIncomingSDPOffer = function (session) {
3978 return toSDP.toSessionSDP(session, {
3979 role: 'responder',
3980 direction: 'incoming'
3981 });
3982};
3983exports.toOutgoingSDPOffer = function (session) {
3984 return toSDP.toSessionSDP(session, {
3985 role: 'initiator',
3986 direction: 'outgoing'
3987 });
3988};
3989exports.toIncomingSDPAnswer = function (session) {
3990 return toSDP.toSessionSDP(session, {
3991 role: 'initiator',
3992 direction: 'incoming'
3993 });
3994};
3995exports.toOutgoingSDPAnswer = function (session) {
3996 return toSDP.toSessionSDP(session, {
3997 role: 'responder',
3998 direction: 'outgoing'
3999 });
4000};
4001exports.toIncomingMediaSDPOffer = function (media) {
4002 return toSDP.toMediaSDP(media, {
4003 role: 'responder',
4004 direction: 'incoming'
4005 });
4006};
4007exports.toOutgoingMediaSDPOffer = function (media) {
4008 return toSDP.toMediaSDP(media, {
4009 role: 'initiator',
4010 direction: 'outgoing'
4011 });
4012};
4013exports.toIncomingMediaSDPAnswer = function (media) {
4014 return toSDP.toMediaSDP(media, {
4015 role: 'initiator',
4016 direction: 'incoming'
4017 });
4018};
4019exports.toOutgoingMediaSDPAnswer = function (media) {
4020 return toSDP.toMediaSDP(media, {
4021 role: 'responder',
4022 direction: 'outgoing'
4023 });
4024};
4025exports.toCandidateSDP = toSDP.toCandidateSDP;
4026exports.toMediaSDP = toSDP.toMediaSDP;
4027exports.toSessionSDP = toSDP.toSessionSDP;
4028
4029
4030// Conversion from SDP to JSON
4031
4032exports.toIncomingJSONOffer = function (sdp, creators) {
4033 return toJSON.toSessionJSON(sdp, {
4034 role: 'responder',
4035 direction: 'incoming',
4036 creators: creators
4037 });
4038};
4039exports.toOutgoingJSONOffer = function (sdp, creators) {
4040 return toJSON.toSessionJSON(sdp, {
4041 role: 'initiator',
4042 direction: 'outgoing',
4043 creators: creators
4044 });
4045};
4046exports.toIncomingJSONAnswer = function (sdp, creators) {
4047 return toJSON.toSessionJSON(sdp, {
4048 role: 'initiator',
4049 direction: 'incoming',
4050 creators: creators
4051 });
4052};
4053exports.toOutgoingJSONAnswer = function (sdp, creators) {
4054 return toJSON.toSessionJSON(sdp, {
4055 role: 'responder',
4056 direction: 'outgoing',
4057 creators: creators
4058 });
4059};
4060exports.toIncomingMediaJSONOffer = function (sdp, creator) {
4061 return toJSON.toMediaJSON(sdp, {
4062 role: 'responder',
4063 direction: 'incoming',
4064 creator: creator
4065 });
4066};
4067exports.toOutgoingMediaJSONOffer = function (sdp, creator) {
4068 return toJSON.toMediaJSON(sdp, {
4069 role: 'initiator',
4070 direction: 'outgoing',
4071 creator: creator
4072 });
4073};
4074exports.toIncomingMediaJSONAnswer = function (sdp, creator) {
4075 return toJSON.toMediaJSON(sdp, {
4076 role: 'initiator',
4077 direction: 'incoming',
4078 creator: creator
4079 });
4080};
4081exports.toOutgoingMediaJSONAnswer = function (sdp, creator) {
4082 return toJSON.toMediaJSON(sdp, {
4083 role: 'responder',
4084 direction: 'outgoing',
4085 creator: creator
4086 });
4087};
4088exports.toCandidateJSON = toJSON.toCandidateJSON;
4089exports.toMediaJSON = toJSON.toMediaJSON;
4090exports.toSessionJSON = toJSON.toSessionJSON;
4091
4092},{"./lib/tojson":19,"./lib/tosdp":20}],14:[function(require,module,exports){
4093// getScreenMedia helper by @HenrikJoreteg
4094var getUserMedia = require('getusermedia');
4095
4096// cache for constraints and callback
4097var cache = {};
4098
4099module.exports = function (constraints, cb) {
4100 var hasConstraints = arguments.length === 2;
4101 var callback = hasConstraints ? cb : constraints;
4102 var error;
4103
4104 if (typeof window === 'undefined' || window.location.protocol === 'http:') {
4105 error = new Error('NavigatorUserMediaError');
4106 error.name = 'HTTPS_REQUIRED';
4107 return callback(error);
4108 }
4109
4110 if (window.navigator.userAgent.match('Chrome')) {
4111 var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
4112 var maxver = 33;
4113 var isCef = !window.chrome.webstore;
4114 // "known" crash in chrome 34 and 35 on linux
4115 if (window.navigator.userAgent.match('Linux')) maxver = 35;
4116 if (isCef || (chromever >= 26 && chromever <= maxver)) {
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
4119 constraints = (hasConstraints && constraints) || {
4120 video: {
4121 mandatory: {
4122 googLeakyBucket: true,
4123 maxWidth: window.screen.width,
4124 maxHeight: window.screen.height,
4125 maxFrameRate: 3,
4126 chromeMediaSource: 'screen'
4127 }
4128 }
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'
4148 }
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);
4165 }
4166 });
4167 } else {
4168 error = new Error('NavigatorUserMediaError');
4169 error.name = 'EXTENSION_UNAVAILABLE'; // does not make much sense but...
4170 }
4171 }
4172};
4173
4174window.addEventListener('message', function (event) {
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];
4183
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
4195 },
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);
4206 }
4207});
4208
4209},{"getusermedia":12}],13:[function(require,module,exports){
4210var WildEmitter = require('wildemitter');
4211
4212function getMaxVolume (analyser, fftBins) {
4213 var maxVolume = -Infinity;
4214 analyser.getFloatFrequencyData(fftBins);
4215
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 };
4221
4222 return maxVolume;
4223}
4224
4225
4226var audioContextType = window.AudioContext || window.webkitAudioContext;
4227// use a single audio context due to hardware limits
4228var audioContext = null;
4229module.exports = function(stream, options) {
4230 var harker = new WildEmitter();
4231
4232
4233 // make it not break in non-supported browsers
4234 if (!audioContextType) return harker;
4235
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;
4244
4245 //Setup Audio Context
4246 if (!audioContext) {
4247 audioContext = new audioContextType();
4248 }
4249 var sourceNode, fftBins, analyser;
4250
4251 analyser = audioContext.createAnalyser();
4252 analyser.fftSize = 512;
4253 analyser.smoothingTimeConstant = smoothing;
4254 fftBins = new Float32Array(analyser.fftSize);
4255
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 }
4267
4268 sourceNode.connect(analyser);
4269 if (play) analyser.connect(audioContext.destination);
4270
4271 harker.speaking = false;
4272
4273 harker.setThreshold = function(t) {
4274 threshold = t;
4275 };
4276
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');
4287 }
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);
4305
4306 harker.emit('volume_change', currentVolume, threshold);
4307
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));
4329
4330 looper();
4331 }, interval);
4332 };
4333 looper();
4334
4335
4336 return harker;
4337}
4338
4339},{"wildemitter":4}],15:[function(require,module,exports){
4340var support = require('webrtcsupport');
4341
4342
4343function 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
4364GainController.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
4371GainController.prototype.getGain = function () {
4372 return this.gain;
4373};
4374
4375GainController.prototype.off = function () {
4376 return this.setGain(0);
4377};
4378
4379GainController.prototype.on = function () {
4380 this.setGain(1);
4381};
4382
4383
4384module.exports = GainController;
4385
4386},{"webrtcsupport":5}],20:[function(require,module,exports){
4387var SENDERS = require('./senders');
4388
4389
4390exports.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
4396 var sdp = [
4397 'v=0',
4398 'o=- ' + sid + ' ' + time + ' IN IP4 0.0.0.0',
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) {
4410 sdp.push(exports.toMediaSDP(content, opts));
4411 });
4412
4413 return sdp.join('\r\n') + '\r\n';
4414};
4415
4416exports.toMediaSDP = function (content, opts) {
4417 var sdp = [];
4418
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') {
4485 sdp.push('a=' + (SENDERS[role][direction][content.senders] || 'sendrecv'));
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 });
4497 if (desc.googConferenceFlag) {
4498 sdp.push('a=x-google-flag:conference');
4499 }
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];
4510 var parameters = [];
4511 payload.parameters.forEach(function (param) {
4512 parameters.push((param.key ? param.key + '=' : '') + param.value);
4513 });
4514 fmtp.push(parameters.join(';'));
4515 sdp.push(fmtp.join(' '));
4516 }
4517
4518 if (payload.feedback) {
4519 payload.feedback.forEach(function (fb) {
4520 if (fb.type === 'trr-int') {
4521 sdp.push('a=rtcp-fb:' + payload.id + ' trr-int ' + (fb.value ? fb.value : '0'));
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') {
4532 sdp.push('a=rtcp-fb:* trr-int ' + (fb.value ? fb.value : '0'));
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
4565exports.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);
4589 }
4590
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};
4601
4602},{"./senders":21}],19:[function(require,module,exports){
4603var SENDERS = require('./senders');
4604var parsers = require('./parsers');
4605var idCounter = Math.random();
4606
4607
4608exports._setIdCounter = function (counter) {
4609 idCounter = counter;
4610};
4611
4612exports.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=');
4621 for (i = 1; i < media.length; i++) {
4622 media[i] = 'm=' + media[i];
4623 if (i !== media.length - 1) {
4624 media[i] += '\r\n';
4625 }
4626 }
4627 var session = media.shift() + '\r\n';
4628 var sessionLines = parsers.lines(session);
4629 var parsed = {};
4630
4631 var contents = [];
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 }
4639 parsed.contents = contents;
4640
4641 var groupLines = parsers.findLines('a=group:', sessionLines);
4642 if (groupLines.length) {
4643 parsed.groups = parsers.groups(groupLines);
4644 }
4645
4646 return parsed;
4647};
4648
4649exports.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]);
4657
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: [],
4672 fingerprints: []
4673 }
4674 };
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 }
4683 var desc = content.description;
4684 var trans = content.transport;
4685
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 }
4691
4692 if (parsers.findLine('a=sendrecv', lines, sessionLines)) {
4693 content.senders = 'both';
4694 } else if (parsers.findLine('a=sendonly', lines, sessionLines)) {
4695 content.senders = SENDERS[role][direction].sendonly;
4696 } else if (parsers.findLine('a=recvonly', lines, sessionLines)) {
4697 content.senders = SENDERS[role][direction].recvonly;
4698 } else if (parsers.findLine('a=inactive', lines, sessionLines)) {
4699 content.senders = 'none';
4700 }
4701
4702 if (desc.descType == 'rtp') {
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 }
4712
4713 var rtpmapLines = parsers.findLines('a=rtpmap:', lines);
4714 rtpmapLines.forEach(function (line) {
4715 var payload = parsers.rtpmap(line);
4716 payload.parameters = [];
4717 payload.feedback = [];
4718
4719 var fmtpLines = parsers.findLines('a=fmtp:' + payload.id, lines);
4720 // There should only be one fmtp line per payload
4721 fmtpLines.forEach(function (line) {
4722 payload.parameters = parsers.fmtp(line);
4723 });
4724
4725 var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines);
4726 fbLines.forEach(function (line) {
4727 payload.feedback.push(parsers.rtcpfb(line));
4728 });
4729
4730 desc.payloads.push(payload);
4731 });
4732
4733 var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines);
4734 cryptoLines.forEach(function (line) {
4735 desc.encryption.push(parsers.crypto(line));
4736 });
4737
4738 if (parsers.findLine('a=rtcp-mux', lines)) {
4739 desc.mux = true;
4740 }
4741
4742 var fbLines = parsers.findLines('a=rtcp-fb:*', lines);
4743 fbLines.forEach(function (line) {
4744 desc.feedback.push(parsers.rtcpfb(line));
4745 });
4746
4747 var extLines = parsers.findLines('a=extmap:', lines);
4748 extLines.forEach(function (line) {
4749 var ext = parsers.extmap(line);
4750
4751 ext.senders = SENDERS[role][direction][ext.senders];
4752
4753 desc.headerExtensions.push(ext);
4754 });
4755
4756 var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines);
4757 desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []);
4758
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 }
4765 }
4766
4767 // transport specific attributes
4768 var fingerprintLines = parsers.findLines('a=fingerprint:', lines, sessionLines);
4769 var setup = parsers.findLine('a=setup:', lines, sessionLines);
4770 fingerprintLines.forEach(function (line) {
4771 var fp = parsers.fingerprint(line);
4772 if (setup) {
4773 fp.setup = setup.substr(8);
4774 }
4775 trans.fingerprints.push(fp);
4776 });
4777
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 });
4797 }
4798
4799 return content;
4800};
4801
4802exports.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};
4807
4808},{"./parsers":22,"./senders":21}],22:[function(require,module,exports){
4809exports.lines = function (sdp) {
4810 return sdp.split('\r\n').filter(function (line) {
4811 return line.length > 0;
4812 });
4813};
4814
4815exports.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 }
4826
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;
4834};
4835
4836exports.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]);
4842 }
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;
4853};
4854
4855exports.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]);
4866 }
4867 }
4868 return parsed;
4869};
4870
4871exports.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;
4883};
4884
4885exports.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
4897exports.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;
4912};
4913
4914exports.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;
4923};
4924
4925exports.fingerprint = function (line) {
4926 var parts = line.substr(14).split(' ');
4927 return {
4928 hash: parts[0],
4929 value: parts[1]
4930 };
4931};
4932
4933exports.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);
4942 } else {
4943 parsed.id = idpart;
4944 parsed.senders = 'sendrecv';
4945 }
4946
4947 parsed.uri = parts.shift() || '';
4948
4949 return parsed;
4950};
4951
4952exports.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;
4964};
4965
4966exports.candidate = function (line) {
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];
4993 } else if (parts[i] === 'tcptype') {
4994 candidate.tcpType = parts[i + 1];
4995 }
4996 }
4997
4998 candidate.network = '1';
4999
5000 return candidate;
5001};
5002
5003exports.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 }
5012 return parsed;
5013};
5014
5015exports.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();
5022
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;
5032 }
5033
5034 parts = parts.join(' ').split(':');
5035 var attribute = parts.shift();
5036 var value = parts.join(':') || null;
5037
5038 sources[ssrc].parameters.push({
5039 key: attribute,
5040 value: value
5041 });
5042 }
5043
5044 return parsed;
5045};
5046
5047exports.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 });
5057 }
5058 return parsed;
5059};
5060
5061exports.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){
5070module.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
5120var util = require('util');
5121var webrtc = require('webrtcsupport');
5122var WildEmitter = require('wildemitter');
5123
5124function dumpSDP(description) {
5125 return {
5126 type: description.type,
5127 sdp: description.sdp
5128 };
5129}
5130
5131function 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
5148function 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
5215util.inherits(TraceablePeerConnection, WildEmitter);
5216
5217Object.defineProperty(TraceablePeerConnection.prototype, 'signalingState', {
5218 get: function () {
5219 return this.peerconnection.signalingState;
5220 }
5221});
5222
5223Object.defineProperty(TraceablePeerConnection.prototype, 'iceConnectionState', {
5224 get: function () {
5225 return this.peerconnection.iceConnectionState;
5226 }
5227});
5228
5229Object.defineProperty(TraceablePeerConnection.prototype, 'localDescription', {
5230 get: function () {
5231 return this.peerconnection.localDescription;
5232 }
5233});
5234
5235Object.defineProperty(TraceablePeerConnection.prototype, 'remoteDescription', {
5236 get: function () {
5237 return this.peerconnection.remoteDescription;
5238 }
5239});
5240
5241TraceablePeerConnection.prototype.addStream = function (stream) {
5242 this.trace('addStream', dumpStream(stream));
5243 this.peerconnection.addStream(stream);
5244};
5245
5246TraceablePeerConnection.prototype.removeStream = function (stream) {
5247 this.trace('removeStream', dumpStream(stream));
5248 this.peerconnection.removeStream(stream);
5249};
5250
5251TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
5252 this.trace('createDataChannel', label, opts);
5253 return this.peerconnection.createDataChannel(label, opts);
5254};
5255
5256TraceablePeerConnection.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
5271TraceablePeerConnection.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
5286TraceablePeerConnection.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
5297TraceablePeerConnection.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
5313TraceablePeerConnection.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
5329TraceablePeerConnection.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
5344TraceablePeerConnection.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
5352module.exports = TraceablePeerConnection;
5353
5354},{"util":2,"webrtcsupport":5,"wildemitter":4}]},{},[1])(1)
5355});
5356;
\No newline at end of file