UNPKG

132 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 webrtc = require('webrtcsupport');
4var getUserMedia = require('getusermedia');
5var PeerConnection = require('rtcpeerconnection');
6var WildEmitter = require('wildemitter');
7var hark = require('hark');
8var GainController = require('mediastream-gain');
9var mockconsole = require('mockconsole');
10
11
12function WebRTC(opts) {
13 var self = this;
14 var options = opts || {};
15 var config = this.config = {
16 debug: false,
17 localVideoEl: '',
18 remoteVideosEl: '',
19 autoRequestMedia: false,
20 // makes the entire PC config overridable
21 peerConnectionConfig: {
22 iceServers: [{"url": "stun:stun.l.google.com:19302"}]
23 },
24 peerConnectionContraints: {
25 optional: [
26 {DtlsSrtpKeyAgreement: true}
27 ]
28 },
29 autoAdjustMic: false,
30 media: {
31 audio: true,
32 video: true
33 },
34 receiveMedia: {
35 mandatory: {
36 OfferToReceiveAudio: true,
37 OfferToReceiveVideo: true
38 }
39 },
40 detectSpeakingEvents: true,
41 enableDataChannels: true
42 };
43 var item, connection;
44
45 // expose screensharing check
46 this.screenSharingSupport = webrtc.screenSharing;
47
48 // We also allow a 'logger' option. It can be any object that implements
49 // log, warn, and error methods.
50 // We log nothing by default, following "the rule of silence":
51 // http://www.linfo.org/rule_of_silence.html
52 this.logger = function () {
53 // we assume that if you're in debug mode and you didn't
54 // pass in a logger, you actually want to log as much as
55 // possible.
56 if (opts.debug) {
57 return opts.logger || console;
58 } else {
59 // or we'll use your logger which should have its own logic
60 // for output. Or we'll return the no-op.
61 return opts.logger || mockconsole;
62 }
63 }();
64
65 // set options
66 for (item in options) {
67 this.config[item] = options[item];
68 }
69
70 // check for support
71 if (!webrtc.support) {
72 this.logger.error('Your browser doesn\'t seem to support WebRTC');
73 }
74
75 // where we'll store our peer connections
76 this.peers = [];
77
78 WildEmitter.call(this);
79
80 // log events in debug mode
81 if (this.config.debug) {
82 this.on('*', function (event, val1, val2) {
83 var logger;
84 // if you didn't pass in a logger and you explicitly turning on debug
85 // we're just going to assume you're wanting log output with console
86 if (self.config.logger === mockconsole) {
87 logger = console;
88 } else {
89 logger = self.logger;
90 }
91 logger.log('event:', event, val1, val2);
92 });
93 }
94}
95
96WebRTC.prototype = Object.create(WildEmitter.prototype, {
97 constructor: {
98 value: WebRTC
99 }
100});
101
102WebRTC.prototype.createPeer = function (opts) {
103 var peer;
104 opts.parent = this;
105 peer = new Peer(opts);
106 this.peers.push(peer);
107 return peer;
108};
109
110WebRTC.prototype.startLocalMedia = function (mediaConstraints, cb) {
111 var self = this;
112 var constraints = mediaConstraints || {video: true, audio: true};
113
114 getUserMedia(constraints, function (err, stream) {
115 if (!err) {
116 if (constraints.audio && self.config.detectSpeakingEvents) {
117 self.setupAudioMonitor(stream);
118 }
119 self.localStream = stream;
120
121 if (self.config.autoAdjustMic) {
122 self.gainController = new GainController(stream);
123 // start out somewhat muted if we can track audio
124 self.setMicIfEnabled(0.5);
125 }
126
127 self.emit('localStream', stream);
128 }
129 if (cb) cb(err, stream);
130 });
131};
132
133WebRTC.prototype.stopLocalMedia = function () {
134 if (this.localStream) {
135 this.localStream.stop();
136 this.emit('localStreamStopped');
137 }
138};
139
140// Audio controls
141WebRTC.prototype.mute = function () {
142 this._audioEnabled(false);
143 this.hardMuted = true;
144 this.emit('audioOff');
145};
146WebRTC.prototype.unmute = function () {
147 this._audioEnabled(true);
148 this.hardMuted = false;
149 this.emit('audioOn');
150};
151
152// Audio monitor
153WebRTC.prototype.setupAudioMonitor = function (stream) {
154 this.logger.log('Setup audio');
155 var audio = hark(stream);
156 var self = this;
157 var timeout;
158
159 audio.on('speaking', function () {
160 if (self.hardMuted) return;
161 self.setMicIfEnabled(1);
162 self.sendToAll('speaking', {});
163 self.emit('speaking');
164 });
165
166 audio.on('stopped_speaking', function () {
167 if (self.hardMuted) return;
168 if (timeout) clearTimeout(timeout);
169
170 timeout = setTimeout(function () {
171 self.setMicIfEnabled(0.5);
172 self.sendToAll('stopped_speaking', {});
173 self.emit('stoppedSpeaking');
174 }, 1000);
175 });
176 if (this.config.enableDataChannels) {
177 // until https://code.google.com/p/chromium/issues/detail?id=121673 is fixed...
178 audio.on('volume_change', function (volume, treshold) {
179 if (self.hardMuted) return;
180 self.emit('volumeChange', volume, treshold);
181 self.peers.forEach(function (peer) {
182 if (peer.enableDataChannels) {
183 var dc = peer.getDataChannel('hark');
184 if (dc.readyState != 'open') return;
185 dc.send(JSON.stringify({type: 'volume', volume: volume }));
186 }
187 });
188 });
189 }
190};
191
192// We do this as a seperate method in order to
193// still leave the "setMicVolume" as a working
194// method.
195WebRTC.prototype.setMicIfEnabled = function (volume) {
196 if (!this.config.autoAdjustMic) return;
197 this.gainController.setGain(volume);
198};
199
200// Video controls
201WebRTC.prototype.pauseVideo = function () {
202 this._videoEnabled(false);
203 this.emit('videoOff');
204};
205WebRTC.prototype.resumeVideo = function () {
206 this._videoEnabled(true);
207 this.emit('videoOn');
208};
209
210// Combined controls
211WebRTC.prototype.pause = function () {
212 this._audioEnabled(false);
213 this.pauseVideo();
214};
215WebRTC.prototype.resume = function () {
216 this._audioEnabled(true);
217 this.resumeVideo();
218};
219
220// Internal methods for enabling/disabling audio/video
221WebRTC.prototype._audioEnabled = function (bool) {
222 // work around for chrome 27 bug where disabling tracks
223 // doesn't seem to work (works in canary, remove when working)
224 this.setMicIfEnabled(bool ? 1 : 0);
225 this.localStream.getAudioTracks().forEach(function (track) {
226 track.enabled = !!bool;
227 });
228};
229WebRTC.prototype._videoEnabled = function (bool) {
230 this.localStream.getVideoTracks().forEach(function (track) {
231 track.enabled = !!bool;
232 });
233};
234
235// removes peers
236WebRTC.prototype.removePeers = function (id, type) {
237 this.getPeers(id, type).forEach(function (peer) {
238 peer.end();
239 });
240};
241
242// fetches all Peer objects by session id and/or type
243WebRTC.prototype.getPeers = function (sessionId, type) {
244 return this.peers.filter(function (peer) {
245 return (!sessionId || peer.id === sessionId) && (!type || peer.type === type);
246 });
247};
248
249// sends message to all
250WebRTC.prototype.sendToAll = function (message, payload) {
251 this.peers.forEach(function (peer) {
252 peer.send(message, payload);
253 });
254};
255
256// sends message to all using a datachannel
257WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) {
258 this.peers.forEach(function (peer) {
259 if (peer.enableDataChannels) {
260 peer.sendDirectly(channel, message, payload);
261 }
262 });
263};
264
265function Peer(options) {
266 var self = this;
267
268 this.id = options.id;
269 this.parent = options.parent;
270 this.type = options.type || 'video';
271 this.oneway = options.oneway || false;
272 this.sharemyscreen = options.sharemyscreen || false;
273 this.browserPrefix = options.prefix;
274 this.stream = options.stream;
275 this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels;
276 this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia;
277 this.channels = {};
278 // Create an RTCPeerConnection via the polyfill
279 this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionContraints);
280 this.pc.on('ice', this.onIceCandidate.bind(this));
281 this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this));
282 this.pc.on('addChannel', this.handleDataChannelAdded.bind(this));
283 this.pc.on('removeStream', this.handleStreamRemoved.bind(this));
284 // Just fire negotiation needed events for now
285 // When browser re-negotiation handling seems to work
286 // we can use this as the trigger for starting the offer/answer process
287 // automatically. We'll just leave it be for now while this stabalizes.
288 this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded'));
289 this.logger = this.parent.logger;
290
291 // handle screensharing/broadcast mode
292 if (options.type === 'screen') {
293 if (this.parent.localScreen && this.sharemyscreen) {
294 this.logger.log('adding local screen stream to peer connection');
295 this.pc.addStream(this.parent.localScreen);
296 this.broadcaster = options.broadcaster;
297 }
298 } else {
299 this.pc.addStream(this.parent.localStream);
300 }
301
302 // call emitter constructor
303 WildEmitter.call(this);
304
305 // proxy events to parent
306 this.on('*', function () {
307 self.parent.emit.apply(self.parent, arguments);
308 });
309}
310
311Peer.prototype = Object.create(WildEmitter.prototype, {
312 constructor: {
313 value: Peer
314 }
315});
316
317Peer.prototype.handleMessage = function (message) {
318 var self = this;
319
320 this.logger.log('getting', message.type, message);
321
322 if (message.prefix) this.browserPrefix = message.prefix;
323
324 if (message.type === 'offer') {
325 this.pc.handleOffer(message.payload, function (err) {
326 if (err) {
327 return;
328 }
329 // auto-accept
330 self.pc.answer(self.receiveMedia, function (err, sessionDescription) {
331 self.send('answer', sessionDescription);
332 });
333 });
334 } else if (message.type === 'answer') {
335 this.pc.handleAnswer(message.payload);
336 } else if (message.type === 'candidate') {
337 this.pc.processIce(message.payload);
338 } else if (message.type === 'speaking') {
339 this.parent.emit('speaking', {id: message.from});
340 } else if (message.type === 'stopped_speaking') {
341 this.parent.emit('stopped_speaking', {id: message.from});
342 } else if (message.type === 'mute') {
343 this.parent.emit('mute', {id: message.from, name: message.payload.name});
344 } else if (message.type === 'unmute') {
345 this.parent.emit('unmute', {id: message.from, name: message.payload.name});
346 }
347};
348
349// send via signalling channel
350Peer.prototype.send = function (messageType, payload) {
351 var message = {
352 to: this.id,
353 broadcaster: this.broadcaster,
354 roomType: this.type,
355 type: messageType,
356 payload: payload,
357 prefix: webrtc.prefix
358 };
359 this.logger.log('sending', messageType, message);
360 this.parent.emit('message', message);
361};
362
363// send via data channel
364Peer.prototype.sendDirectly = function (channel, messageType, payload) {
365 var message = {
366 type: messageType,
367 payload: payload
368 };
369 this.logger.log('sending via datachannel', channel, messageType, message);
370 this.getDataChannel(channel).send(JSON.stringify(message));
371};
372
373// Internal method registering handlers for a data channel and emitting events on the peer
374Peer.prototype._observeDataChannel = function (channel) {
375 var self = this;
376 channel.onclose = this.emit.bind(this, 'channelClose', channel);
377 channel.onerror = this.emit.bind(this, 'channelError', channel);
378 channel.onmessage = function (event) {
379 self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
380 };
381 channel.onopen = this.emit.bind(this, 'channelOpen', channel);
382};
383
384// Fetch or create a data channel by the given name
385Peer.prototype.getDataChannel = function (name, opts) {
386 if (!webrtc.dataChannel) return this.emit('error', new Error('createDataChannel not supported'));
387 var channel = this.channels[name];
388 opts || (opts = {});
389 if (channel) return channel;
390 // if we don't have one by this label, create it
391 channel = this.channels[name] = this.pc.createDataChannel(name, opts);
392 this._observeDataChannel(channel);
393 return channel;
394};
395
396Peer.prototype.onIceCandidate = function (candidate) {
397 if (this.closed) return;
398 if (candidate) {
399 this.send('candidate', candidate);
400 } else {
401 this.logger.log("End of candidates.");
402 }
403};
404
405Peer.prototype.start = function () {
406 var self = this;
407
408 // well, the webrtc api requires that we either
409 // a) create a datachannel a priori
410 // b) do a renegotiation later to add the SCTP m-line
411 // Let's do (a) first...
412 if (this.enableDataChannels) {
413 this.getDataChannel('simplewebrtc');
414 }
415
416 this.pc.offer(this.receiveMedia, function (err, sessionDescription) {
417 self.send('offer', sessionDescription);
418 });
419};
420
421Peer.prototype.end = function () {
422 if (this.closed) return;
423 this.pc.close();
424 this.handleStreamRemoved();
425};
426
427Peer.prototype.handleRemoteStreamAdded = function (event) {
428 var self = this;
429 if (this.stream) {
430 this.logger.warn('Already have a remote stream');
431 } else {
432 this.stream = event.stream;
433 this.stream.onended = function () {
434 self.end();
435 };
436 this.parent.emit('peerStreamAdded', this);
437 }
438};
439
440Peer.prototype.handleStreamRemoved = function () {
441 this.parent.peers.splice(this.parent.peers.indexOf(this), 1);
442 this.closed = true;
443 this.parent.emit('peerStreamRemoved', this);
444};
445
446Peer.prototype.handleDataChannelAdded = function (channel) {
447 this.channels[channel.label] = channel;
448 this._observeDataChannel(channel);
449};
450
451module.exports = WebRTC;
452
453},{"getusermedia":3,"hark":6,"mediastream-gain":7,"mockconsole":8,"rtcpeerconnection":4,"webrtcsupport":2,"wildemitter":5}],3:[function(require,module,exports){
454// getUserMedia helper by @HenrikJoreteg
455var func = (navigator.getUserMedia ||
456 navigator.webkitGetUserMedia ||
457 navigator.mozGetUserMedia ||
458 navigator.msGetUserMedia);
459
460
461module.exports = function (constraints, cb) {
462 var options;
463 var haveOpts = arguments.length === 2;
464 var defaultOpts = {video: true, audio: true};
465 var error;
466 var denied = 'PERMISSION_DENIED';
467 var notSatified = 'CONSTRAINT_NOT_SATISFIED';
468
469 // make constraints optional
470 if (!haveOpts) {
471 cb = constraints;
472 constraints = defaultOpts;
473 }
474
475 // treat lack of browser support like an error
476 if (!func) {
477 // throw proper error per spec
478 error = new Error('NavigatorUserMediaError');
479 error.name = 'NOT_SUPPORTED_ERROR';
480 return cb(error);
481 }
482
483 func.call(navigator, constraints, function (stream) {
484 cb(null, stream);
485 }, function (err) {
486 var error;
487 // coerce into an error object since FF gives us a string
488 // there are only two valid names according to the spec
489 // we coerce all non-denied to "constraint not satisfied".
490 if (typeof err === 'string') {
491 error = new Error('NavigatorUserMediaError');
492 if (err === denied) {
493 error.name = denied;
494 } else {
495 error.name = notSatified;
496 }
497 } else {
498 // if we get an error object make sure '.name' property is set
499 // according to spec: http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermediaerror-and-navigatorusermediaerrorcallback
500 error = err;
501 if (!error.name) {
502 // this is likely chrome which
503 // sets a property called "ERROR_DENIED" on the error object
504 // if so we make sure to set a name
505 if (error[denied]) {
506 err.name = denied;
507 } else {
508 err.name = notSatified;
509 }
510 }
511 }
512
513 cb(error);
514 });
515};
516
517},{}],2:[function(require,module,exports){
518// created by @HenrikJoreteg
519var prefix;
520var isChrome = false;
521var isFirefox = false;
522var ua = navigator.userAgent.toLowerCase();
523
524// basic sniffing
525if (ua.indexOf('firefox') !== -1) {
526 prefix = 'moz';
527 isFirefox = true;
528} else if (ua.indexOf('chrome') !== -1) {
529 prefix = 'webkit';
530 isChrome = true;
531}
532
533var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
534var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
535var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
536var MediaStream = window.webkitMediaStream || window.MediaStream;
537var screenSharing = navigator.userAgent.match('Chrome') && parseInt(navigator.userAgent.match(/Chrome\/(.*) /)[1], 10) >= 26;
538var AudioContext = window.webkitAudioContext || window.AudioContext;
539
540
541// export support flags and constructors.prototype && PC
542module.exports = {
543 support: !!PC,
544 dataChannel: isChrome || isFirefox || (PC && PC.prototype && PC.prototype.createDataChannel),
545 prefix: prefix,
546 webAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource),
547 mediaStream: !!(MediaStream && MediaStream.prototype.removeTrack),
548 screenSharing: !!screenSharing,
549 AudioContext: AudioContext,
550 PeerConnection: PC,
551 SessionDescription: SessionDescription,
552 IceCandidate: IceCandidate
553};
554
555},{}],5:[function(require,module,exports){
556/*
557WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
558on @visionmedia's Emitter from UI Kit.
559
560Why? I wanted it standalone.
561
562I also wanted support for wildcard emitters like this:
563
564emitter.on('*', function (eventName, other, event, payloads) {
565
566});
567
568emitter.on('somenamespace*', function (eventName, payloads) {
569
570});
571
572Please note that callbacks triggered by wildcard registered events also get
573the event name as the first argument.
574*/
575module.exports = WildEmitter;
576
577function WildEmitter() {
578 this.callbacks = {};
579}
580
581// Listen on the given `event` with `fn`. Store a group name if present.
582WildEmitter.prototype.on = function (event, groupName, fn) {
583 var hasGroup = (arguments.length === 3),
584 group = hasGroup ? arguments[1] : undefined,
585 func = hasGroup ? arguments[2] : arguments[1];
586 func._groupName = group;
587 (this.callbacks[event] = this.callbacks[event] || []).push(func);
588 return this;
589};
590
591// Adds an `event` listener that will be invoked a single
592// time then automatically removed.
593WildEmitter.prototype.once = function (event, groupName, fn) {
594 var self = this,
595 hasGroup = (arguments.length === 3),
596 group = hasGroup ? arguments[1] : undefined,
597 func = hasGroup ? arguments[2] : arguments[1];
598 function on() {
599 self.off(event, on);
600 func.apply(this, arguments);
601 }
602 this.on(event, group, on);
603 return this;
604};
605
606// Unbinds an entire group
607WildEmitter.prototype.releaseGroup = function (groupName) {
608 var item, i, len, handlers;
609 for (item in this.callbacks) {
610 handlers = this.callbacks[item];
611 for (i = 0, len = handlers.length; i < len; i++) {
612 if (handlers[i]._groupName === groupName) {
613 //console.log('removing');
614 // remove it and shorten the array we're looping through
615 handlers.splice(i, 1);
616 i--;
617 len--;
618 }
619 }
620 }
621 return this;
622};
623
624// Remove the given callback for `event` or all
625// registered callbacks.
626WildEmitter.prototype.off = function (event, fn) {
627 var callbacks = this.callbacks[event],
628 i;
629
630 if (!callbacks) return this;
631
632 // remove all handlers
633 if (arguments.length === 1) {
634 delete this.callbacks[event];
635 return this;
636 }
637
638 // remove specific handler
639 i = callbacks.indexOf(fn);
640 callbacks.splice(i, 1);
641 return this;
642};
643
644// Emit `event` with the given args.
645// also calls any `*` handlers
646WildEmitter.prototype.emit = function (event) {
647 var args = [].slice.call(arguments, 1),
648 callbacks = this.callbacks[event],
649 specialCallbacks = this.getWildcardCallbacks(event),
650 i,
651 len,
652 item;
653
654 if (callbacks) {
655 for (i = 0, len = callbacks.length; i < len; ++i) {
656 if (callbacks[i]) {
657 callbacks[i].apply(this, args);
658 } else {
659 break;
660 }
661 }
662 }
663
664 if (specialCallbacks) {
665 for (i = 0, len = specialCallbacks.length; i < len; ++i) {
666 if (specialCallbacks[i]) {
667 specialCallbacks[i].apply(this, [event].concat(args));
668 } else {
669 break;
670 }
671 }
672 }
673
674 return this;
675};
676
677// Helper for for finding special wildcard event handlers that match the event
678WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
679 var item,
680 split,
681 result = [];
682
683 for (item in this.callbacks) {
684 split = item.split('*');
685 if (item === '*' || (split.length === 2 && eventName.slice(0, split[1].length) === split[1])) {
686 result = result.concat(this.callbacks[item]);
687 }
688 }
689 return result;
690};
691
692},{}],8:[function(require,module,exports){
693var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(",");
694var l = methods.length;
695var fn = function () {};
696var mockconsole = {};
697
698while (l--) {
699 mockconsole[methods[l]] = fn;
700}
701
702module.exports = mockconsole;
703
704},{}],6:[function(require,module,exports){
705var WildEmitter = require('wildemitter');
706
707function getMaxVolume (analyser, fftBins) {
708 var maxVolume = -Infinity;
709 analyser.getFloatFrequencyData(fftBins);
710
711 for(var i=0, ii=fftBins.length; i < ii; i++) {
712 if (fftBins[i] > maxVolume && fftBins[i] < 0) {
713 maxVolume = fftBins[i];
714 }
715 };
716
717 return maxVolume;
718}
719
720
721var audioContextType = window.webkitAudioContext || window.AudioContext;
722// use a single audio context due to hardware limits
723var audioContext = null;
724module.exports = function(stream, options) {
725 var harker = new WildEmitter();
726
727
728 // make it not break in non-supported browsers
729 if (!audioContextType) return harker;
730
731 //Config
732 var options = options || {},
733 smoothing = (options.smoothing || 0.5),
734 interval = (options.interval || 100),
735 threshold = options.threshold,
736 play = options.play,
737 running = true;
738
739 //Setup Audio Context
740 if (!audioContext) {
741 audioContext = new audioContextType();
742 }
743 var sourceNode, fftBins, analyser;
744
745 analyser = audioContext.createAnalyser();
746 analyser.fftSize = 512;
747 analyser.smoothingTimeConstant = smoothing;
748 fftBins = new Float32Array(analyser.fftSize);
749
750 if (stream.jquery) stream = stream[0];
751 if (stream instanceof HTMLAudioElement) {
752 //Audio Tag
753 sourceNode = audioContext.createMediaElementSource(stream);
754 if (typeof play === 'undefined') play = true;
755 threshold = threshold || -65;
756 } else {
757 //WebRTC Stream
758 sourceNode = audioContext.createMediaStreamSource(stream);
759 threshold = threshold || -45;
760 }
761
762 sourceNode.connect(analyser);
763 if (play) analyser.connect(audioContext.destination);
764
765 harker.speaking = false;
766
767 harker.setThreshold = function(t) {
768 threshold = t;
769 };
770
771 harker.setInterval = function(i) {
772 interval = i;
773 };
774
775 harker.stop = function() {
776 running = false;
777 harker.emit('volume_change', -100, threshold);
778 if (harker.speaking) {
779 harker.speaking = false;
780 harker.emit('stopped_speaking');
781 }
782 };
783
784 // Poll the analyser node to determine if speaking
785 // and emit events if changed
786 var looper = function() {
787 setTimeout(function() {
788
789 //check if stop has been called
790 if(!running) {
791 return;
792 }
793
794 var currentVolume = getMaxVolume(analyser, fftBins);
795
796 harker.emit('volume_change', currentVolume, threshold);
797
798 if (currentVolume > threshold) {
799 if (!harker.speaking) {
800 harker.speaking = true;
801 harker.emit('speaking');
802 }
803 } else {
804 if (harker.speaking) {
805 harker.speaking = false;
806 harker.emit('stopped_speaking');
807 }
808 }
809
810 looper();
811 }, interval);
812 };
813 looper();
814
815
816 return harker;
817}
818
819},{"wildemitter":9}],10:[function(require,module,exports){
820var events = require('events');
821
822exports.isArray = isArray;
823exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'};
824exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'};
825
826
827exports.print = function () {};
828exports.puts = function () {};
829exports.debug = function() {};
830
831exports.inspect = function(obj, showHidden, depth, colors) {
832 var seen = [];
833
834 var stylize = function(str, styleType) {
835 // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
836 var styles =
837 { 'bold' : [1, 22],
838 'italic' : [3, 23],
839 'underline' : [4, 24],
840 'inverse' : [7, 27],
841 'white' : [37, 39],
842 'grey' : [90, 39],
843 'black' : [30, 39],
844 'blue' : [34, 39],
845 'cyan' : [36, 39],
846 'green' : [32, 39],
847 'magenta' : [35, 39],
848 'red' : [31, 39],
849 'yellow' : [33, 39] };
850
851 var style =
852 { 'special': 'cyan',
853 'number': 'blue',
854 'boolean': 'yellow',
855 'undefined': 'grey',
856 'null': 'bold',
857 'string': 'green',
858 'date': 'magenta',
859 // "name": intentionally not styling
860 'regexp': 'red' }[styleType];
861
862 if (style) {
863 return '\u001b[' + styles[style][0] + 'm' + str +
864 '\u001b[' + styles[style][1] + 'm';
865 } else {
866 return str;
867 }
868 };
869 if (! colors) {
870 stylize = function(str, styleType) { return str; };
871 }
872
873 function format(value, recurseTimes) {
874 // Provide a hook for user-specified inspect functions.
875 // Check that value is an object with an inspect function on it
876 if (value && typeof value.inspect === 'function' &&
877 // Filter out the util module, it's inspect function is special
878 value !== exports &&
879 // Also filter out any prototype objects using the circular check.
880 !(value.constructor && value.constructor.prototype === value)) {
881 return value.inspect(recurseTimes);
882 }
883
884 // Primitive types cannot have properties
885 switch (typeof value) {
886 case 'undefined':
887 return stylize('undefined', 'undefined');
888
889 case 'string':
890 var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
891 .replace(/'/g, "\\'")
892 .replace(/\\"/g, '"') + '\'';
893 return stylize(simple, 'string');
894
895 case 'number':
896 return stylize('' + value, 'number');
897
898 case 'boolean':
899 return stylize('' + value, 'boolean');
900 }
901 // For some reason typeof null is "object", so special case here.
902 if (value === null) {
903 return stylize('null', 'null');
904 }
905
906 // Look up the keys of the object.
907 var visible_keys = Object_keys(value);
908 var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys;
909
910 // Functions without properties can be shortcutted.
911 if (typeof value === 'function' && keys.length === 0) {
912 if (isRegExp(value)) {
913 return stylize('' + value, 'regexp');
914 } else {
915 var name = value.name ? ': ' + value.name : '';
916 return stylize('[Function' + name + ']', 'special');
917 }
918 }
919
920 // Dates without properties can be shortcutted
921 if (isDate(value) && keys.length === 0) {
922 return stylize(value.toUTCString(), 'date');
923 }
924
925 var base, type, braces;
926 // Determine the object type
927 if (isArray(value)) {
928 type = 'Array';
929 braces = ['[', ']'];
930 } else {
931 type = 'Object';
932 braces = ['{', '}'];
933 }
934
935 // Make functions say that they are functions
936 if (typeof value === 'function') {
937 var n = value.name ? ': ' + value.name : '';
938 base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
939 } else {
940 base = '';
941 }
942
943 // Make dates with properties first say the date
944 if (isDate(value)) {
945 base = ' ' + value.toUTCString();
946 }
947
948 if (keys.length === 0) {
949 return braces[0] + base + braces[1];
950 }
951
952 if (recurseTimes < 0) {
953 if (isRegExp(value)) {
954 return stylize('' + value, 'regexp');
955 } else {
956 return stylize('[Object]', 'special');
957 }
958 }
959
960 seen.push(value);
961
962 var output = keys.map(function(key) {
963 var name, str;
964 if (value.__lookupGetter__) {
965 if (value.__lookupGetter__(key)) {
966 if (value.__lookupSetter__(key)) {
967 str = stylize('[Getter/Setter]', 'special');
968 } else {
969 str = stylize('[Getter]', 'special');
970 }
971 } else {
972 if (value.__lookupSetter__(key)) {
973 str = stylize('[Setter]', 'special');
974 }
975 }
976 }
977 if (visible_keys.indexOf(key) < 0) {
978 name = '[' + key + ']';
979 }
980 if (!str) {
981 if (seen.indexOf(value[key]) < 0) {
982 if (recurseTimes === null) {
983 str = format(value[key]);
984 } else {
985 str = format(value[key], recurseTimes - 1);
986 }
987 if (str.indexOf('\n') > -1) {
988 if (isArray(value)) {
989 str = str.split('\n').map(function(line) {
990 return ' ' + line;
991 }).join('\n').substr(2);
992 } else {
993 str = '\n' + str.split('\n').map(function(line) {
994 return ' ' + line;
995 }).join('\n');
996 }
997 }
998 } else {
999 str = stylize('[Circular]', 'special');
1000 }
1001 }
1002 if (typeof name === 'undefined') {
1003 if (type === 'Array' && key.match(/^\d+$/)) {
1004 return str;
1005 }
1006 name = JSON.stringify('' + key);
1007 if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
1008 name = name.substr(1, name.length - 2);
1009 name = stylize(name, 'name');
1010 } else {
1011 name = name.replace(/'/g, "\\'")
1012 .replace(/\\"/g, '"')
1013 .replace(/(^"|"$)/g, "'");
1014 name = stylize(name, 'string');
1015 }
1016 }
1017
1018 return name + ': ' + str;
1019 });
1020
1021 seen.pop();
1022
1023 var numLinesEst = 0;
1024 var length = output.reduce(function(prev, cur) {
1025 numLinesEst++;
1026 if (cur.indexOf('\n') >= 0) numLinesEst++;
1027 return prev + cur.length + 1;
1028 }, 0);
1029
1030 if (length > 50) {
1031 output = braces[0] +
1032 (base === '' ? '' : base + '\n ') +
1033 ' ' +
1034 output.join(',\n ') +
1035 ' ' +
1036 braces[1];
1037
1038 } else {
1039 output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
1040 }
1041
1042 return output;
1043 }
1044 return format(obj, (typeof depth === 'undefined' ? 2 : depth));
1045};
1046
1047
1048function isArray(ar) {
1049 return Array.isArray(ar) ||
1050 (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]');
1051}
1052
1053
1054function isRegExp(re) {
1055 typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]';
1056}
1057
1058
1059function isDate(d) {
1060 return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]';
1061}
1062
1063function pad(n) {
1064 return n < 10 ? '0' + n.toString(10) : n.toString(10);
1065}
1066
1067var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
1068 'Oct', 'Nov', 'Dec'];
1069
1070// 26 Feb 16:19:34
1071function timestamp() {
1072 var d = new Date();
1073 var time = [pad(d.getHours()),
1074 pad(d.getMinutes()),
1075 pad(d.getSeconds())].join(':');
1076 return [d.getDate(), months[d.getMonth()], time].join(' ');
1077}
1078
1079exports.log = function (msg) {};
1080
1081exports.pump = null;
1082
1083var Object_keys = Object.keys || function (obj) {
1084 var res = [];
1085 for (var key in obj) res.push(key);
1086 return res;
1087};
1088
1089var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) {
1090 var res = [];
1091 for (var key in obj) {
1092 if (Object.hasOwnProperty.call(obj, key)) res.push(key);
1093 }
1094 return res;
1095};
1096
1097var Object_create = Object.create || function (prototype, properties) {
1098 // from es5-shim
1099 var object;
1100 if (prototype === null) {
1101 object = { '__proto__' : null };
1102 }
1103 else {
1104 if (typeof prototype !== 'object') {
1105 throw new TypeError(
1106 'typeof prototype[' + (typeof prototype) + '] != \'object\''
1107 );
1108 }
1109 var Type = function () {};
1110 Type.prototype = prototype;
1111 object = new Type();
1112 object.__proto__ = prototype;
1113 }
1114 if (typeof properties !== 'undefined' && Object.defineProperties) {
1115 Object.defineProperties(object, properties);
1116 }
1117 return object;
1118};
1119
1120exports.inherits = function(ctor, superCtor) {
1121 ctor.super_ = superCtor;
1122 ctor.prototype = Object_create(superCtor.prototype, {
1123 constructor: {
1124 value: ctor,
1125 enumerable: false,
1126 writable: true,
1127 configurable: true
1128 }
1129 });
1130};
1131
1132var formatRegExp = /%[sdj%]/g;
1133exports.format = function(f) {
1134 if (typeof f !== 'string') {
1135 var objects = [];
1136 for (var i = 0; i < arguments.length; i++) {
1137 objects.push(exports.inspect(arguments[i]));
1138 }
1139 return objects.join(' ');
1140 }
1141
1142 var i = 1;
1143 var args = arguments;
1144 var len = args.length;
1145 var str = String(f).replace(formatRegExp, function(x) {
1146 if (x === '%%') return '%';
1147 if (i >= len) return x;
1148 switch (x) {
1149 case '%s': return String(args[i++]);
1150 case '%d': return Number(args[i++]);
1151 case '%j': return JSON.stringify(args[i++]);
1152 default:
1153 return x;
1154 }
1155 });
1156 for(var x = args[i]; i < len; x = args[++i]){
1157 if (x === null || typeof x !== 'object') {
1158 str += ' ' + x;
1159 } else {
1160 str += ' ' + exports.inspect(x);
1161 }
1162 }
1163 return str;
1164};
1165
1166},{"events":11}],12:[function(require,module,exports){
1167// Underscore.js 1.6.0
1168// http://underscorejs.org
1169// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
1170// Underscore may be freely distributed under the MIT license.
1171
1172(function() {
1173
1174 // Baseline setup
1175 // --------------
1176
1177 // Establish the root object, `window` in the browser, or `exports` on the server.
1178 var root = this;
1179
1180 // Save the previous value of the `_` variable.
1181 var previousUnderscore = root._;
1182
1183 // Establish the object that gets returned to break out of a loop iteration.
1184 var breaker = {};
1185
1186 // Save bytes in the minified (but not gzipped) version:
1187 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
1188
1189 // Create quick reference variables for speed access to core prototypes.
1190 var
1191 push = ArrayProto.push,
1192 slice = ArrayProto.slice,
1193 concat = ArrayProto.concat,
1194 toString = ObjProto.toString,
1195 hasOwnProperty = ObjProto.hasOwnProperty;
1196
1197 // All **ECMAScript 5** native function implementations that we hope to use
1198 // are declared here.
1199 var
1200 nativeForEach = ArrayProto.forEach,
1201 nativeMap = ArrayProto.map,
1202 nativeReduce = ArrayProto.reduce,
1203 nativeReduceRight = ArrayProto.reduceRight,
1204 nativeFilter = ArrayProto.filter,
1205 nativeEvery = ArrayProto.every,
1206 nativeSome = ArrayProto.some,
1207 nativeIndexOf = ArrayProto.indexOf,
1208 nativeLastIndexOf = ArrayProto.lastIndexOf,
1209 nativeIsArray = Array.isArray,
1210 nativeKeys = Object.keys,
1211 nativeBind = FuncProto.bind;
1212
1213 // Create a safe reference to the Underscore object for use below.
1214 var _ = function(obj) {
1215 if (obj instanceof _) return obj;
1216 if (!(this instanceof _)) return new _(obj);
1217 this._wrapped = obj;
1218 };
1219
1220 // Export the Underscore object for **Node.js**, with
1221 // backwards-compatibility for the old `require()` API. If we're in
1222 // the browser, add `_` as a global object via a string identifier,
1223 // for Closure Compiler "advanced" mode.
1224 if (typeof exports !== 'undefined') {
1225 if (typeof module !== 'undefined' && module.exports) {
1226 exports = module.exports = _;
1227 }
1228 exports._ = _;
1229 } else {
1230 root._ = _;
1231 }
1232
1233 // Current version.
1234 _.VERSION = '1.6.0';
1235
1236 // Collection Functions
1237 // --------------------
1238
1239 // The cornerstone, an `each` implementation, aka `forEach`.
1240 // Handles objects with the built-in `forEach`, arrays, and raw objects.
1241 // Delegates to **ECMAScript 5**'s native `forEach` if available.
1242 var each = _.each = _.forEach = function(obj, iterator, context) {
1243 if (obj == null) return obj;
1244 if (nativeForEach && obj.forEach === nativeForEach) {
1245 obj.forEach(iterator, context);
1246 } else if (obj.length === +obj.length) {
1247 for (var i = 0, length = obj.length; i < length; i++) {
1248 if (iterator.call(context, obj[i], i, obj) === breaker) return;
1249 }
1250 } else {
1251 var keys = _.keys(obj);
1252 for (var i = 0, length = keys.length; i < length; i++) {
1253 if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
1254 }
1255 }
1256 return obj;
1257 };
1258
1259 // Return the results of applying the iterator to each element.
1260 // Delegates to **ECMAScript 5**'s native `map` if available.
1261 _.map = _.collect = function(obj, iterator, context) {
1262 var results = [];
1263 if (obj == null) return results;
1264 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
1265 each(obj, function(value, index, list) {
1266 results.push(iterator.call(context, value, index, list));
1267 });
1268 return results;
1269 };
1270
1271 var reduceError = 'Reduce of empty array with no initial value';
1272
1273 // **Reduce** builds up a single result from a list of values, aka `inject`,
1274 // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
1275 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
1276 var initial = arguments.length > 2;
1277 if (obj == null) obj = [];
1278 if (nativeReduce && obj.reduce === nativeReduce) {
1279 if (context) iterator = _.bind(iterator, context);
1280 return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
1281 }
1282 each(obj, function(value, index, list) {
1283 if (!initial) {
1284 memo = value;
1285 initial = true;
1286 } else {
1287 memo = iterator.call(context, memo, value, index, list);
1288 }
1289 });
1290 if (!initial) throw new TypeError(reduceError);
1291 return memo;
1292 };
1293
1294 // The right-associative version of reduce, also known as `foldr`.
1295 // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
1296 _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
1297 var initial = arguments.length > 2;
1298 if (obj == null) obj = [];
1299 if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
1300 if (context) iterator = _.bind(iterator, context);
1301 return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
1302 }
1303 var length = obj.length;
1304 if (length !== +length) {
1305 var keys = _.keys(obj);
1306 length = keys.length;
1307 }
1308 each(obj, function(value, index, list) {
1309 index = keys ? keys[--length] : --length;
1310 if (!initial) {
1311 memo = obj[index];
1312 initial = true;
1313 } else {
1314 memo = iterator.call(context, memo, obj[index], index, list);
1315 }
1316 });
1317 if (!initial) throw new TypeError(reduceError);
1318 return memo;
1319 };
1320
1321 // Return the first value which passes a truth test. Aliased as `detect`.
1322 _.find = _.detect = function(obj, predicate, context) {
1323 var result;
1324 any(obj, function(value, index, list) {
1325 if (predicate.call(context, value, index, list)) {
1326 result = value;
1327 return true;
1328 }
1329 });
1330 return result;
1331 };
1332
1333 // Return all the elements that pass a truth test.
1334 // Delegates to **ECMAScript 5**'s native `filter` if available.
1335 // Aliased as `select`.
1336 _.filter = _.select = function(obj, predicate, context) {
1337 var results = [];
1338 if (obj == null) return results;
1339 if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context);
1340 each(obj, function(value, index, list) {
1341 if (predicate.call(context, value, index, list)) results.push(value);
1342 });
1343 return results;
1344 };
1345
1346 // Return all the elements for which a truth test fails.
1347 _.reject = function(obj, predicate, context) {
1348 return _.filter(obj, function(value, index, list) {
1349 return !predicate.call(context, value, index, list);
1350 }, context);
1351 };
1352
1353 // Determine whether all of the elements match a truth test.
1354 // Delegates to **ECMAScript 5**'s native `every` if available.
1355 // Aliased as `all`.
1356 _.every = _.all = function(obj, predicate, context) {
1357 predicate || (predicate = _.identity);
1358 var result = true;
1359 if (obj == null) return result;
1360 if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context);
1361 each(obj, function(value, index, list) {
1362 if (!(result = result && predicate.call(context, value, index, list))) return breaker;
1363 });
1364 return !!result;
1365 };
1366
1367 // Determine if at least one element in the object matches a truth test.
1368 // Delegates to **ECMAScript 5**'s native `some` if available.
1369 // Aliased as `any`.
1370 var any = _.some = _.any = function(obj, predicate, context) {
1371 predicate || (predicate = _.identity);
1372 var result = false;
1373 if (obj == null) return result;
1374 if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context);
1375 each(obj, function(value, index, list) {
1376 if (result || (result = predicate.call(context, value, index, list))) return breaker;
1377 });
1378 return !!result;
1379 };
1380
1381 // Determine if the array or object contains a given value (using `===`).
1382 // Aliased as `include`.
1383 _.contains = _.include = function(obj, target) {
1384 if (obj == null) return false;
1385 if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
1386 return any(obj, function(value) {
1387 return value === target;
1388 });
1389 };
1390
1391 // Invoke a method (with arguments) on every item in a collection.
1392 _.invoke = function(obj, method) {
1393 var args = slice.call(arguments, 2);
1394 var isFunc = _.isFunction(method);
1395 return _.map(obj, function(value) {
1396 return (isFunc ? method : value[method]).apply(value, args);
1397 });
1398 };
1399
1400 // Convenience version of a common use case of `map`: fetching a property.
1401 _.pluck = function(obj, key) {
1402 return _.map(obj, _.property(key));
1403 };
1404
1405 // Convenience version of a common use case of `filter`: selecting only objects
1406 // containing specific `key:value` pairs.
1407 _.where = function(obj, attrs) {
1408 return _.filter(obj, _.matches(attrs));
1409 };
1410
1411 // Convenience version of a common use case of `find`: getting the first object
1412 // containing specific `key:value` pairs.
1413 _.findWhere = function(obj, attrs) {
1414 return _.find(obj, _.matches(attrs));
1415 };
1416
1417 // Return the maximum element or (element-based computation).
1418 // Can't optimize arrays of integers longer than 65,535 elements.
1419 // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
1420 _.max = function(obj, iterator, context) {
1421 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
1422 return Math.max.apply(Math, obj);
1423 }
1424 var result = -Infinity, lastComputed = -Infinity;
1425 each(obj, function(value, index, list) {
1426 var computed = iterator ? iterator.call(context, value, index, list) : value;
1427 if (computed > lastComputed) {
1428 result = value;
1429 lastComputed = computed;
1430 }
1431 });
1432 return result;
1433 };
1434
1435 // Return the minimum element (or element-based computation).
1436 _.min = function(obj, iterator, context) {
1437 if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
1438 return Math.min.apply(Math, obj);
1439 }
1440 var result = Infinity, lastComputed = Infinity;
1441 each(obj, function(value, index, list) {
1442 var computed = iterator ? iterator.call(context, value, index, list) : value;
1443 if (computed < lastComputed) {
1444 result = value;
1445 lastComputed = computed;
1446 }
1447 });
1448 return result;
1449 };
1450
1451 // Shuffle an array, using the modern version of the
1452 // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
1453 _.shuffle = function(obj) {
1454 var rand;
1455 var index = 0;
1456 var shuffled = [];
1457 each(obj, function(value) {
1458 rand = _.random(index++);
1459 shuffled[index - 1] = shuffled[rand];
1460 shuffled[rand] = value;
1461 });
1462 return shuffled;
1463 };
1464
1465 // Sample **n** random values from a collection.
1466 // If **n** is not specified, returns a single random element.
1467 // The internal `guard` argument allows it to work with `map`.
1468 _.sample = function(obj, n, guard) {
1469 if (n == null || guard) {
1470 if (obj.length !== +obj.length) obj = _.values(obj);
1471 return obj[_.random(obj.length - 1)];
1472 }
1473 return _.shuffle(obj).slice(0, Math.max(0, n));
1474 };
1475
1476 // An internal function to generate lookup iterators.
1477 var lookupIterator = function(value) {
1478 if (value == null) return _.identity;
1479 if (_.isFunction(value)) return value;
1480 return _.property(value);
1481 };
1482
1483 // Sort the object's values by a criterion produced by an iterator.
1484 _.sortBy = function(obj, iterator, context) {
1485 iterator = lookupIterator(iterator);
1486 return _.pluck(_.map(obj, function(value, index, list) {
1487 return {
1488 value: value,
1489 index: index,
1490 criteria: iterator.call(context, value, index, list)
1491 };
1492 }).sort(function(left, right) {
1493 var a = left.criteria;
1494 var b = right.criteria;
1495 if (a !== b) {
1496 if (a > b || a === void 0) return 1;
1497 if (a < b || b === void 0) return -1;
1498 }
1499 return left.index - right.index;
1500 }), 'value');
1501 };
1502
1503 // An internal function used for aggregate "group by" operations.
1504 var group = function(behavior) {
1505 return function(obj, iterator, context) {
1506 var result = {};
1507 iterator = lookupIterator(iterator);
1508 each(obj, function(value, index) {
1509 var key = iterator.call(context, value, index, obj);
1510 behavior(result, key, value);
1511 });
1512 return result;
1513 };
1514 };
1515
1516 // Groups the object's values by a criterion. Pass either a string attribute
1517 // to group by, or a function that returns the criterion.
1518 _.groupBy = group(function(result, key, value) {
1519 _.has(result, key) ? result[key].push(value) : result[key] = [value];
1520 });
1521
1522 // Indexes the object's values by a criterion, similar to `groupBy`, but for
1523 // when you know that your index values will be unique.
1524 _.indexBy = group(function(result, key, value) {
1525 result[key] = value;
1526 });
1527
1528 // Counts instances of an object that group by a certain criterion. Pass
1529 // either a string attribute to count by, or a function that returns the
1530 // criterion.
1531 _.countBy = group(function(result, key) {
1532 _.has(result, key) ? result[key]++ : result[key] = 1;
1533 });
1534
1535 // Use a comparator function to figure out the smallest index at which
1536 // an object should be inserted so as to maintain order. Uses binary search.
1537 _.sortedIndex = function(array, obj, iterator, context) {
1538 iterator = lookupIterator(iterator);
1539 var value = iterator.call(context, obj);
1540 var low = 0, high = array.length;
1541 while (low < high) {
1542 var mid = (low + high) >>> 1;
1543 iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
1544 }
1545 return low;
1546 };
1547
1548 // Safely create a real, live array from anything iterable.
1549 _.toArray = function(obj) {
1550 if (!obj) return [];
1551 if (_.isArray(obj)) return slice.call(obj);
1552 if (obj.length === +obj.length) return _.map(obj, _.identity);
1553 return _.values(obj);
1554 };
1555
1556 // Return the number of elements in an object.
1557 _.size = function(obj) {
1558 if (obj == null) return 0;
1559 return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
1560 };
1561
1562 // Array Functions
1563 // ---------------
1564
1565 // Get the first element of an array. Passing **n** will return the first N
1566 // values in the array. Aliased as `head` and `take`. The **guard** check
1567 // allows it to work with `_.map`.
1568 _.first = _.head = _.take = function(array, n, guard) {
1569 if (array == null) return void 0;
1570 if ((n == null) || guard) return array[0];
1571 if (n < 0) return [];
1572 return slice.call(array, 0, n);
1573 };
1574
1575 // Returns everything but the last entry of the array. Especially useful on
1576 // the arguments object. Passing **n** will return all the values in
1577 // the array, excluding the last N. The **guard** check allows it to work with
1578 // `_.map`.
1579 _.initial = function(array, n, guard) {
1580 return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
1581 };
1582
1583 // Get the last element of an array. Passing **n** will return the last N
1584 // values in the array. The **guard** check allows it to work with `_.map`.
1585 _.last = function(array, n, guard) {
1586 if (array == null) return void 0;
1587 if ((n == null) || guard) return array[array.length - 1];
1588 return slice.call(array, Math.max(array.length - n, 0));
1589 };
1590
1591 // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
1592 // Especially useful on the arguments object. Passing an **n** will return
1593 // the rest N values in the array. The **guard**
1594 // check allows it to work with `_.map`.
1595 _.rest = _.tail = _.drop = function(array, n, guard) {
1596 return slice.call(array, (n == null) || guard ? 1 : n);
1597 };
1598
1599 // Trim out all falsy values from an array.
1600 _.compact = function(array) {
1601 return _.filter(array, _.identity);
1602 };
1603
1604 // Internal implementation of a recursive `flatten` function.
1605 var flatten = function(input, shallow, output) {
1606 if (shallow && _.every(input, _.isArray)) {
1607 return concat.apply(output, input);
1608 }
1609 each(input, function(value) {
1610 if (_.isArray(value) || _.isArguments(value)) {
1611 shallow ? push.apply(output, value) : flatten(value, shallow, output);
1612 } else {
1613 output.push(value);
1614 }
1615 });
1616 return output;
1617 };
1618
1619 // Flatten out an array, either recursively (by default), or just one level.
1620 _.flatten = function(array, shallow) {
1621 return flatten(array, shallow, []);
1622 };
1623
1624 // Return a version of the array that does not contain the specified value(s).
1625 _.without = function(array) {
1626 return _.difference(array, slice.call(arguments, 1));
1627 };
1628
1629 // Split an array into two arrays: one whose elements all satisfy the given
1630 // predicate, and one whose elements all do not satisfy the predicate.
1631 _.partition = function(array, predicate) {
1632 var pass = [], fail = [];
1633 each(array, function(elem) {
1634 (predicate(elem) ? pass : fail).push(elem);
1635 });
1636 return [pass, fail];
1637 };
1638
1639 // Produce a duplicate-free version of the array. If the array has already
1640 // been sorted, you have the option of using a faster algorithm.
1641 // Aliased as `unique`.
1642 _.uniq = _.unique = function(array, isSorted, iterator, context) {
1643 if (_.isFunction(isSorted)) {
1644 context = iterator;
1645 iterator = isSorted;
1646 isSorted = false;
1647 }
1648 var initial = iterator ? _.map(array, iterator, context) : array;
1649 var results = [];
1650 var seen = [];
1651 each(initial, function(value, index) {
1652 if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
1653 seen.push(value);
1654 results.push(array[index]);
1655 }
1656 });
1657 return results;
1658 };
1659
1660 // Produce an array that contains the union: each distinct element from all of
1661 // the passed-in arrays.
1662 _.union = function() {
1663 return _.uniq(_.flatten(arguments, true));
1664 };
1665
1666 // Produce an array that contains every item shared between all the
1667 // passed-in arrays.
1668 _.intersection = function(array) {
1669 var rest = slice.call(arguments, 1);
1670 return _.filter(_.uniq(array), function(item) {
1671 return _.every(rest, function(other) {
1672 return _.contains(other, item);
1673 });
1674 });
1675 };
1676
1677 // Take the difference between one array and a number of other arrays.
1678 // Only the elements present in just the first array will remain.
1679 _.difference = function(array) {
1680 var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
1681 return _.filter(array, function(value){ return !_.contains(rest, value); });
1682 };
1683
1684 // Zip together multiple lists into a single array -- elements that share
1685 // an index go together.
1686 _.zip = function() {
1687 var length = _.max(_.pluck(arguments, 'length').concat(0));
1688 var results = new Array(length);
1689 for (var i = 0; i < length; i++) {
1690 results[i] = _.pluck(arguments, '' + i);
1691 }
1692 return results;
1693 };
1694
1695 // Converts lists into objects. Pass either a single array of `[key, value]`
1696 // pairs, or two parallel arrays of the same length -- one of keys, and one of
1697 // the corresponding values.
1698 _.object = function(list, values) {
1699 if (list == null) return {};
1700 var result = {};
1701 for (var i = 0, length = list.length; i < length; i++) {
1702 if (values) {
1703 result[list[i]] = values[i];
1704 } else {
1705 result[list[i][0]] = list[i][1];
1706 }
1707 }
1708 return result;
1709 };
1710
1711 // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
1712 // we need this function. Return the position of the first occurrence of an
1713 // item in an array, or -1 if the item is not included in the array.
1714 // Delegates to **ECMAScript 5**'s native `indexOf` if available.
1715 // If the array is large and already in sort order, pass `true`
1716 // for **isSorted** to use binary search.
1717 _.indexOf = function(array, item, isSorted) {
1718 if (array == null) return -1;
1719 var i = 0, length = array.length;
1720 if (isSorted) {
1721 if (typeof isSorted == 'number') {
1722 i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
1723 } else {
1724 i = _.sortedIndex(array, item);
1725 return array[i] === item ? i : -1;
1726 }
1727 }
1728 if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
1729 for (; i < length; i++) if (array[i] === item) return i;
1730 return -1;
1731 };
1732
1733 // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
1734 _.lastIndexOf = function(array, item, from) {
1735 if (array == null) return -1;
1736 var hasIndex = from != null;
1737 if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
1738 return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
1739 }
1740 var i = (hasIndex ? from : array.length);
1741 while (i--) if (array[i] === item) return i;
1742 return -1;
1743 };
1744
1745 // Generate an integer Array containing an arithmetic progression. A port of
1746 // the native Python `range()` function. See
1747 // [the Python documentation](http://docs.python.org/library/functions.html#range).
1748 _.range = function(start, stop, step) {
1749 if (arguments.length <= 1) {
1750 stop = start || 0;
1751 start = 0;
1752 }
1753 step = arguments[2] || 1;
1754
1755 var length = Math.max(Math.ceil((stop - start) / step), 0);
1756 var idx = 0;
1757 var range = new Array(length);
1758
1759 while(idx < length) {
1760 range[idx++] = start;
1761 start += step;
1762 }
1763
1764 return range;
1765 };
1766
1767 // Function (ahem) Functions
1768 // ------------------
1769
1770 // Reusable constructor function for prototype setting.
1771 var ctor = function(){};
1772
1773 // Create a function bound to a given object (assigning `this`, and arguments,
1774 // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
1775 // available.
1776 _.bind = function(func, context) {
1777 var args, bound;
1778 if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
1779 if (!_.isFunction(func)) throw new TypeError;
1780 args = slice.call(arguments, 2);
1781 return bound = function() {
1782 if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
1783 ctor.prototype = func.prototype;
1784 var self = new ctor;
1785 ctor.prototype = null;
1786 var result = func.apply(self, args.concat(slice.call(arguments)));
1787 if (Object(result) === result) return result;
1788 return self;
1789 };
1790 };
1791
1792 // Partially apply a function by creating a version that has had some of its
1793 // arguments pre-filled, without changing its dynamic `this` context. _ acts
1794 // as a placeholder, allowing any combination of arguments to be pre-filled.
1795 _.partial = function(func) {
1796 var boundArgs = slice.call(arguments, 1);
1797 return function() {
1798 var position = 0;
1799 var args = boundArgs.slice();
1800 for (var i = 0, length = args.length; i < length; i++) {
1801 if (args[i] === _) args[i] = arguments[position++];
1802 }
1803 while (position < arguments.length) args.push(arguments[position++]);
1804 return func.apply(this, args);
1805 };
1806 };
1807
1808 // Bind a number of an object's methods to that object. Remaining arguments
1809 // are the method names to be bound. Useful for ensuring that all callbacks
1810 // defined on an object belong to it.
1811 _.bindAll = function(obj) {
1812 var funcs = slice.call(arguments, 1);
1813 if (funcs.length === 0) throw new Error('bindAll must be passed function names');
1814 each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
1815 return obj;
1816 };
1817
1818 // Memoize an expensive function by storing its results.
1819 _.memoize = function(func, hasher) {
1820 var memo = {};
1821 hasher || (hasher = _.identity);
1822 return function() {
1823 var key = hasher.apply(this, arguments);
1824 return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
1825 };
1826 };
1827
1828 // Delays a function for the given number of milliseconds, and then calls
1829 // it with the arguments supplied.
1830 _.delay = function(func, wait) {
1831 var args = slice.call(arguments, 2);
1832 return setTimeout(function(){ return func.apply(null, args); }, wait);
1833 };
1834
1835 // Defers a function, scheduling it to run after the current call stack has
1836 // cleared.
1837 _.defer = function(func) {
1838 return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
1839 };
1840
1841 // Returns a function, that, when invoked, will only be triggered at most once
1842 // during a given window of time. Normally, the throttled function will run
1843 // as much as it can, without ever going more than once per `wait` duration;
1844 // but if you'd like to disable the execution on the leading edge, pass
1845 // `{leading: false}`. To disable execution on the trailing edge, ditto.
1846 _.throttle = function(func, wait, options) {
1847 var context, args, result;
1848 var timeout = null;
1849 var previous = 0;
1850 options || (options = {});
1851 var later = function() {
1852 previous = options.leading === false ? 0 : _.now();
1853 timeout = null;
1854 result = func.apply(context, args);
1855 context = args = null;
1856 };
1857 return function() {
1858 var now = _.now();
1859 if (!previous && options.leading === false) previous = now;
1860 var remaining = wait - (now - previous);
1861 context = this;
1862 args = arguments;
1863 if (remaining <= 0) {
1864 clearTimeout(timeout);
1865 timeout = null;
1866 previous = now;
1867 result = func.apply(context, args);
1868 context = args = null;
1869 } else if (!timeout && options.trailing !== false) {
1870 timeout = setTimeout(later, remaining);
1871 }
1872 return result;
1873 };
1874 };
1875
1876 // Returns a function, that, as long as it continues to be invoked, will not
1877 // be triggered. The function will be called after it stops being called for
1878 // N milliseconds. If `immediate` is passed, trigger the function on the
1879 // leading edge, instead of the trailing.
1880 _.debounce = function(func, wait, immediate) {
1881 var timeout, args, context, timestamp, result;
1882
1883 var later = function() {
1884 var last = _.now() - timestamp;
1885 if (last < wait) {
1886 timeout = setTimeout(later, wait - last);
1887 } else {
1888 timeout = null;
1889 if (!immediate) {
1890 result = func.apply(context, args);
1891 context = args = null;
1892 }
1893 }
1894 };
1895
1896 return function() {
1897 context = this;
1898 args = arguments;
1899 timestamp = _.now();
1900 var callNow = immediate && !timeout;
1901 if (!timeout) {
1902 timeout = setTimeout(later, wait);
1903 }
1904 if (callNow) {
1905 result = func.apply(context, args);
1906 context = args = null;
1907 }
1908
1909 return result;
1910 };
1911 };
1912
1913 // Returns a function that will be executed at most one time, no matter how
1914 // often you call it. Useful for lazy initialization.
1915 _.once = function(func) {
1916 var ran = false, memo;
1917 return function() {
1918 if (ran) return memo;
1919 ran = true;
1920 memo = func.apply(this, arguments);
1921 func = null;
1922 return memo;
1923 };
1924 };
1925
1926 // Returns the first function passed as an argument to the second,
1927 // allowing you to adjust arguments, run code before and after, and
1928 // conditionally execute the original function.
1929 _.wrap = function(func, wrapper) {
1930 return _.partial(wrapper, func);
1931 };
1932
1933 // Returns a function that is the composition of a list of functions, each
1934 // consuming the return value of the function that follows.
1935 _.compose = function() {
1936 var funcs = arguments;
1937 return function() {
1938 var args = arguments;
1939 for (var i = funcs.length - 1; i >= 0; i--) {
1940 args = [funcs[i].apply(this, args)];
1941 }
1942 return args[0];
1943 };
1944 };
1945
1946 // Returns a function that will only be executed after being called N times.
1947 _.after = function(times, func) {
1948 return function() {
1949 if (--times < 1) {
1950 return func.apply(this, arguments);
1951 }
1952 };
1953 };
1954
1955 // Object Functions
1956 // ----------------
1957
1958 // Retrieve the names of an object's properties.
1959 // Delegates to **ECMAScript 5**'s native `Object.keys`
1960 _.keys = function(obj) {
1961 if (!_.isObject(obj)) return [];
1962 if (nativeKeys) return nativeKeys(obj);
1963 var keys = [];
1964 for (var key in obj) if (_.has(obj, key)) keys.push(key);
1965 return keys;
1966 };
1967
1968 // Retrieve the values of an object's properties.
1969 _.values = function(obj) {
1970 var keys = _.keys(obj);
1971 var length = keys.length;
1972 var values = new Array(length);
1973 for (var i = 0; i < length; i++) {
1974 values[i] = obj[keys[i]];
1975 }
1976 return values;
1977 };
1978
1979 // Convert an object into a list of `[key, value]` pairs.
1980 _.pairs = function(obj) {
1981 var keys = _.keys(obj);
1982 var length = keys.length;
1983 var pairs = new Array(length);
1984 for (var i = 0; i < length; i++) {
1985 pairs[i] = [keys[i], obj[keys[i]]];
1986 }
1987 return pairs;
1988 };
1989
1990 // Invert the keys and values of an object. The values must be serializable.
1991 _.invert = function(obj) {
1992 var result = {};
1993 var keys = _.keys(obj);
1994 for (var i = 0, length = keys.length; i < length; i++) {
1995 result[obj[keys[i]]] = keys[i];
1996 }
1997 return result;
1998 };
1999
2000 // Return a sorted list of the function names available on the object.
2001 // Aliased as `methods`
2002 _.functions = _.methods = function(obj) {
2003 var names = [];
2004 for (var key in obj) {
2005 if (_.isFunction(obj[key])) names.push(key);
2006 }
2007 return names.sort();
2008 };
2009
2010 // Extend a given object with all the properties in passed-in object(s).
2011 _.extend = function(obj) {
2012 each(slice.call(arguments, 1), function(source) {
2013 if (source) {
2014 for (var prop in source) {
2015 obj[prop] = source[prop];
2016 }
2017 }
2018 });
2019 return obj;
2020 };
2021
2022 // Return a copy of the object only containing the whitelisted properties.
2023 _.pick = function(obj) {
2024 var copy = {};
2025 var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
2026 each(keys, function(key) {
2027 if (key in obj) copy[key] = obj[key];
2028 });
2029 return copy;
2030 };
2031
2032 // Return a copy of the object without the blacklisted properties.
2033 _.omit = function(obj) {
2034 var copy = {};
2035 var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
2036 for (var key in obj) {
2037 if (!_.contains(keys, key)) copy[key] = obj[key];
2038 }
2039 return copy;
2040 };
2041
2042 // Fill in a given object with default properties.
2043 _.defaults = function(obj) {
2044 each(slice.call(arguments, 1), function(source) {
2045 if (source) {
2046 for (var prop in source) {
2047 if (obj[prop] === void 0) obj[prop] = source[prop];
2048 }
2049 }
2050 });
2051 return obj;
2052 };
2053
2054 // Create a (shallow-cloned) duplicate of an object.
2055 _.clone = function(obj) {
2056 if (!_.isObject(obj)) return obj;
2057 return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
2058 };
2059
2060 // Invokes interceptor with the obj, and then returns obj.
2061 // The primary purpose of this method is to "tap into" a method chain, in
2062 // order to perform operations on intermediate results within the chain.
2063 _.tap = function(obj, interceptor) {
2064 interceptor(obj);
2065 return obj;
2066 };
2067
2068 // Internal recursive comparison function for `isEqual`.
2069 var eq = function(a, b, aStack, bStack) {
2070 // Identical objects are equal. `0 === -0`, but they aren't identical.
2071 // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
2072 if (a === b) return a !== 0 || 1 / a == 1 / b;
2073 // A strict comparison is necessary because `null == undefined`.
2074 if (a == null || b == null) return a === b;
2075 // Unwrap any wrapped objects.
2076 if (a instanceof _) a = a._wrapped;
2077 if (b instanceof _) b = b._wrapped;
2078 // Compare `[[Class]]` names.
2079 var className = toString.call(a);
2080 if (className != toString.call(b)) return false;
2081 switch (className) {
2082 // Strings, numbers, dates, and booleans are compared by value.
2083 case '[object String]':
2084 // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
2085 // equivalent to `new String("5")`.
2086 return a == String(b);
2087 case '[object Number]':
2088 // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
2089 // other numeric values.
2090 return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
2091 case '[object Date]':
2092 case '[object Boolean]':
2093 // Coerce dates and booleans to numeric primitive values. Dates are compared by their
2094 // millisecond representations. Note that invalid dates with millisecond representations
2095 // of `NaN` are not equivalent.
2096 return +a == +b;
2097 // RegExps are compared by their source patterns and flags.
2098 case '[object RegExp]':
2099 return a.source == b.source &&
2100 a.global == b.global &&
2101 a.multiline == b.multiline &&
2102 a.ignoreCase == b.ignoreCase;
2103 }
2104 if (typeof a != 'object' || typeof b != 'object') return false;
2105 // Assume equality for cyclic structures. The algorithm for detecting cyclic
2106 // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
2107 var length = aStack.length;
2108 while (length--) {
2109 // Linear search. Performance is inversely proportional to the number of
2110 // unique nested structures.
2111 if (aStack[length] == a) return bStack[length] == b;
2112 }
2113 // Objects with different constructors are not equivalent, but `Object`s
2114 // from different frames are.
2115 var aCtor = a.constructor, bCtor = b.constructor;
2116 if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
2117 _.isFunction(bCtor) && (bCtor instanceof bCtor))
2118 && ('constructor' in a && 'constructor' in b)) {
2119 return false;
2120 }
2121 // Add the first object to the stack of traversed objects.
2122 aStack.push(a);
2123 bStack.push(b);
2124 var size = 0, result = true;
2125 // Recursively compare objects and arrays.
2126 if (className == '[object Array]') {
2127 // Compare array lengths to determine if a deep comparison is necessary.
2128 size = a.length;
2129 result = size == b.length;
2130 if (result) {
2131 // Deep compare the contents, ignoring non-numeric properties.
2132 while (size--) {
2133 if (!(result = eq(a[size], b[size], aStack, bStack))) break;
2134 }
2135 }
2136 } else {
2137 // Deep compare objects.
2138 for (var key in a) {
2139 if (_.has(a, key)) {
2140 // Count the expected number of properties.
2141 size++;
2142 // Deep compare each member.
2143 if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
2144 }
2145 }
2146 // Ensure that both objects contain the same number of properties.
2147 if (result) {
2148 for (key in b) {
2149 if (_.has(b, key) && !(size--)) break;
2150 }
2151 result = !size;
2152 }
2153 }
2154 // Remove the first object from the stack of traversed objects.
2155 aStack.pop();
2156 bStack.pop();
2157 return result;
2158 };
2159
2160 // Perform a deep comparison to check if two objects are equal.
2161 _.isEqual = function(a, b) {
2162 return eq(a, b, [], []);
2163 };
2164
2165 // Is a given array, string, or object empty?
2166 // An "empty" object has no enumerable own-properties.
2167 _.isEmpty = function(obj) {
2168 if (obj == null) return true;
2169 if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
2170 for (var key in obj) if (_.has(obj, key)) return false;
2171 return true;
2172 };
2173
2174 // Is a given value a DOM element?
2175 _.isElement = function(obj) {
2176 return !!(obj && obj.nodeType === 1);
2177 };
2178
2179 // Is a given value an array?
2180 // Delegates to ECMA5's native Array.isArray
2181 _.isArray = nativeIsArray || function(obj) {
2182 return toString.call(obj) == '[object Array]';
2183 };
2184
2185 // Is a given variable an object?
2186 _.isObject = function(obj) {
2187 return obj === Object(obj);
2188 };
2189
2190 // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
2191 each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
2192 _['is' + name] = function(obj) {
2193 return toString.call(obj) == '[object ' + name + ']';
2194 };
2195 });
2196
2197 // Define a fallback version of the method in browsers (ahem, IE), where
2198 // there isn't any inspectable "Arguments" type.
2199 if (!_.isArguments(arguments)) {
2200 _.isArguments = function(obj) {
2201 return !!(obj && _.has(obj, 'callee'));
2202 };
2203 }
2204
2205 // Optimize `isFunction` if appropriate.
2206 if (typeof (/./) !== 'function') {
2207 _.isFunction = function(obj) {
2208 return typeof obj === 'function';
2209 };
2210 }
2211
2212 // Is a given object a finite number?
2213 _.isFinite = function(obj) {
2214 return isFinite(obj) && !isNaN(parseFloat(obj));
2215 };
2216
2217 // Is the given value `NaN`? (NaN is the only number which does not equal itself).
2218 _.isNaN = function(obj) {
2219 return _.isNumber(obj) && obj != +obj;
2220 };
2221
2222 // Is a given value a boolean?
2223 _.isBoolean = function(obj) {
2224 return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
2225 };
2226
2227 // Is a given value equal to null?
2228 _.isNull = function(obj) {
2229 return obj === null;
2230 };
2231
2232 // Is a given variable undefined?
2233 _.isUndefined = function(obj) {
2234 return obj === void 0;
2235 };
2236
2237 // Shortcut function for checking if an object has a given property directly
2238 // on itself (in other words, not on a prototype).
2239 _.has = function(obj, key) {
2240 return hasOwnProperty.call(obj, key);
2241 };
2242
2243 // Utility Functions
2244 // -----------------
2245
2246 // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
2247 // previous owner. Returns a reference to the Underscore object.
2248 _.noConflict = function() {
2249 root._ = previousUnderscore;
2250 return this;
2251 };
2252
2253 // Keep the identity function around for default iterators.
2254 _.identity = function(value) {
2255 return value;
2256 };
2257
2258 _.constant = function(value) {
2259 return function () {
2260 return value;
2261 };
2262 };
2263
2264 _.property = function(key) {
2265 return function(obj) {
2266 return obj[key];
2267 };
2268 };
2269
2270 // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
2271 _.matches = function(attrs) {
2272 return function(obj) {
2273 if (obj === attrs) return true; //avoid comparing an object to itself.
2274 for (var key in attrs) {
2275 if (attrs[key] !== obj[key])
2276 return false;
2277 }
2278 return true;
2279 }
2280 };
2281
2282 // Run a function **n** times.
2283 _.times = function(n, iterator, context) {
2284 var accum = Array(Math.max(0, n));
2285 for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
2286 return accum;
2287 };
2288
2289 // Return a random integer between min and max (inclusive).
2290 _.random = function(min, max) {
2291 if (max == null) {
2292 max = min;
2293 min = 0;
2294 }
2295 return min + Math.floor(Math.random() * (max - min + 1));
2296 };
2297
2298 // A (possibly faster) way to get the current timestamp as an integer.
2299 _.now = Date.now || function() { return new Date().getTime(); };
2300
2301 // List of HTML entities for escaping.
2302 var entityMap = {
2303 escape: {
2304 '&': '&amp;',
2305 '<': '&lt;',
2306 '>': '&gt;',
2307 '"': '&quot;',
2308 "'": '&#x27;'
2309 }
2310 };
2311 entityMap.unescape = _.invert(entityMap.escape);
2312
2313 // Regexes containing the keys and values listed immediately above.
2314 var entityRegexes = {
2315 escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
2316 unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
2317 };
2318
2319 // Functions for escaping and unescaping strings to/from HTML interpolation.
2320 _.each(['escape', 'unescape'], function(method) {
2321 _[method] = function(string) {
2322 if (string == null) return '';
2323 return ('' + string).replace(entityRegexes[method], function(match) {
2324 return entityMap[method][match];
2325 });
2326 };
2327 });
2328
2329 // If the value of the named `property` is a function then invoke it with the
2330 // `object` as context; otherwise, return it.
2331 _.result = function(object, property) {
2332 if (object == null) return void 0;
2333 var value = object[property];
2334 return _.isFunction(value) ? value.call(object) : value;
2335 };
2336
2337 // Add your own custom functions to the Underscore object.
2338 _.mixin = function(obj) {
2339 each(_.functions(obj), function(name) {
2340 var func = _[name] = obj[name];
2341 _.prototype[name] = function() {
2342 var args = [this._wrapped];
2343 push.apply(args, arguments);
2344 return result.call(this, func.apply(_, args));
2345 };
2346 });
2347 };
2348
2349 // Generate a unique integer id (unique within the entire client session).
2350 // Useful for temporary DOM ids.
2351 var idCounter = 0;
2352 _.uniqueId = function(prefix) {
2353 var id = ++idCounter + '';
2354 return prefix ? prefix + id : id;
2355 };
2356
2357 // By default, Underscore uses ERB-style template delimiters, change the
2358 // following template settings to use alternative delimiters.
2359 _.templateSettings = {
2360 evaluate : /<%([\s\S]+?)%>/g,
2361 interpolate : /<%=([\s\S]+?)%>/g,
2362 escape : /<%-([\s\S]+?)%>/g
2363 };
2364
2365 // When customizing `templateSettings`, if you don't want to define an
2366 // interpolation, evaluation or escaping regex, we need one that is
2367 // guaranteed not to match.
2368 var noMatch = /(.)^/;
2369
2370 // Certain characters need to be escaped so that they can be put into a
2371 // string literal.
2372 var escapes = {
2373 "'": "'",
2374 '\\': '\\',
2375 '\r': 'r',
2376 '\n': 'n',
2377 '\t': 't',
2378 '\u2028': 'u2028',
2379 '\u2029': 'u2029'
2380 };
2381
2382 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
2383
2384 // JavaScript micro-templating, similar to John Resig's implementation.
2385 // Underscore templating handles arbitrary delimiters, preserves whitespace,
2386 // and correctly escapes quotes within interpolated code.
2387 _.template = function(text, data, settings) {
2388 var render;
2389 settings = _.defaults({}, settings, _.templateSettings);
2390
2391 // Combine delimiters into one regular expression via alternation.
2392 var matcher = new RegExp([
2393 (settings.escape || noMatch).source,
2394 (settings.interpolate || noMatch).source,
2395 (settings.evaluate || noMatch).source
2396 ].join('|') + '|$', 'g');
2397
2398 // Compile the template source, escaping string literals appropriately.
2399 var index = 0;
2400 var source = "__p+='";
2401 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
2402 source += text.slice(index, offset)
2403 .replace(escaper, function(match) { return '\\' + escapes[match]; });
2404
2405 if (escape) {
2406 source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
2407 }
2408 if (interpolate) {
2409 source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
2410 }
2411 if (evaluate) {
2412 source += "';\n" + evaluate + "\n__p+='";
2413 }
2414 index = offset + match.length;
2415 return match;
2416 });
2417 source += "';\n";
2418
2419 // If a variable is not specified, place data values in local scope.
2420 if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
2421
2422 source = "var __t,__p='',__j=Array.prototype.join," +
2423 "print=function(){__p+=__j.call(arguments,'');};\n" +
2424 source + "return __p;\n";
2425
2426 try {
2427 render = new Function(settings.variable || 'obj', '_', source);
2428 } catch (e) {
2429 e.source = source;
2430 throw e;
2431 }
2432
2433 if (data) return render(data, _);
2434 var template = function(data) {
2435 return render.call(this, data, _);
2436 };
2437
2438 // Provide the compiled function source as a convenience for precompilation.
2439 template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
2440
2441 return template;
2442 };
2443
2444 // Add a "chain" function, which will delegate to the wrapper.
2445 _.chain = function(obj) {
2446 return _(obj).chain();
2447 };
2448
2449 // OOP
2450 // ---------------
2451 // If Underscore is called as a function, it returns a wrapped object that
2452 // can be used OO-style. This wrapper holds altered versions of all the
2453 // underscore functions. Wrapped objects may be chained.
2454
2455 // Helper function to continue chaining intermediate results.
2456 var result = function(obj) {
2457 return this._chain ? _(obj).chain() : obj;
2458 };
2459
2460 // Add all of the Underscore functions to the wrapper object.
2461 _.mixin(_);
2462
2463 // Add all mutator Array functions to the wrapper.
2464 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
2465 var method = ArrayProto[name];
2466 _.prototype[name] = function() {
2467 var obj = this._wrapped;
2468 method.apply(obj, arguments);
2469 if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
2470 return result.call(this, obj);
2471 };
2472 });
2473
2474 // Add all accessor Array functions to the wrapper.
2475 each(['concat', 'join', 'slice'], function(name) {
2476 var method = ArrayProto[name];
2477 _.prototype[name] = function() {
2478 return result.call(this, method.apply(this._wrapped, arguments));
2479 };
2480 });
2481
2482 _.extend(_.prototype, {
2483
2484 // Start chaining a wrapped Underscore object.
2485 chain: function() {
2486 this._chain = true;
2487 return this;
2488 },
2489
2490 // Extracts the result from a wrapped and chained object.
2491 value: function() {
2492 return this._wrapped;
2493 }
2494
2495 });
2496
2497 // AMD registration happens at the end for compatibility with AMD loaders
2498 // that may not enforce next-turn semantics on modules. Even though general
2499 // practice for AMD registration is to be anonymous, underscore registers
2500 // as a named module because, like jQuery, it is a base library that is
2501 // popular enough to be bundled in a third party lib, but not be part of
2502 // an AMD load request. Those cases could generate an error when an
2503 // anonymous define() is called outside of a loader request.
2504 if (typeof define === 'function' && define.amd) {
2505 define('underscore', [], function() {
2506 return _;
2507 });
2508 }
2509}).call(this);
2510
2511},{}],13:[function(require,module,exports){
2512/*
2513WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
2514on @visionmedia's Emitter from UI Kit.
2515
2516Why? I wanted it standalone.
2517
2518I also wanted support for wildcard emitters like this:
2519
2520emitter.on('*', function (eventName, other, event, payloads) {
2521
2522});
2523
2524emitter.on('somenamespace*', function (eventName, payloads) {
2525
2526});
2527
2528Please note that callbacks triggered by wildcard registered events also get
2529the event name as the first argument.
2530*/
2531module.exports = WildEmitter;
2532
2533function WildEmitter() {
2534 this.callbacks = {};
2535}
2536
2537// Listen on the given `event` with `fn`. Store a group name if present.
2538WildEmitter.prototype.on = function (event, groupName, fn) {
2539 var hasGroup = (arguments.length === 3),
2540 group = hasGroup ? arguments[1] : undefined,
2541 func = hasGroup ? arguments[2] : arguments[1];
2542 func._groupName = group;
2543 (this.callbacks[event] = this.callbacks[event] || []).push(func);
2544 return this;
2545};
2546
2547// Adds an `event` listener that will be invoked a single
2548// time then automatically removed.
2549WildEmitter.prototype.once = function (event, groupName, fn) {
2550 var self = this,
2551 hasGroup = (arguments.length === 3),
2552 group = hasGroup ? arguments[1] : undefined,
2553 func = hasGroup ? arguments[2] : arguments[1];
2554 function on() {
2555 self.off(event, on);
2556 func.apply(this, arguments);
2557 }
2558 this.on(event, group, on);
2559 return this;
2560};
2561
2562// Unbinds an entire group
2563WildEmitter.prototype.releaseGroup = function (groupName) {
2564 var item, i, len, handlers;
2565 for (item in this.callbacks) {
2566 handlers = this.callbacks[item];
2567 for (i = 0, len = handlers.length; i < len; i++) {
2568 if (handlers[i]._groupName === groupName) {
2569 //console.log('removing');
2570 // remove it and shorten the array we're looping through
2571 handlers.splice(i, 1);
2572 i--;
2573 len--;
2574 }
2575 }
2576 }
2577 return this;
2578};
2579
2580// Remove the given callback for `event` or all
2581// registered callbacks.
2582WildEmitter.prototype.off = function (event, fn) {
2583 var callbacks = this.callbacks[event],
2584 i;
2585
2586 if (!callbacks) return this;
2587
2588 // remove all handlers
2589 if (arguments.length === 1) {
2590 delete this.callbacks[event];
2591 return this;
2592 }
2593
2594 // remove specific handler
2595 i = callbacks.indexOf(fn);
2596 callbacks.splice(i, 1);
2597 return this;
2598};
2599
2600/// Emit `event` with the given args.
2601// also calls any `*` handlers
2602WildEmitter.prototype.emit = function (event) {
2603 var args = [].slice.call(arguments, 1),
2604 callbacks = this.callbacks[event],
2605 specialCallbacks = this.getWildcardCallbacks(event),
2606 i,
2607 len,
2608 item,
2609 listeners;
2610
2611 if (callbacks) {
2612 listeners = callbacks.slice();
2613 for (i = 0, len = listeners.length; i < len; ++i) {
2614 if (listeners[i]) {
2615 listeners[i].apply(this, args);
2616 } else {
2617 break;
2618 }
2619 }
2620 }
2621
2622 if (specialCallbacks) {
2623 len = specialCallbacks.length;
2624 listeners = specialCallbacks.slice();
2625 for (i = 0, len = listeners.length; i < len; ++i) {
2626 if (listeners[i]) {
2627 listeners[i].apply(this, [event].concat(args));
2628 } else {
2629 break;
2630 }
2631 }
2632 }
2633
2634 return this;
2635};
2636
2637// Helper for for finding special wildcard event handlers that match the event
2638WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
2639 var item,
2640 split,
2641 result = [];
2642
2643 for (item in this.callbacks) {
2644 split = item.split('*');
2645 if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {
2646 result = result.concat(this.callbacks[item]);
2647 }
2648 }
2649 return result;
2650};
2651
2652},{}],9:[function(require,module,exports){
2653/*
2654WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
2655on @visionmedia's Emitter from UI Kit.
2656
2657Why? I wanted it standalone.
2658
2659I also wanted support for wildcard emitters like this:
2660
2661emitter.on('*', function (eventName, other, event, payloads) {
2662
2663});
2664
2665emitter.on('somenamespace*', function (eventName, payloads) {
2666
2667});
2668
2669Please note that callbacks triggered by wildcard registered events also get
2670the event name as the first argument.
2671*/
2672module.exports = WildEmitter;
2673
2674function WildEmitter() {
2675 this.callbacks = {};
2676}
2677
2678// Listen on the given `event` with `fn`. Store a group name if present.
2679WildEmitter.prototype.on = function (event, groupName, fn) {
2680 var hasGroup = (arguments.length === 3),
2681 group = hasGroup ? arguments[1] : undefined,
2682 func = hasGroup ? arguments[2] : arguments[1];
2683 func._groupName = group;
2684 (this.callbacks[event] = this.callbacks[event] || []).push(func);
2685 return this;
2686};
2687
2688// Adds an `event` listener that will be invoked a single
2689// time then automatically removed.
2690WildEmitter.prototype.once = function (event, groupName, fn) {
2691 var self = this,
2692 hasGroup = (arguments.length === 3),
2693 group = hasGroup ? arguments[1] : undefined,
2694 func = hasGroup ? arguments[2] : arguments[1];
2695 function on() {
2696 self.off(event, on);
2697 func.apply(this, arguments);
2698 }
2699 this.on(event, group, on);
2700 return this;
2701};
2702
2703// Unbinds an entire group
2704WildEmitter.prototype.releaseGroup = function (groupName) {
2705 var item, i, len, handlers;
2706 for (item in this.callbacks) {
2707 handlers = this.callbacks[item];
2708 for (i = 0, len = handlers.length; i < len; i++) {
2709 if (handlers[i]._groupName === groupName) {
2710 //console.log('removing');
2711 // remove it and shorten the array we're looping through
2712 handlers.splice(i, 1);
2713 i--;
2714 len--;
2715 }
2716 }
2717 }
2718 return this;
2719};
2720
2721// Remove the given callback for `event` or all
2722// registered callbacks.
2723WildEmitter.prototype.off = function (event, fn) {
2724 var callbacks = this.callbacks[event],
2725 i;
2726
2727 if (!callbacks) return this;
2728
2729 // remove all handlers
2730 if (arguments.length === 1) {
2731 delete this.callbacks[event];
2732 return this;
2733 }
2734
2735 // remove specific handler
2736 i = callbacks.indexOf(fn);
2737 callbacks.splice(i, 1);
2738 return this;
2739};
2740
2741// Emit `event` with the given args.
2742// also calls any `*` handlers
2743WildEmitter.prototype.emit = function (event) {
2744 var args = [].slice.call(arguments, 1),
2745 callbacks = this.callbacks[event],
2746 specialCallbacks = this.getWildcardCallbacks(event),
2747 i,
2748 len,
2749 item;
2750
2751 if (callbacks) {
2752 for (i = 0, len = callbacks.length; i < len; ++i) {
2753 if (callbacks[i]) {
2754 callbacks[i].apply(this, args);
2755 } else {
2756 break;
2757 }
2758 }
2759 }
2760
2761 if (specialCallbacks) {
2762 for (i = 0, len = specialCallbacks.length; i < len; ++i) {
2763 if (specialCallbacks[i]) {
2764 specialCallbacks[i].apply(this, [event].concat(args));
2765 } else {
2766 break;
2767 }
2768 }
2769 }
2770
2771 return this;
2772};
2773
2774// Helper for for finding special wildcard event handlers that match the event
2775WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
2776 var item,
2777 split,
2778 result = [];
2779
2780 for (item in this.callbacks) {
2781 split = item.split('*');
2782 if (item === '*' || (split.length === 2 && eventName.slice(0, split[1].length) === split[1])) {
2783 result = result.concat(this.callbacks[item]);
2784 }
2785 }
2786 return result;
2787};
2788
2789},{}],4:[function(require,module,exports){
2790var _ = require('underscore');
2791var util = require('util');
2792var webrtc = require('webrtcsupport');
2793var SJJ = require('sdp-jingle-json');
2794var WildEmitter = require('wildemitter');
2795var peerconn = require('traceablepeerconnection');
2796
2797function PeerConnection(config, constraints) {
2798 var self = this;
2799 var item;
2800 WildEmitter.call(this);
2801
2802 config = config || {};
2803 config.iceServers = config.iceServers || [];
2804
2805 this.pc = new peerconn(config, constraints);
2806 // proxy events
2807 this.pc.on('*', function () {
2808 self.emit.apply(self, arguments);
2809 });
2810
2811 // proxy some events directly
2812 this.pc.onremovestream = this.emit.bind(this, 'removeStream');
2813 this.pc.onnegotiationneeded = this.emit.bind(this, 'negotiationNeeded');
2814 this.pc.oniceconnectionstatechange = this.emit.bind(this, 'iceConnectionStateChange');
2815 this.pc.onsignalingstatechange = this.emit.bind(this, 'signalingStateChange');
2816
2817 // handle incoming ice and data channel events
2818 this.pc.onaddstream = this._onAddStream.bind(this);
2819 this.pc.onicecandidate = this._onIce.bind(this);
2820 this.pc.ondatachannel = this._onDataChannel.bind(this);
2821
2822 this.localDescription = {
2823 contents: []
2824 };
2825 this.remoteDescription = {
2826 contents: []
2827 };
2828
2829 this.localStream = null;
2830 this.remoteStreams = [];
2831
2832 this.config = {
2833 debug: false,
2834 ice: {},
2835 sid: '',
2836 isInitiator: true,
2837 sdpSessionID: Date.now(),
2838 useJingle: false
2839 };
2840
2841 // apply our config
2842 for (item in config) {
2843 this.config[item] = config[item];
2844 }
2845
2846 if (this.config.debug) {
2847 this.on('*', function (eventName, event) {
2848 var logger = config.logger || console;
2849 logger.log('PeerConnection event:', arguments);
2850 });
2851 }
2852}
2853
2854util.inherits(PeerConnection, WildEmitter);
2855
2856PeerConnection.prototype.__defineGetter__('signalingState', function () {
2857 return this.pc.signalingState;
2858});
2859PeerConnection.prototype.__defineGetter__('iceConnectionState', function () {
2860 return this.pc.iceConnectionState;
2861});
2862
2863// Add a stream to the peer connection object
2864PeerConnection.prototype.addStream = function (stream) {
2865 this.localStream = stream;
2866 this.pc.addStream(stream);
2867};
2868
2869
2870// Init and add ice candidate object with correct constructor
2871PeerConnection.prototype.processIce = function (update, cb) {
2872 cb = cb || function () {};
2873 var self = this;
2874
2875 if (update.contents) {
2876 var contentNames = _.pluck(this.remoteDescription.contents, 'name');
2877 var contents = update.contents;
2878
2879 contents.forEach(function (content) {
2880 var transport = content.transport || {};
2881 var candidates = transport.candidates || [];
2882 var mline = contentNames.indexOf(content.name);
2883 var mid = content.name;
2884
2885 candidates.forEach(function (candidate) {
2886 console.log('addicecandidate');
2887 var iceCandidate = SJJ.toCandidateSDP(candidate) + '\r\n';
2888 self.pc.addIceCandidate(new webrtc.IceCandidate({
2889 candidate: iceCandidate,
2890 sdpMLineIndex: mline,
2891 sdpMid: mid
2892 })
2893 /* not yet, breaks Chrome M32 */
2894 /*
2895 , function () {
2896 // well, this success callback is pretty meaningless
2897 },
2898 function (err) {
2899 self.emit('error', err);
2900 }
2901 */
2902 );
2903 });
2904 });
2905 } else {
2906 self.pc.addIceCandidate(new webrtc.IceCandidate(update.candidate));
2907 }
2908 cb();
2909};
2910
2911// Generate and emit an offer with the given constraints
2912PeerConnection.prototype.offer = function (constraints, cb) {
2913 var self = this;
2914 var hasConstraints = arguments.length === 2;
2915 var mediaConstraints = hasConstraints ? constraints : {
2916 mandatory: {
2917 OfferToReceiveAudio: true,
2918 OfferToReceiveVideo: true
2919 }
2920 };
2921 cb = hasConstraints ? cb : constraints;
2922 cb = cb || function () {};
2923
2924 // Actually generate the offer
2925 this.pc.createOffer(
2926 function (offer) {
2927 self.pc.setLocalDescription(offer,
2928 function () {
2929 var jingle;
2930 var expandedOffer = {
2931 type: 'offer',
2932 sdp: offer.sdp
2933 };
2934 if (self.config.useJingle) {
2935 jingle = SJJ.toSessionJSON(offer.sdp, self.config.isInitiator ? 'initiator' : 'responder');
2936 jingle.sid = self.config.sid;
2937 self.localDescription = jingle;
2938
2939 // Save ICE credentials
2940 _.each(jingle.contents, function (content) {
2941 var transport = content.transport || {};
2942 if (transport.ufrag) {
2943 self.config.ice[content.name] = {
2944 ufrag: transport.ufrag,
2945 pwd: transport.pwd
2946 };
2947 }
2948 });
2949
2950 expandedOffer.jingle = jingle;
2951 }
2952
2953 self.emit('offer', expandedOffer);
2954 cb(null, expandedOffer);
2955 },
2956 function (err) {
2957 self.emit('error', err);
2958 cb(err);
2959 }
2960 );
2961 },
2962 function (err) {
2963 self.emit('error', err);
2964 cb(err);
2965 },
2966 mediaConstraints
2967 );
2968};
2969
2970
2971// Process an incoming offer so that ICE may proceed before deciding
2972// to answer the request.
2973PeerConnection.prototype.handleOffer = function (offer, cb) {
2974 cb = cb || function () {};
2975 var self = this;
2976 offer.type = 'offer';
2977 if (offer.jingle) {
2978 offer.sdp = SJJ.toSessionSDP(offer.jingle, self.config.sdpSessionID);
2979 }
2980 self.pc.setRemoteDescription(new webrtc.SessionDescription(offer), function () {
2981 cb();
2982 }, cb);
2983};
2984
2985// Answer an offer with audio only
2986PeerConnection.prototype.answerAudioOnly = function (cb) {
2987 var mediaConstraints = {
2988 mandatory: {
2989 OfferToReceiveAudio: true,
2990 OfferToReceiveVideo: false
2991 }
2992 };
2993 this._answer(mediaConstraints, cb);
2994};
2995
2996// Answer an offer without offering to recieve
2997PeerConnection.prototype.answerBroadcastOnly = function (cb) {
2998 var mediaConstraints = {
2999 mandatory: {
3000 OfferToReceiveAudio: false,
3001 OfferToReceiveVideo: false
3002 }
3003 };
3004 this._answer(mediaConstraints, cb);
3005};
3006
3007// Answer an offer with given constraints default is audio/video
3008PeerConnection.prototype.answer = function (constraints, cb) {
3009 var self = this;
3010 var hasConstraints = arguments.length === 2;
3011 var callback = hasConstraints ? cb : constraints;
3012 var mediaConstraints = hasConstraints ? constraints : {
3013 mandatory: {
3014 OfferToReceiveAudio: true,
3015 OfferToReceiveVideo: true
3016 }
3017 };
3018
3019 this._answer(mediaConstraints, callback);
3020};
3021
3022// Process an answer
3023PeerConnection.prototype.handleAnswer = function (answer, cb) {
3024 cb = cb || function () {};
3025 var self = this;
3026 if (answer.jingle) {
3027 answer.sdp = SJJ.toSessionSDP(answer.jingle, self.config.sdpSessionID);
3028 self.remoteDescription = answer.jingle;
3029 }
3030 self.pc.setRemoteDescription(
3031 new webrtc.SessionDescription(answer),
3032 function () {
3033 cb(null);
3034 },
3035 cb
3036 );
3037};
3038
3039// Close the peer connection
3040PeerConnection.prototype.close = function () {
3041 this.pc.close();
3042 this.emit('close');
3043};
3044
3045// Internal code sharing for various types of answer methods
3046PeerConnection.prototype._answer = function (constraints, cb) {
3047 cb = cb || function () {};
3048 var self = this;
3049 if (!this.pc.remoteDescription) {
3050 // the old API is used, call handleOffer
3051 throw new Error('remoteDescription not set');
3052 }
3053 self.pc.createAnswer(
3054 function (answer) {
3055 self.pc.setLocalDescription(answer,
3056 function () {
3057 var expandedAnswer = {
3058 type: 'answer',
3059 sdp: answer.sdp
3060 };
3061 if (self.config.useJingle) {
3062 var jingle = SJJ.toSessionJSON(answer.sdp);
3063 jingle.sid = self.config.sid;
3064 self.localDescription = jingle;
3065 expandedAnswer.jingle = jingle;
3066 }
3067 self.emit('answer', expandedAnswer);
3068 cb(null, expandedAnswer);
3069 },
3070 function (err) {
3071 self.emit('error', err);
3072 cb(err);
3073 }
3074 );
3075 },
3076 function (err) {
3077 self.emit('error', err);
3078 cb(err);
3079 },
3080 constraints
3081 );
3082};
3083
3084// Internal method for emitting ice candidates on our peer object
3085PeerConnection.prototype._onIce = function (event) {
3086 var self = this;
3087 if (event.candidate) {
3088 var ice = event.candidate;
3089
3090 var expandedCandidate = {
3091 candidate: event.candidate
3092 };
3093
3094 if (self.config.useJingle) {
3095 if (!self.config.ice[ice.sdpMid]) {
3096 var jingle = SJJ.toSessionJSON(self.pc.localDescription.sdp, self.config.isInitiator ? 'initiator' : 'responder');
3097 _.each(jingle.contents, function (content) {
3098 var transport = content.transport || {};
3099 if (transport.ufrag) {
3100 self.config.ice[content.name] = {
3101 ufrag: transport.ufrag,
3102 pwd: transport.pwd
3103 };
3104 }
3105 });
3106 }
3107 expandedCandidate.jingle = {
3108 contents: [{
3109 name: ice.sdpMid,
3110 creator: self.config.isInitiator ? 'initiator' : 'responder',
3111 transport: {
3112 transType: 'iceUdp',
3113 ufrag: self.config.ice[ice.sdpMid].ufrag,
3114 pwd: self.config.ice[ice.sdpMid].pwd,
3115 candidates: [
3116 SJJ.toCandidateJSON(ice.candidate)
3117 ]
3118 }
3119 }]
3120 };
3121 }
3122
3123 this.emit('ice', expandedCandidate);
3124 } else {
3125 this.emit('endOfCandidates');
3126 }
3127};
3128
3129// Internal method for processing a new data channel being added by the
3130// other peer.
3131PeerConnection.prototype._onDataChannel = function (event) {
3132 this.emit('addChannel', event.channel);
3133};
3134
3135// Internal handling of adding stream
3136PeerConnection.prototype._onAddStream = function (event) {
3137 this.remoteStreams.push(event.stream);
3138 this.emit('addStream', event);
3139};
3140
3141// Create a data channel spec reference:
3142// http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCDataChannelInit
3143PeerConnection.prototype.createDataChannel = function (name, opts) {
3144 var channel = this.pc.createDataChannel(name, opts);
3145 return channel;
3146};
3147
3148module.exports = PeerConnection;
3149
3150},{"sdp-jingle-json":15,"traceablepeerconnection":14,"underscore":12,"util":10,"webrtcsupport":2,"wildemitter":13}],7:[function(require,module,exports){
3151var support = require('webrtcsupport');
3152
3153
3154function GainController(stream) {
3155 this.support = support.webAudio && support.mediaStream;
3156
3157 // set our starting value
3158 this.gain = 1;
3159
3160 if (this.support) {
3161 var context = this.context = new support.AudioContext();
3162 this.microphone = context.createMediaStreamSource(stream);
3163 this.gainFilter = context.createGain();
3164 this.destination = context.createMediaStreamDestination();
3165 this.outputStream = this.destination.stream;
3166 this.microphone.connect(this.gainFilter);
3167 this.gainFilter.connect(this.destination);
3168 stream.removeTrack(stream.getAudioTracks()[0]);
3169 stream.addTrack(this.outputStream.getAudioTracks()[0]);
3170 }
3171 this.stream = stream;
3172}
3173
3174// setting
3175GainController.prototype.setGain = function (val) {
3176 // check for support
3177 if (!this.support) return;
3178 this.gainFilter.gain.value = val;
3179 this.gain = val;
3180};
3181
3182GainController.prototype.getGain = function () {
3183 return this.gain;
3184};
3185
3186GainController.prototype.off = function () {
3187 return this.setGain(0);
3188};
3189
3190GainController.prototype.on = function () {
3191 this.setGain(1);
3192};
3193
3194
3195module.exports = GainController;
3196
3197},{"webrtcsupport":2}],16:[function(require,module,exports){
3198// shim for using process in browser
3199
3200var process = module.exports = {};
3201
3202process.nextTick = (function () {
3203 var canSetImmediate = typeof window !== 'undefined'
3204 && window.setImmediate;
3205 var canPost = typeof window !== 'undefined'
3206 && window.postMessage && window.addEventListener
3207 ;
3208
3209 if (canSetImmediate) {
3210 return function (f) { return window.setImmediate(f) };
3211 }
3212
3213 if (canPost) {
3214 var queue = [];
3215 window.addEventListener('message', function (ev) {
3216 var source = ev.source;
3217 if ((source === window || source === null) && ev.data === 'process-tick') {
3218 ev.stopPropagation();
3219 if (queue.length > 0) {
3220 var fn = queue.shift();
3221 fn();
3222 }
3223 }
3224 }, true);
3225
3226 return function nextTick(fn) {
3227 queue.push(fn);
3228 window.postMessage('process-tick', '*');
3229 };
3230 }
3231
3232 return function nextTick(fn) {
3233 setTimeout(fn, 0);
3234 };
3235})();
3236
3237process.title = 'browser';
3238process.browser = true;
3239process.env = {};
3240process.argv = [];
3241
3242process.binding = function (name) {
3243 throw new Error('process.binding is not supported');
3244}
3245
3246// TODO(shtylman)
3247process.cwd = function () { return '/' };
3248process.chdir = function (dir) {
3249 throw new Error('process.chdir is not supported');
3250};
3251
3252},{}],11:[function(require,module,exports){
3253var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {};
3254
3255var EventEmitter = exports.EventEmitter = process.EventEmitter;
3256var isArray = typeof Array.isArray === 'function'
3257 ? Array.isArray
3258 : function (xs) {
3259 return Object.prototype.toString.call(xs) === '[object Array]'
3260 }
3261;
3262function indexOf (xs, x) {
3263 if (xs.indexOf) return xs.indexOf(x);
3264 for (var i = 0; i < xs.length; i++) {
3265 if (x === xs[i]) return i;
3266 }
3267 return -1;
3268}
3269
3270// By default EventEmitters will print a warning if more than
3271// 10 listeners are added to it. This is a useful default which
3272// helps finding memory leaks.
3273//
3274// Obviously not all Emitters should be limited to 10. This function allows
3275// that to be increased. Set to zero for unlimited.
3276var defaultMaxListeners = 10;
3277EventEmitter.prototype.setMaxListeners = function(n) {
3278 if (!this._events) this._events = {};
3279 this._events.maxListeners = n;
3280};
3281
3282
3283EventEmitter.prototype.emit = function(type) {
3284 // If there is no 'error' event listener then throw.
3285 if (type === 'error') {
3286 if (!this._events || !this._events.error ||
3287 (isArray(this._events.error) && !this._events.error.length))
3288 {
3289 if (arguments[1] instanceof Error) {
3290 throw arguments[1]; // Unhandled 'error' event
3291 } else {
3292 throw new Error("Uncaught, unspecified 'error' event.");
3293 }
3294 return false;
3295 }
3296 }
3297
3298 if (!this._events) return false;
3299 var handler = this._events[type];
3300 if (!handler) return false;
3301
3302 if (typeof handler == 'function') {
3303 switch (arguments.length) {
3304 // fast cases
3305 case 1:
3306 handler.call(this);
3307 break;
3308 case 2:
3309 handler.call(this, arguments[1]);
3310 break;
3311 case 3:
3312 handler.call(this, arguments[1], arguments[2]);
3313 break;
3314 // slower
3315 default:
3316 var args = Array.prototype.slice.call(arguments, 1);
3317 handler.apply(this, args);
3318 }
3319 return true;
3320
3321 } else if (isArray(handler)) {
3322 var args = Array.prototype.slice.call(arguments, 1);
3323
3324 var listeners = handler.slice();
3325 for (var i = 0, l = listeners.length; i < l; i++) {
3326 listeners[i].apply(this, args);
3327 }
3328 return true;
3329
3330 } else {
3331 return false;
3332 }
3333};
3334
3335// EventEmitter is defined in src/node_events.cc
3336// EventEmitter.prototype.emit() is also defined there.
3337EventEmitter.prototype.addListener = function(type, listener) {
3338 if ('function' !== typeof listener) {
3339 throw new Error('addListener only takes instances of Function');
3340 }
3341
3342 if (!this._events) this._events = {};
3343
3344 // To avoid recursion in the case that type == "newListeners"! Before
3345 // adding it to the listeners, first emit "newListeners".
3346 this.emit('newListener', type, listener);
3347
3348 if (!this._events[type]) {
3349 // Optimize the case of one listener. Don't need the extra array object.
3350 this._events[type] = listener;
3351 } else if (isArray(this._events[type])) {
3352
3353 // Check for listener leak
3354 if (!this._events[type].warned) {
3355 var m;
3356 if (this._events.maxListeners !== undefined) {
3357 m = this._events.maxListeners;
3358 } else {
3359 m = defaultMaxListeners;
3360 }
3361
3362 if (m && m > 0 && this._events[type].length > m) {
3363 this._events[type].warned = true;
3364 console.error('(node) warning: possible EventEmitter memory ' +
3365 'leak detected. %d listeners added. ' +
3366 'Use emitter.setMaxListeners() to increase limit.',
3367 this._events[type].length);
3368 console.trace();
3369 }
3370 }
3371
3372 // If we've already got an array, just append.
3373 this._events[type].push(listener);
3374 } else {
3375 // Adding the second element, need to change to array.
3376 this._events[type] = [this._events[type], listener];
3377 }
3378
3379 return this;
3380};
3381
3382EventEmitter.prototype.on = EventEmitter.prototype.addListener;
3383
3384EventEmitter.prototype.once = function(type, listener) {
3385 var self = this;
3386 self.on(type, function g() {
3387 self.removeListener(type, g);
3388 listener.apply(this, arguments);
3389 });
3390
3391 return this;
3392};
3393
3394EventEmitter.prototype.removeListener = function(type, listener) {
3395 if ('function' !== typeof listener) {
3396 throw new Error('removeListener only takes instances of Function');
3397 }
3398
3399 // does not use listeners(), so no side effect of creating _events[type]
3400 if (!this._events || !this._events[type]) return this;
3401
3402 var list = this._events[type];
3403
3404 if (isArray(list)) {
3405 var i = indexOf(list, listener);
3406 if (i < 0) return this;
3407 list.splice(i, 1);
3408 if (list.length == 0)
3409 delete this._events[type];
3410 } else if (this._events[type] === listener) {
3411 delete this._events[type];
3412 }
3413
3414 return this;
3415};
3416
3417EventEmitter.prototype.removeAllListeners = function(type) {
3418 if (arguments.length === 0) {
3419 this._events = {};
3420 return this;
3421 }
3422
3423 // does not use listeners(), so no side effect of creating _events[type]
3424 if (type && this._events && this._events[type]) this._events[type] = null;
3425 return this;
3426};
3427
3428EventEmitter.prototype.listeners = function(type) {
3429 if (!this._events) this._events = {};
3430 if (!this._events[type]) this._events[type] = [];
3431 if (!isArray(this._events[type])) {
3432 this._events[type] = [this._events[type]];
3433 }
3434 return this._events[type];
3435};
3436
3437EventEmitter.listenerCount = function(emitter, type) {
3438 var ret;
3439 if (!emitter._events || !emitter._events[type])
3440 ret = 0;
3441 else if (typeof emitter._events[type] === 'function')
3442 ret = 1;
3443 else
3444 ret = emitter._events[type].length;
3445 return ret;
3446};
3447
3448},{"__browserify_process":16}],15:[function(require,module,exports){
3449var tosdp = require('./lib/tosdp');
3450var tojson = require('./lib/tojson');
3451
3452
3453exports.toSessionSDP = tosdp.toSessionSDP;
3454exports.toMediaSDP = tosdp.toMediaSDP;
3455exports.toCandidateSDP = tosdp.toCandidateSDP;
3456
3457exports.toSessionJSON = tojson.toSessionJSON;
3458exports.toMediaJSON = tojson.toMediaJSON;
3459exports.toCandidateJSON = tojson.toCandidateJSON;
3460
3461},{"./lib/tojson":18,"./lib/tosdp":17}],17:[function(require,module,exports){
3462var senders = {
3463 'initiator': 'sendonly',
3464 'responder': 'recvonly',
3465 'both': 'sendrecv',
3466 'none': 'inactive',
3467 'sendonly': 'initator',
3468 'recvonly': 'responder',
3469 'sendrecv': 'both',
3470 'inactive': 'none'
3471};
3472
3473
3474exports.toSessionSDP = function (session, sid, time) {
3475 var sdp = [
3476 'v=0',
3477 'o=- ' + (sid || session.sid || Date.now()) + ' ' + (time || Date.now()) + ' IN IP4 0.0.0.0',
3478 's=-',
3479 't=0 0'
3480 ];
3481
3482 var groups = session.groups || [];
3483 groups.forEach(function (group) {
3484 sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' '));
3485 });
3486
3487 var contents = session.contents || [];
3488 contents.forEach(function (content) {
3489 sdp.push(exports.toMediaSDP(content));
3490 });
3491
3492 return sdp.join('\r\n') + '\r\n';
3493};
3494
3495exports.toMediaSDP = function (content) {
3496 var sdp = [];
3497
3498 var desc = content.description;
3499 var transport = content.transport;
3500 var payloads = desc.payloads || [];
3501 var fingerprints = (transport && transport.fingerprints) || [];
3502
3503 var mline = [desc.media, '1'];
3504
3505 if ((desc.encryption && desc.encryption.length > 0) || (fingerprints.length > 0)) {
3506 mline.push('RTP/SAVPF');
3507 } else {
3508 mline.push('RTP/AVPF');
3509 }
3510 payloads.forEach(function (payload) {
3511 mline.push(payload.id);
3512 });
3513
3514
3515 sdp.push('m=' + mline.join(' '));
3516
3517 sdp.push('c=IN IP4 0.0.0.0');
3518 sdp.push('a=rtcp:1 IN IP4 0.0.0.0');
3519
3520 if (transport) {
3521 if (transport.ufrag) {
3522 sdp.push('a=ice-ufrag:' + transport.ufrag);
3523 }
3524 if (transport.pwd) {
3525 sdp.push('a=ice-pwd:' + transport.pwd);
3526 }
3527 if (transport.setup) {
3528 sdp.push('a=setup:' + transport.setup);
3529 }
3530 fingerprints.forEach(function (fingerprint) {
3531 sdp.push('a=fingerprint:' + fingerprint.hash + ' ' + fingerprint.value);
3532 });
3533 }
3534
3535 sdp.push('a=' + (senders[content.senders] || 'sendrecv'));
3536 sdp.push('a=mid:' + content.name);
3537
3538 if (desc.mux) {
3539 sdp.push('a=rtcp-mux');
3540 }
3541
3542 var encryption = desc.encryption || [];
3543 encryption.forEach(function (crypto) {
3544 sdp.push('a=crypto:' + crypto.tag + ' ' + crypto.cipherSuite + ' ' + crypto.keyParams + (crypto.sessionParams ? ' ' + crypto.sessionParams : ''));
3545 });
3546
3547 payloads.forEach(function (payload) {
3548 var rtpmap = 'a=rtpmap:' + payload.id + ' ' + payload.name + '/' + payload.clockrate;
3549 if (payload.channels && payload.channels != '1') {
3550 rtpmap += '/' + payload.channels;
3551 }
3552 sdp.push(rtpmap);
3553
3554 if (payload.parameters && payload.parameters.length) {
3555 var fmtp = ['a=fmtp:' + payload.id];
3556 payload.parameters.forEach(function (param) {
3557 fmtp.push((param.key ? param.key + '=' : '') + param.value);
3558 });
3559 sdp.push(fmtp.join(' '));
3560 }
3561
3562 if (payload.feedback) {
3563 payload.feedback.forEach(function (fb) {
3564 if (fb.type === 'trr-int') {
3565 sdp.push('a=rtcp-fb:' + payload.id + ' trr-int ' + fb.value ? fb.value : '0');
3566 } else {
3567 sdp.push('a=rtcp-fb:' + payload.id + ' ' + fb.type + (fb.subtype ? ' ' + fb.subtype : ''));
3568 }
3569 });
3570 }
3571 });
3572
3573 if (desc.feedback) {
3574 desc.feedback.forEach(function (fb) {
3575 if (fb.type === 'trr-int') {
3576 sdp.push('a=rtcp-fb:* trr-int ' + fb.value ? fb.value : '0');
3577 } else {
3578 sdp.push('a=rtcp-fb:* ' + fb.type + (fb.subtype ? ' ' + fb.subtype : ''));
3579 }
3580 });
3581 }
3582
3583 var hdrExts = desc.headerExtensions || [];
3584 hdrExts.forEach(function (hdr) {
3585 sdp.push('a=extmap:' + hdr.id + (hdr.senders ? '/' + senders[hdr.senders] : '') + ' ' + hdr.uri);
3586 });
3587
3588 var ssrcGroups = desc.sourceGroups || [];
3589 ssrcGroups.forEach(function (ssrcGroup) {
3590 sdp.push('a=ssrc-group:' + ssrcGroup.semantics + ' ' + ssrcGroup.sources.join(' '));
3591 });
3592
3593 var ssrcs = desc.sources || [];
3594 ssrcs.forEach(function (ssrc) {
3595 for (var i = 0; i < ssrc.parameters.length; i++) {
3596 var param = ssrc.parameters[i];
3597 sdp.push('a=ssrc:' + (ssrc.ssrc || desc.ssrc) + ' ' + param.key + (param.value ? (':' + param.value) : ''));
3598 }
3599 });
3600
3601 var candidates = transport.candidates || [];
3602 candidates.forEach(function (candidate) {
3603 sdp.push(exports.toCandidateSDP(candidate));
3604 });
3605
3606 return sdp.join('\r\n');
3607};
3608
3609exports.toCandidateSDP = function (candidate) {
3610 var sdp = [];
3611
3612 sdp.push(candidate.foundation);
3613 sdp.push(candidate.component);
3614 sdp.push(candidate.protocol);
3615 sdp.push(candidate.priority);
3616 sdp.push(candidate.ip);
3617 sdp.push(candidate.port);
3618
3619 var type = candidate.type;
3620 sdp.push('typ');
3621 sdp.push(type);
3622 if (type === 'srflx' || type === 'prflx' || type === 'relay') {
3623 if (candidate.relAddr && candidate.relPort) {
3624 sdp.push('raddr');
3625 sdp.push(candidate.relAddr);
3626 sdp.push('rport');
3627 sdp.push(candidate.relPort);
3628 }
3629 }
3630
3631 sdp.push('generation');
3632 sdp.push(candidate.generation || '0');
3633
3634 return 'a=candidate:' + sdp.join(' ');
3635};
3636
3637},{}],18:[function(require,module,exports){
3638var parsers = require('./parsers');
3639var idCounter = Math.random();
3640
3641exports._setIdCounter = function (counter) {
3642 idCounter = counter;
3643};
3644
3645exports.toSessionJSON = function (sdp, creator) {
3646 // Divide the SDP into session and media sections.
3647 var media = sdp.split('\r\nm=');
3648 for (var i = 1; i < media.length; i++) {
3649 media[i] = 'm=' + media[i];
3650 if (i !== media.length - 1) {
3651 media[i] += '\r\n';
3652 }
3653 }
3654 var session = media.shift() + '\r\n';
3655 var sessionLines = parsers.lines(session);
3656 var parsed = {};
3657
3658 var contents = [];
3659 media.forEach(function (m) {
3660 contents.push(exports.toMediaJSON(m, session, creator));
3661 });
3662 parsed.contents = contents;
3663
3664 var groupLines = parsers.findLines('a=group:', sessionLines);
3665 if (groupLines.length) {
3666 parsed.groups = parsers.groups(groupLines);
3667 }
3668
3669 return parsed;
3670};
3671
3672exports.toMediaJSON = function (media, session, creator) {
3673 var lines = parsers.lines(media);
3674 var sessionLines = parsers.lines(session);
3675 var mline = parsers.mline(lines[0]);
3676
3677 var content = {
3678 creator: creator,
3679 name: mline.media,
3680 description: {
3681 descType: 'rtp',
3682 media: mline.media,
3683 payloads: [],
3684 encryption: [],
3685 feedback: [],
3686 headerExtensions: []
3687 },
3688 transport: {
3689 transType: 'iceUdp',
3690 candidates: [],
3691 fingerprints: []
3692 }
3693 };
3694 var desc = content.description;
3695 var trans = content.transport;
3696
3697 var ssrc = parsers.findLine('a=ssrc:', lines);
3698 if (ssrc) {
3699 desc.ssrc = ssrc.substr(7).split(' ')[0];
3700 }
3701
3702 // If we have a mid, use that for the content name instead.
3703 var mid = parsers.findLine('a=mid:', lines);
3704 if (mid) {
3705 content.name = mid.substr(6);
3706 }
3707
3708 if (parsers.findLine('a=sendrecv', lines, sessionLines)) {
3709 content.senders = 'both';
3710 } else if (parsers.findLine('a=sendonly', lines, sessionLines)) {
3711 content.senders = 'initiator';
3712 } else if (parsers.findLine('a=recvonly', lines, sessionLines)) {
3713 content.senders = 'responder';
3714 } else if (parsers.findLine('a=inactive', lines, sessionLines)) {
3715 content.senders = 'none';
3716 }
3717
3718 var rtpmapLines = parsers.findLines('a=rtpmap:', lines);
3719 rtpmapLines.forEach(function (line) {
3720 var payload = parsers.rtpmap(line);
3721 payload.feedback = [];
3722
3723 var fmtpLines = parsers.findLines('a=fmtp:' + payload.id, lines);
3724 fmtpLines.forEach(function (line) {
3725 payload.parameters = parsers.fmtp(line);
3726 });
3727
3728 var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines);
3729 fbLines.forEach(function (line) {
3730 payload.feedback.push(parsers.rtcpfb(line));
3731 });
3732
3733 desc.payloads.push(payload);
3734 });
3735
3736 var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines);
3737 cryptoLines.forEach(function (line) {
3738 desc.encryption.push(parsers.crypto(line));
3739 });
3740
3741 if (parsers.findLine('a=rtcp-mux', lines)) {
3742 desc.mux = true;
3743 }
3744
3745 var fbLines = parsers.findLines('a=rtcp-fb:*', lines);
3746 fbLines.forEach(function (line) {
3747 desc.feedback.push(parsers.rtcpfb(line));
3748 });
3749
3750 var extLines = parsers.findLines('a=extmap:', lines);
3751 extLines.forEach(function (line) {
3752 var ext = parsers.extmap(line);
3753
3754 var senders = {
3755 sendonly: 'responder',
3756 recvonly: 'initiator',
3757 sendrecv: 'both',
3758 inactive: 'none'
3759 };
3760 ext.senders = senders[ext.senders];
3761
3762 desc.headerExtensions.push(ext);
3763 });
3764
3765 var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines);
3766 desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []);
3767
3768 var ssrcLines = parsers.findLines('a=ssrc:', lines);
3769 desc.sources = parsers.sources(ssrcLines || []);
3770
3771 var fingerprintLines = parsers.findLines('a=fingerprint:', lines, sessionLines);
3772 fingerprintLines.forEach(function (line) {
3773 var fp = parsers.fingerprint(line);
3774 var setup = parsers.findLine('a=setup:', lines, sessionLines);
3775 if (setup) {
3776 fp.setup = setup.substr(8);
3777 }
3778 trans.fingerprints.push(fp);
3779 });
3780
3781 var ufragLine = parsers.findLine('a=ice-ufrag:', lines, sessionLines);
3782 var pwdLine = parsers.findLine('a=ice-pwd:', lines, sessionLines);
3783 if (ufragLine && pwdLine) {
3784 trans.ufrag = ufragLine.substr(12);
3785 trans.pwd = pwdLine.substr(10);
3786 trans.candidates = [];
3787
3788 var candidateLines = parsers.findLines('a=candidate:', lines, sessionLines);
3789 candidateLines.forEach(function (line) {
3790 trans.candidates.push(exports.toCandidateJSON(line));
3791 });
3792 }
3793
3794 return content;
3795};
3796
3797exports.toCandidateJSON = function (line) {
3798 var candidate = parsers.candidate(line.split('\r\n')[0]);
3799 candidate.id = (idCounter++).toString(36).substr(0, 12);
3800 return candidate;
3801};
3802
3803},{"./parsers":19}],14:[function(require,module,exports){
3804// based on https://github.com/ESTOS/strophe.jingle/
3805// adds wildemitter support
3806var util = require('util');
3807var webrtc = require('webrtcsupport');
3808var WildEmitter = require('wildemitter');
3809
3810function dumpSDP(description) {
3811 return 'type: ' + description.type + '\r\n' + description.sdp;
3812}
3813
3814function TraceablePeerConnection(config, constraints) {
3815 var self = this;
3816 WildEmitter.call(this);
3817
3818 this.peerconnection = new webrtc.PeerConnection(config, constraints);
3819
3820 this.trace = function (what, info) {
3821 self.emit('PeerConnectionTrace', {
3822 time: new Date(),
3823 type: what,
3824 value: info || ""
3825 });
3826 };
3827
3828 this.onicecandidate = null;
3829 this.peerconnection.onicecandidate = function (event) {
3830 self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
3831 if (self.onicecandidate !== null) {
3832 self.onicecandidate(event);
3833 }
3834 };
3835 this.onaddstream = null;
3836 this.peerconnection.onaddstream = function (event) {
3837 self.trace('onaddstream', event.stream.id);
3838 if (self.onaddstream !== null) {
3839 self.onaddstream(event);
3840 }
3841 };
3842 this.onremovestream = null;
3843 this.peerconnection.onremovestream = function (event) {
3844 self.trace('onremovestream', event.stream.id);
3845 if (self.onremovestream !== null) {
3846 self.onremovestream(event);
3847 }
3848 };
3849 this.onsignalingstatechange = null;
3850 this.peerconnection.onsignalingstatechange = function (event) {
3851 self.trace('onsignalingstatechange', self.signalingState);
3852 if (self.onsignalingstatechange !== null) {
3853 self.onsignalingstatechange(event);
3854 }
3855 };
3856 this.oniceconnectionstatechange = null;
3857 this.peerconnection.oniceconnectionstatechange = function (event) {
3858 self.trace('oniceconnectionstatechange', self.iceConnectionState);
3859 if (self.oniceconnectionstatechange !== null) {
3860 self.oniceconnectionstatechange(event);
3861 }
3862 };
3863 this.onnegotiationneeded = null;
3864 this.peerconnection.onnegotiationneeded = function (event) {
3865 self.trace('onnegotiationneeded');
3866 if (self.onnegotiationneeded !== null) {
3867 self.onnegotiationneeded(event);
3868 }
3869 };
3870 self.ondatachannel = null;
3871 this.peerconnection.ondatachannel = function (event) {
3872 self.trace('ondatachannel', event);
3873 if (self.ondatachannel !== null) {
3874 self.ondatachannel(event);
3875 }
3876 };
3877}
3878
3879util.inherits(TraceablePeerConnection, WildEmitter);
3880
3881if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
3882 TraceablePeerConnection.prototype.__defineGetter__('signalingState', function () { return this.peerconnection.signalingState; });
3883 TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function () { return this.peerconnection.iceConnectionState; });
3884 TraceablePeerConnection.prototype.__defineGetter__('localDescription', function () { return this.peerconnection.localDescription; });
3885 TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function () { return this.peerconnection.remoteDescription; });
3886}
3887
3888TraceablePeerConnection.prototype.addStream = function (stream) {
3889 this.trace('addStream', stream.id);
3890 this.peerconnection.addStream(stream);
3891};
3892
3893TraceablePeerConnection.prototype.removeStream = function (stream) {
3894 this.trace('removeStream', stream.id);
3895 this.peerconnection.removeStream(stream);
3896};
3897
3898TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
3899 this.trace('createDataChannel', label, opts);
3900 return this.peerconnection.createDataChannel(label, opts);
3901};
3902
3903TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
3904 var self = this;
3905 this.trace('setLocalDescription', dumpSDP(description));
3906 this.peerconnection.setLocalDescription(description,
3907 function () {
3908 self.trace('setLocalDescriptionOnSuccess');
3909 successCallback();
3910 },
3911 function (err) {
3912 self.trace('setLocalDescriptionOnFailure', err);
3913 failureCallback(err);
3914 }
3915 );
3916};
3917
3918TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
3919 var self = this;
3920 this.trace('setRemoteDescription', dumpSDP(description));
3921 this.peerconnection.setRemoteDescription(description,
3922 function () {
3923 self.trace('setRemoteDescriptionOnSuccess');
3924 successCallback();
3925 },
3926 function (err) {
3927 self.trace('setRemoteDescriptionOnFailure', err);
3928 failureCallback(err);
3929 }
3930 );
3931};
3932
3933TraceablePeerConnection.prototype.close = function () {
3934 this.trace('stop');
3935 if (this.statsinterval !== null) {
3936 window.clearInterval(this.statsinterval);
3937 this.statsinterval = null;
3938 }
3939 this.peerconnection.close();
3940};
3941
3942TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
3943 var self = this;
3944 this.trace('createOffer', JSON.stringify(constraints, null, ' '));
3945 this.peerconnection.createOffer(
3946 function (offer) {
3947 self.trace('createOfferOnSuccess', dumpSDP(offer));
3948 successCallback(offer);
3949 },
3950 function (err) {
3951 self.trace('createOfferOnFailure', err);
3952 failureCallback(err);
3953 },
3954 constraints
3955 );
3956};
3957
3958TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
3959 var self = this;
3960 this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
3961 this.peerconnection.createAnswer(
3962 function (answer) {
3963 self.trace('createAnswerOnSuccess', dumpSDP(answer));
3964 successCallback(answer);
3965 },
3966 function (err) {
3967 self.trace('createAnswerOnFailure', err);
3968 failureCallback(err);
3969 },
3970 constraints
3971 );
3972};
3973
3974TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
3975 var self = this;
3976 this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
3977 this.peerconnection.addIceCandidate(candidate);
3978 /* maybe later
3979 this.peerconnection.addIceCandidate(candidate,
3980 function () {
3981 self.trace('addIceCandidateOnSuccess');
3982 successCallback();
3983 },
3984 function (err) {
3985 self.trace('addIceCandidateOnFailure', err);
3986 failureCallback(err);
3987 }
3988 );
3989 */
3990};
3991
3992TraceablePeerConnection.prototype.getStats = function (callback, errback) {
3993 if (navigator.mozGetUserMedia) {
3994 // ignore for now...
3995 } else {
3996 this.peerconnection.getStats(callback);
3997 }
3998};
3999
4000module.exports = TraceablePeerConnection;
4001
4002},{"util":10,"webrtcsupport":2,"wildemitter":13}],19:[function(require,module,exports){
4003exports.lines = function (sdp) {
4004 return sdp.split('\r\n').filter(function (line) {
4005 return line.length > 0;
4006 });
4007};
4008
4009exports.findLine = function (prefix, mediaLines, sessionLines) {
4010 var prefixLength = prefix.length;
4011 for (var i = 0; i < mediaLines.length; i++) {
4012 if (mediaLines[i].substr(0, prefixLength) === prefix) {
4013 return mediaLines[i];
4014 }
4015 }
4016 // Continue searching in parent session section
4017 if (!sessionLines) {
4018 return false;
4019 }
4020
4021 for (var j = 0; j < sessionLines.length; j++) {
4022 if (sessionLines[j].substr(0, prefixLength) === prefix) {
4023 return sessionLines[j];
4024 }
4025 }
4026
4027 return false;
4028};
4029
4030exports.findLines = function (prefix, mediaLines, sessionLines) {
4031 var results = [];
4032 var prefixLength = prefix.length;
4033 for (var i = 0; i < mediaLines.length; i++) {
4034 if (mediaLines[i].substr(0, prefixLength) === prefix) {
4035 results.push(mediaLines[i]);
4036 }
4037 }
4038 if (results.length || !sessionLines) {
4039 return results;
4040 }
4041 for (var j = 0; j < sessionLines.length; j++) {
4042 if (sessionLines[j].substr(0, prefixLength) === prefix) {
4043 results.push(sessionLines[j]);
4044 }
4045 }
4046 return results;
4047};
4048
4049exports.mline = function (line) {
4050 var parts = line.substr(2).split(' ');
4051 var parsed = {
4052 media: parts[0],
4053 port: parts[1],
4054 proto: parts[2],
4055 formats: []
4056 };
4057 for (var i = 3; i < parts.length; i++) {
4058 if (parts[i]) {
4059 parsed.formats.push(parts[i]);
4060 }
4061 }
4062 return parsed;
4063};
4064
4065exports.rtpmap = function (line) {
4066 var parts = line.substr(9).split(' ');
4067 var parsed = {
4068 id: parts.shift()
4069 };
4070
4071 parts = parts[0].split('/');
4072
4073 parsed.name = parts[0];
4074 parsed.clockrate = parts[1];
4075 parsed.channels = parts.length == 3 ? parts[2] : '1';
4076 return parsed;
4077};
4078
4079exports.fmtp = function (line) {
4080 var kv, key, value;
4081 var parts = line.substr(line.indexOf(' ') + 1).split(';');
4082 var parsed = [];
4083 for (var i = 0; i < parts.length; i++) {
4084 kv = parts[i].split('=');
4085 key = kv[0].trim();
4086 value = kv[1];
4087 if (key && value) {
4088 parsed.push({key: key, value: value});
4089 } else if (key) {
4090 parsed.push({key: '', value: key});
4091 }
4092 }
4093 return parsed;
4094};
4095
4096exports.crypto = function (line) {
4097 var parts = line.substr(9).split(' ');
4098 var parsed = {
4099 tag: parts[0],
4100 cipherSuite: parts[1],
4101 keyParams: parts[2],
4102 sessionParams: parts.slice(3).join(' ')
4103 };
4104 return parsed;
4105};
4106
4107exports.fingerprint = function (line) {
4108 var parts = line.substr(14).split(' ');
4109 return {
4110 hash: parts[0],
4111 value: parts[1]
4112 };
4113};
4114
4115exports.extmap = function (line) {
4116 var parts = line.substr(9).split(' ');
4117 var parsed = {};
4118
4119 var idpart = parts.shift();
4120 var sp = idpart.indexOf('/');
4121 if (sp >= 0) {
4122 parsed.id = idpart.substr(0, sp);
4123 parsed.senders = idpart.substr(sp + 1);
4124 } else {
4125 parsed.id = idpart;
4126 parsed.senders = 'sendrecv';
4127 }
4128
4129 parsed.uri = parts.shift() || '';
4130
4131 return parsed;
4132};
4133
4134exports.rtcpfb = function (line) {
4135 var parts = line.substr(10).split(' ');
4136 var parsed = {};
4137 parsed.id = parts.shift();
4138 parsed.type = parts.shift();
4139 if (parsed.type === 'trr-int') {
4140 parsed.value = parts.shift();
4141 } else {
4142 parsed.subtype = parts.shift() || '';
4143 }
4144 parsed.parameters = parts;
4145 return parsed;
4146};
4147
4148exports.candidate = function (line) {
4149 var parts = line.substring(12).split(' ');
4150
4151 var candidate = {
4152 foundation: parts[0],
4153 component: parts[1],
4154 protocol: parts[2].toLowerCase(),
4155 priority: parts[3],
4156 ip: parts[4],
4157 port: parts[5],
4158 // skip parts[6] == 'typ'
4159 type: parts[7],
4160 generation: '0'
4161 };
4162
4163 for (var i = 8; i < parts.length; i += 2) {
4164 if (parts[i] === 'raddr') {
4165 candidate.relAddr = parts[i + 1];
4166 } else if (parts[i] === 'rport') {
4167 candidate.relPort = parts[i + 1];
4168 } else if (parts[i] === 'generation') {
4169 candidate.generation = parts[i + 1];
4170 }
4171 }
4172
4173 candidate.network = '1';
4174
4175 return candidate;
4176};
4177
4178exports.sourceGroups = function (lines) {
4179 var parsed = [];
4180 for (var i = 0; i < lines.length; i++) {
4181 var parts = lines[i].substr(13).split(' ');
4182 parsed.push({
4183 semantics: parts.shift(),
4184 sources: parts
4185 });
4186 }
4187 return parsed;
4188};
4189
4190exports.sources = function (lines) {
4191 // http://tools.ietf.org/html/rfc5576
4192 var parsed = [];
4193 var sources = {};
4194 for (var i = 0; i < lines.length; i++) {
4195 var parts = lines[i].substr(7).split(' ');
4196 var ssrc = parts.shift();
4197
4198 if (!sources[ssrc]) {
4199 var source = {
4200 ssrc: ssrc,
4201 parameters: []
4202 };
4203 parsed.push(source);
4204
4205 // Keep an index
4206 sources[ssrc] = source;
4207 }
4208
4209 parts = parts.join(' ').split(':');
4210 var attribute = parts.shift();
4211 var value = parts.join(':') || null;
4212
4213 sources[ssrc].parameters.push({
4214 key: attribute,
4215 value: value
4216 });
4217 }
4218
4219 return parsed;
4220};
4221
4222exports.groups = function (lines) {
4223 // http://tools.ietf.org/html/rfc5888
4224 var parsed = [];
4225 var parts;
4226 for (var i = 0; i < lines.length; i++) {
4227 parts = lines[i].substr(8).split(' ');
4228 parsed.push({
4229 semantics: parts.shift(),
4230 contents: parts
4231 });
4232 }
4233 return parsed;
4234};
4235
4236},{}]},{},[1])(1)
4237});
4238;
\No newline at end of file