Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
webrtc.js/webrtc.bundle.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
5356 lines (4717 sloc)
170 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (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; | |
| return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){ | |
| var util = require('util'); | |
| var webrtc = require('webrtcsupport'); | |
| var WildEmitter = require('wildemitter'); | |
| var mockconsole = require('mockconsole'); | |
| var localMedia = require('localmedia'); | |
| var Peer = require('./peer'); | |
| function WebRTC(opts) { | |
| var self = this; | |
| var options = opts || {}; | |
| var config = this.config = { | |
| debug: false, | |
| // makes the entire PC config overridable | |
| peerConnectionConfig: { | |
| iceServers: [{"url": "stun:stun.l.google.com:19302"}] | |
| }, | |
| peerConnectionConstraints: { | |
| optional: [ | |
| {DtlsSrtpKeyAgreement: true} | |
| ] | |
| }, | |
| receiveMedia: { | |
| mandatory: { | |
| OfferToReceiveAudio: true, | |
| OfferToReceiveVideo: true | |
| } | |
| }, | |
| enableDataChannels: true | |
| }; | |
| var item; | |
| // expose screensharing check | |
| this.screenSharingSupport = webrtc.screenSharing; | |
| // We also allow a 'logger' option. It can be any object that implements | |
| // log, warn, and error methods. | |
| // We log nothing by default, following "the rule of silence": | |
| // http://www.linfo.org/rule_of_silence.html | |
| this.logger = function () { | |
| // we assume that if you're in debug mode and you didn't | |
| // pass in a logger, you actually want to log as much as | |
| // possible. | |
| if (opts.debug) { | |
| return opts.logger || console; | |
| } else { | |
| // or we'll use your logger which should have its own logic | |
| // for output. Or we'll return the no-op. | |
| return opts.logger || mockconsole; | |
| } | |
| }(); | |
| // set options | |
| for (item in options) { | |
| this.config[item] = options[item]; | |
| } | |
| // check for support | |
| if (!webrtc.support) { | |
| this.logger.error('Your browser doesn\'t seem to support WebRTC'); | |
| } | |
| // where we'll store our peer connections | |
| this.peers = []; | |
| // call localMedia constructor | |
| localMedia.call(this, this.config); | |
| this.on('speaking', function () { | |
| if (!self.hardMuted) { | |
| // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload | |
| self.peers.forEach(function (peer) { | |
| if (peer.enableDataChannels) { | |
| var dc = peer.getDataChannel('hark'); | |
| if (dc.readyState != 'open') return; | |
| dc.send(JSON.stringify({type: 'speaking'})); | |
| } | |
| }); | |
| } | |
| }); | |
| this.on('stoppedSpeaking', function () { | |
| if (!self.hardMuted) { | |
| // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload | |
| self.peers.forEach(function (peer) { | |
| if (peer.enableDataChannels) { | |
| var dc = peer.getDataChannel('hark'); | |
| if (dc.readyState != 'open') return; | |
| dc.send(JSON.stringify({type: 'stoppedSpeaking'})); | |
| } | |
| }); | |
| } | |
| }); | |
| this.on('volumeChange', function (volume, treshold) { | |
| if (!self.hardMuted) { | |
| // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload | |
| self.peers.forEach(function (peer) { | |
| if (peer.enableDataChannels) { | |
| var dc = peer.getDataChannel('hark'); | |
| if (dc.readyState != 'open') return; | |
| dc.send(JSON.stringify({type: 'volume', volume: volume })); | |
| } | |
| }); | |
| } | |
| }); | |
| // log events in debug mode | |
| if (this.config.debug) { | |
| this.on('*', function (event, val1, val2) { | |
| var logger; | |
| // if you didn't pass in a logger and you explicitly turning on debug | |
| // we're just going to assume you're wanting log output with console | |
| if (self.config.logger === mockconsole) { | |
| logger = console; | |
| } else { | |
| logger = self.logger; | |
| } | |
| logger.log('event:', event, val1, val2); | |
| }); | |
| } | |
| } | |
| util.inherits(WebRTC, localMedia); | |
| WebRTC.prototype.createPeer = function (opts) { | |
| var peer; | |
| opts.parent = this; | |
| peer = new Peer(opts); | |
| this.peers.push(peer); | |
| return peer; | |
| }; | |
| // removes peers | |
| WebRTC.prototype.removePeers = function (id, type) { | |
| this.getPeers(id, type).forEach(function (peer) { | |
| peer.end(); | |
| }); | |
| }; | |
| // fetches all Peer objects by session id and/or type | |
| WebRTC.prototype.getPeers = function (sessionId, type) { | |
| return this.peers.filter(function (peer) { | |
| return (!sessionId || peer.id === sessionId) && (!type || peer.type === type); | |
| }); | |
| }; | |
| // sends message to all | |
| WebRTC.prototype.sendToAll = function (message, payload) { | |
| this.peers.forEach(function (peer) { | |
| peer.send(message, payload); | |
| }); | |
| }; | |
| // sends message to all using a datachannel | |
| // only sends to anyone who has an open datachannel | |
| WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) { | |
| this.peers.forEach(function (peer) { | |
| if (peer.enableDataChannels) { | |
| peer.sendDirectly(channel, message, payload); | |
| } | |
| }); | |
| }; | |
| module.exports = WebRTC; | |
| },{"./peer":3,"localmedia":7,"mockconsole":6,"util":2,"webrtcsupport":5,"wildemitter":4}],2:[function(require,module,exports){ | |
| var events = require('events'); | |
| exports.isArray = isArray; | |
| exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; | |
| exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; | |
| exports.print = function () {}; | |
| exports.puts = function () {}; | |
| exports.debug = function() {}; | |
| exports.inspect = function(obj, showHidden, depth, colors) { | |
| var seen = []; | |
| var stylize = function(str, styleType) { | |
| // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics | |
| var styles = | |
| { 'bold' : [1, 22], | |
| 'italic' : [3, 23], | |
| 'underline' : [4, 24], | |
| 'inverse' : [7, 27], | |
| 'white' : [37, 39], | |
| 'grey' : [90, 39], | |
| 'black' : [30, 39], | |
| 'blue' : [34, 39], | |
| 'cyan' : [36, 39], | |
| 'green' : [32, 39], | |
| 'magenta' : [35, 39], | |
| 'red' : [31, 39], | |
| 'yellow' : [33, 39] }; | |
| var style = | |
| { 'special': 'cyan', | |
| 'number': 'blue', | |
| 'boolean': 'yellow', | |
| 'undefined': 'grey', | |
| 'null': 'bold', | |
| 'string': 'green', | |
| 'date': 'magenta', | |
| // "name": intentionally not styling | |
| 'regexp': 'red' }[styleType]; | |
| if (style) { | |
| return '\u001b[' + styles[style][0] + 'm' + str + | |
| '\u001b[' + styles[style][1] + 'm'; | |
| } else { | |
| return str; | |
| } | |
| }; | |
| if (! colors) { | |
| stylize = function(str, styleType) { return str; }; | |
| } | |
| function format(value, recurseTimes) { | |
| // Provide a hook for user-specified inspect functions. | |
| // Check that value is an object with an inspect function on it | |
| if (value && typeof value.inspect === 'function' && | |
| // Filter out the util module, it's inspect function is special | |
| value !== exports && | |
| // Also filter out any prototype objects using the circular check. | |
| !(value.constructor && value.constructor.prototype === value)) { | |
| return value.inspect(recurseTimes); | |
| } | |
| // Primitive types cannot have properties | |
| switch (typeof value) { | |
| case 'undefined': | |
| return stylize('undefined', 'undefined'); | |
| case 'string': | |
| var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') | |
| .replace(/'/g, "\\'") | |
| .replace(/\\"/g, '"') + '\''; | |
| return stylize(simple, 'string'); | |
| case 'number': | |
| return stylize('' + value, 'number'); | |
| case 'boolean': | |
| return stylize('' + value, 'boolean'); | |
| } | |
| // For some reason typeof null is "object", so special case here. | |
| if (value === null) { | |
| return stylize('null', 'null'); | |
| } | |
| // Look up the keys of the object. | |
| var visible_keys = Object_keys(value); | |
| var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; | |
| // Functions without properties can be shortcutted. | |
| if (typeof value === 'function' && keys.length === 0) { | |
| if (isRegExp(value)) { | |
| return stylize('' + value, 'regexp'); | |
| } else { | |
| var name = value.name ? ': ' + value.name : ''; | |
| return stylize('[Function' + name + ']', 'special'); | |
| } | |
| } | |
| // Dates without properties can be shortcutted | |
| if (isDate(value) && keys.length === 0) { | |
| return stylize(value.toUTCString(), 'date'); | |
| } | |
| var base, type, braces; | |
| // Determine the object type | |
| if (isArray(value)) { | |
| type = 'Array'; | |
| braces = ['[', ']']; | |
| } else { | |
| type = 'Object'; | |
| braces = ['{', '}']; | |
| } | |
| // Make functions say that they are functions | |
| if (typeof value === 'function') { | |
| var n = value.name ? ': ' + value.name : ''; | |
| base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; | |
| } else { | |
| base = ''; | |
| } | |
| // Make dates with properties first say the date | |
| if (isDate(value)) { | |
| base = ' ' + value.toUTCString(); | |
| } | |
| if (keys.length === 0) { | |
| return braces[0] + base + braces[1]; | |
| } | |
| if (recurseTimes < 0) { | |
| if (isRegExp(value)) { | |
| return stylize('' + value, 'regexp'); | |
| } else { | |
| return stylize('[Object]', 'special'); | |
| } | |
| } | |
| seen.push(value); | |
| var output = keys.map(function(key) { | |
| var name, str; | |
| if (value.__lookupGetter__) { | |
| if (value.__lookupGetter__(key)) { | |
| if (value.__lookupSetter__(key)) { | |
| str = stylize('[Getter/Setter]', 'special'); | |
| } else { | |
| str = stylize('[Getter]', 'special'); | |
| } | |
| } else { | |
| if (value.__lookupSetter__(key)) { | |
| str = stylize('[Setter]', 'special'); | |
| } | |
| } | |
| } | |
| if (visible_keys.indexOf(key) < 0) { | |
| name = '[' + key + ']'; | |
| } | |
| if (!str) { | |
| if (seen.indexOf(value[key]) < 0) { | |
| if (recurseTimes === null) { | |
| str = format(value[key]); | |
| } else { | |
| str = format(value[key], recurseTimes - 1); | |
| } | |
| if (str.indexOf('\n') > -1) { | |
| if (isArray(value)) { | |
| str = str.split('\n').map(function(line) { | |
| return ' ' + line; | |
| }).join('\n').substr(2); | |
| } else { | |
| str = '\n' + str.split('\n').map(function(line) { | |
| return ' ' + line; | |
| }).join('\n'); | |
| } | |
| } | |
| } else { | |
| str = stylize('[Circular]', 'special'); | |
| } | |
| } | |
| if (typeof name === 'undefined') { | |
| if (type === 'Array' && key.match(/^\d+$/)) { | |
| return str; | |
| } | |
| name = JSON.stringify('' + key); | |
| if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { | |
| name = name.substr(1, name.length - 2); | |
| name = stylize(name, 'name'); | |
| } else { | |
| name = name.replace(/'/g, "\\'") | |
| .replace(/\\"/g, '"') | |
| .replace(/(^"|"$)/g, "'"); | |
| name = stylize(name, 'string'); | |
| } | |
| } | |
| return name + ': ' + str; | |
| }); | |
| seen.pop(); | |
| var numLinesEst = 0; | |
| var length = output.reduce(function(prev, cur) { | |
| numLinesEst++; | |
| if (cur.indexOf('\n') >= 0) numLinesEst++; | |
| return prev + cur.length + 1; | |
| }, 0); | |
| if (length > 50) { | |
| output = braces[0] + | |
| (base === '' ? '' : base + '\n ') + | |
| ' ' + | |
| output.join(',\n ') + | |
| ' ' + | |
| braces[1]; | |
| } else { | |
| output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; | |
| } | |
| return output; | |
| } | |
| return format(obj, (typeof depth === 'undefined' ? 2 : depth)); | |
| }; | |
| function isArray(ar) { | |
| return Array.isArray(ar) || | |
| (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]'); | |
| } | |
| function isRegExp(re) { | |
| typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'; | |
| } | |
| function isDate(d) { | |
| return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; | |
| } | |
| function pad(n) { | |
| return n < 10 ? '0' + n.toString(10) : n.toString(10); | |
| } | |
| var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', | |
| 'Oct', 'Nov', 'Dec']; | |
| // 26 Feb 16:19:34 | |
| function timestamp() { | |
| var d = new Date(); | |
| var time = [pad(d.getHours()), | |
| pad(d.getMinutes()), | |
| pad(d.getSeconds())].join(':'); | |
| return [d.getDate(), months[d.getMonth()], time].join(' '); | |
| } | |
| exports.log = function (msg) {}; | |
| exports.pump = null; | |
| var Object_keys = Object.keys || function (obj) { | |
| var res = []; | |
| for (var key in obj) res.push(key); | |
| return res; | |
| }; | |
| var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { | |
| var res = []; | |
| for (var key in obj) { | |
| if (Object.hasOwnProperty.call(obj, key)) res.push(key); | |
| } | |
| return res; | |
| }; | |
| var Object_create = Object.create || function (prototype, properties) { | |
| // from es5-shim | |
| var object; | |
| if (prototype === null) { | |
| object = { '__proto__' : null }; | |
| } | |
| else { | |
| if (typeof prototype !== 'object') { | |
| throw new TypeError( | |
| 'typeof prototype[' + (typeof prototype) + '] != \'object\'' | |
| ); | |
| } | |
| var Type = function () {}; | |
| Type.prototype = prototype; | |
| object = new Type(); | |
| object.__proto__ = prototype; | |
| } | |
| if (typeof properties !== 'undefined' && Object.defineProperties) { | |
| Object.defineProperties(object, properties); | |
| } | |
| return object; | |
| }; | |
| exports.inherits = function(ctor, superCtor) { | |
| ctor.super_ = superCtor; | |
| ctor.prototype = Object_create(superCtor.prototype, { | |
| constructor: { | |
| value: ctor, | |
| enumerable: false, | |
| writable: true, | |
| configurable: true | |
| } | |
| }); | |
| }; | |
| var formatRegExp = /%[sdj%]/g; | |
| exports.format = function(f) { | |
| if (typeof f !== 'string') { | |
| var objects = []; | |
| for (var i = 0; i < arguments.length; i++) { | |
| objects.push(exports.inspect(arguments[i])); | |
| } | |
| return objects.join(' '); | |
| } | |
| var i = 1; | |
| var args = arguments; | |
| var len = args.length; | |
| var str = String(f).replace(formatRegExp, function(x) { | |
| if (x === '%%') return '%'; | |
| if (i >= len) return x; | |
| switch (x) { | |
| case '%s': return String(args[i++]); | |
| case '%d': return Number(args[i++]); | |
| case '%j': return JSON.stringify(args[i++]); | |
| default: | |
| return x; | |
| } | |
| }); | |
| for(var x = args[i]; i < len; x = args[++i]){ | |
| if (x === null || typeof x !== 'object') { | |
| str += ' ' + x; | |
| } else { | |
| str += ' ' + exports.inspect(x); | |
| } | |
| } | |
| return str; | |
| }; | |
| },{"events":8}],4:[function(require,module,exports){ | |
| /* | |
| WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based | |
| on @visionmedia's Emitter from UI Kit. | |
| Why? I wanted it standalone. | |
| I also wanted support for wildcard emitters like this: | |
| emitter.on('*', function (eventName, other, event, payloads) { | |
| }); | |
| emitter.on('somenamespace*', function (eventName, payloads) { | |
| }); | |
| Please note that callbacks triggered by wildcard registered events also get | |
| the event name as the first argument. | |
| */ | |
| module.exports = WildEmitter; | |
| function WildEmitter() { | |
| this.callbacks = {}; | |
| } | |
| // Listen on the given `event` with `fn`. Store a group name if present. | |
| WildEmitter.prototype.on = function (event, groupName, fn) { | |
| var hasGroup = (arguments.length === 3), | |
| group = hasGroup ? arguments[1] : undefined, | |
| func = hasGroup ? arguments[2] : arguments[1]; | |
| func._groupName = group; | |
| (this.callbacks[event] = this.callbacks[event] || []).push(func); | |
| return this; | |
| }; | |
| // Adds an `event` listener that will be invoked a single | |
| // time then automatically removed. | |
| WildEmitter.prototype.once = function (event, groupName, fn) { | |
| var self = this, | |
| hasGroup = (arguments.length === 3), | |
| group = hasGroup ? arguments[1] : undefined, | |
| func = hasGroup ? arguments[2] : arguments[1]; | |
| function on() { | |
| self.off(event, on); | |
| func.apply(this, arguments); | |
| } | |
| this.on(event, group, on); | |
| return this; | |
| }; | |
| // Unbinds an entire group | |
| WildEmitter.prototype.releaseGroup = function (groupName) { | |
| var item, i, len, handlers; | |
| for (item in this.callbacks) { | |
| handlers = this.callbacks[item]; | |
| for (i = 0, len = handlers.length; i < len; i++) { | |
| if (handlers[i]._groupName === groupName) { | |
| //console.log('removing'); | |
| // remove it and shorten the array we're looping through | |
| handlers.splice(i, 1); | |
| i--; | |
| len--; | |
| } | |
| } | |
| } | |
| return this; | |
| }; | |
| // Remove the given callback for `event` or all | |
| // registered callbacks. | |
| WildEmitter.prototype.off = function (event, fn) { | |
| var callbacks = this.callbacks[event], | |
| i; | |
| if (!callbacks) return this; | |
| // remove all handlers | |
| if (arguments.length === 1) { | |
| delete this.callbacks[event]; | |
| return this; | |
| } | |
| // remove specific handler | |
| i = callbacks.indexOf(fn); | |
| callbacks.splice(i, 1); | |
| return this; | |
| }; | |
| /// Emit `event` with the given args. | |
| // also calls any `*` handlers | |
| WildEmitter.prototype.emit = function (event) { | |
| var args = [].slice.call(arguments, 1), | |
| callbacks = this.callbacks[event], | |
| specialCallbacks = this.getWildcardCallbacks(event), | |
| i, | |
| len, | |
| item, | |
| listeners; | |
| if (callbacks) { | |
| listeners = callbacks.slice(); | |
| for (i = 0, len = listeners.length; i < len; ++i) { | |
| if (listeners[i]) { | |
| listeners[i].apply(this, args); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| if (specialCallbacks) { | |
| len = specialCallbacks.length; | |
| listeners = specialCallbacks.slice(); | |
| for (i = 0, len = listeners.length; i < len; ++i) { | |
| if (listeners[i]) { | |
| listeners[i].apply(this, [event].concat(args)); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| return this; | |
| }; | |
| // Helper for for finding special wildcard event handlers that match the event | |
| WildEmitter.prototype.getWildcardCallbacks = function (eventName) { | |
| var item, | |
| split, | |
| result = []; | |
| for (item in this.callbacks) { | |
| split = item.split('*'); | |
| if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) { | |
| result = result.concat(this.callbacks[item]); | |
| } | |
| } | |
| return result; | |
| }; | |
| },{}],5:[function(require,module,exports){ | |
| // created by @HenrikJoreteg | |
| var prefix; | |
| if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { | |
| prefix = 'moz'; | |
| } else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { | |
| prefix = 'webkit'; | |
| } | |
| var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; | |
| var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; | |
| var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; | |
| var MediaStream = window.webkitMediaStream || window.MediaStream; | |
| var screenSharing = window.location.protocol === 'https:' && | |
| ((window.navigator.userAgent.match('Chrome') && parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10) >= 26) || | |
| (window.navigator.userAgent.match('Firefox') && parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10) >= 33)); | |
| var AudioContext = window.AudioContext || window.webkitAudioContext; | |
| var supportVp8 = document.createElement('video').canPlayType('video/webm; codecs="vp8", vorbis') === "probably"; | |
| var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia; | |
| // export support flags and constructors.prototype && PC | |
| module.exports = { | |
| prefix: prefix, | |
| support: !!PC && supportVp8 && !!getUserMedia, | |
| // new support style | |
| supportRTCPeerConnection: !!PC, | |
| supportVp8: supportVp8, | |
| supportGetUserMedia: !!getUserMedia, | |
| supportDataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel), | |
| supportWebAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource), | |
| supportMediaStream: !!(MediaStream && MediaStream.prototype.removeTrack), | |
| supportScreenSharing: !!screenSharing, | |
| // old deprecated style. Dont use this anymore | |
| dataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel), | |
| webAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource), | |
| mediaStream: !!(MediaStream && MediaStream.prototype.removeTrack), | |
| screenSharing: !!screenSharing, | |
| // constructors | |
| AudioContext: AudioContext, | |
| PeerConnection: PC, | |
| SessionDescription: SessionDescription, | |
| IceCandidate: IceCandidate, | |
| MediaStream: MediaStream, | |
| getUserMedia: getUserMedia | |
| }; | |
| },{}],6:[function(require,module,exports){ | |
| var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","); | |
| var l = methods.length; | |
| var fn = function () {}; | |
| var mockconsole = {}; | |
| while (l--) { | |
| mockconsole[methods[l]] = fn; | |
| } | |
| module.exports = mockconsole; | |
| },{}],9:[function(require,module,exports){ | |
| // shim for using process in browser | |
| var process = module.exports = {}; | |
| process.nextTick = (function () { | |
| var canSetImmediate = typeof window !== 'undefined' | |
| && window.setImmediate; | |
| var canPost = typeof window !== 'undefined' | |
| && window.postMessage && window.addEventListener | |
| ; | |
| if (canSetImmediate) { | |
| return function (f) { return window.setImmediate(f) }; | |
| } | |
| if (canPost) { | |
| var queue = []; | |
| window.addEventListener('message', function (ev) { | |
| var source = ev.source; | |
| if ((source === window || source === null) && ev.data === 'process-tick') { | |
| ev.stopPropagation(); | |
| if (queue.length > 0) { | |
| var fn = queue.shift(); | |
| fn(); | |
| } | |
| } | |
| }, true); | |
| return function nextTick(fn) { | |
| queue.push(fn); | |
| window.postMessage('process-tick', '*'); | |
| }; | |
| } | |
| return function nextTick(fn) { | |
| setTimeout(fn, 0); | |
| }; | |
| })(); | |
| process.title = 'browser'; | |
| process.browser = true; | |
| process.env = {}; | |
| process.argv = []; | |
| process.binding = function (name) { | |
| throw new Error('process.binding is not supported'); | |
| } | |
| // TODO(shtylman) | |
| process.cwd = function () { return '/' }; | |
| process.chdir = function (dir) { | |
| throw new Error('process.chdir is not supported'); | |
| }; | |
| },{}],8:[function(require,module,exports){ | |
| var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {}; | |
| var EventEmitter = exports.EventEmitter = process.EventEmitter; | |
| var isArray = typeof Array.isArray === 'function' | |
| ? Array.isArray | |
| : function (xs) { | |
| return Object.prototype.toString.call(xs) === '[object Array]' | |
| } | |
| ; | |
| function indexOf (xs, x) { | |
| if (xs.indexOf) return xs.indexOf(x); | |
| for (var i = 0; i < xs.length; i++) { | |
| if (x === xs[i]) return i; | |
| } | |
| return -1; | |
| } | |
| // By default EventEmitters will print a warning if more than | |
| // 10 listeners are added to it. This is a useful default which | |
| // helps finding memory leaks. | |
| // | |
| // Obviously not all Emitters should be limited to 10. This function allows | |
| // that to be increased. Set to zero for unlimited. | |
| var defaultMaxListeners = 10; | |
| EventEmitter.prototype.setMaxListeners = function(n) { | |
| if (!this._events) this._events = {}; | |
| this._events.maxListeners = n; | |
| }; | |
| EventEmitter.prototype.emit = function(type) { | |
| // If there is no 'error' event listener then throw. | |
| if (type === 'error') { | |
| if (!this._events || !this._events.error || | |
| (isArray(this._events.error) && !this._events.error.length)) | |
| { | |
| if (arguments[1] instanceof Error) { | |
| throw arguments[1]; // Unhandled 'error' event | |
| } else { | |
| throw new Error("Uncaught, unspecified 'error' event."); | |
| } | |
| return false; | |
| } | |
| } | |
| if (!this._events) return false; | |
| var handler = this._events[type]; | |
| if (!handler) return false; | |
| if (typeof handler == 'function') { | |
| switch (arguments.length) { | |
| // fast cases | |
| case 1: | |
| handler.call(this); | |
| break; | |
| case 2: | |
| handler.call(this, arguments[1]); | |
| break; | |
| case 3: | |
| handler.call(this, arguments[1], arguments[2]); | |
| break; | |
| // slower | |
| default: | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| handler.apply(this, args); | |
| } | |
| return true; | |
| } else if (isArray(handler)) { | |
| var args = Array.prototype.slice.call(arguments, 1); | |
| var listeners = handler.slice(); | |
| for (var i = 0, l = listeners.length; i < l; i++) { | |
| listeners[i].apply(this, args); | |
| } | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| }; | |
| // EventEmitter is defined in src/node_events.cc | |
| // EventEmitter.prototype.emit() is also defined there. | |
| EventEmitter.prototype.addListener = function(type, listener) { | |
| if ('function' !== typeof listener) { | |
| throw new Error('addListener only takes instances of Function'); | |
| } | |
| if (!this._events) this._events = {}; | |
| // To avoid recursion in the case that type == "newListeners"! Before | |
| // adding it to the listeners, first emit "newListeners". | |
| this.emit('newListener', type, listener); | |
| if (!this._events[type]) { | |
| // Optimize the case of one listener. Don't need the extra array object. | |
| this._events[type] = listener; | |
| } else if (isArray(this._events[type])) { | |
| // Check for listener leak | |
| if (!this._events[type].warned) { | |
| var m; | |
| if (this._events.maxListeners !== undefined) { | |
| m = this._events.maxListeners; | |
| } else { | |
| m = defaultMaxListeners; | |
| } | |
| if (m && m > 0 && this._events[type].length > m) { | |
| this._events[type].warned = true; | |
| console.error('(node) warning: possible EventEmitter memory ' + | |
| 'leak detected. %d listeners added. ' + | |
| 'Use emitter.setMaxListeners() to increase limit.', | |
| this._events[type].length); | |
| console.trace(); | |
| } | |
| } | |
| // If we've already got an array, just append. | |
| this._events[type].push(listener); | |
| } else { | |
| // Adding the second element, need to change to array. | |
| this._events[type] = [this._events[type], listener]; | |
| } | |
| return this; | |
| }; | |
| EventEmitter.prototype.on = EventEmitter.prototype.addListener; | |
| EventEmitter.prototype.once = function(type, listener) { | |
| var self = this; | |
| self.on(type, function g() { | |
| self.removeListener(type, g); | |
| listener.apply(this, arguments); | |
| }); | |
| return this; | |
| }; | |
| EventEmitter.prototype.removeListener = function(type, listener) { | |
| if ('function' !== typeof listener) { | |
| throw new Error('removeListener only takes instances of Function'); | |
| } | |
| // does not use listeners(), so no side effect of creating _events[type] | |
| if (!this._events || !this._events[type]) return this; | |
| var list = this._events[type]; | |
| if (isArray(list)) { | |
| var i = indexOf(list, listener); | |
| if (i < 0) return this; | |
| list.splice(i, 1); | |
| if (list.length == 0) | |
| delete this._events[type]; | |
| } else if (this._events[type] === listener) { | |
| delete this._events[type]; | |
| } | |
| return this; | |
| }; | |
| EventEmitter.prototype.removeAllListeners = function(type) { | |
| if (arguments.length === 0) { | |
| this._events = {}; | |
| return this; | |
| } | |
| // does not use listeners(), so no side effect of creating _events[type] | |
| if (type && this._events && this._events[type]) this._events[type] = null; | |
| return this; | |
| }; | |
| EventEmitter.prototype.listeners = function(type) { | |
| if (!this._events) this._events = {}; | |
| if (!this._events[type]) this._events[type] = []; | |
| if (!isArray(this._events[type])) { | |
| this._events[type] = [this._events[type]]; | |
| } | |
| return this._events[type]; | |
| }; | |
| EventEmitter.listenerCount = function(emitter, type) { | |
| var ret; | |
| if (!emitter._events || !emitter._events[type]) | |
| ret = 0; | |
| else if (typeof emitter._events[type] === 'function') | |
| ret = 1; | |
| else | |
| ret = emitter._events[type].length; | |
| return ret; | |
| }; | |
| },{"__browserify_process":9}],3:[function(require,module,exports){ | |
| var util = require('util'); | |
| var webrtc = require('webrtcsupport'); | |
| var PeerConnection = require('rtcpeerconnection'); | |
| var WildEmitter = require('wildemitter'); | |
| var FileTransfer = require('filetransfer'); | |
| // the inband-v1 protocol is sending metadata inband in a serialized JSON object | |
| // followed by the actual data. Receiver closes the datachannel upon completion | |
| var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1'; | |
| function Peer(options) { | |
| var self = this; | |
| this.id = options.id; | |
| this.parent = options.parent; | |
| this.type = options.type || 'video'; | |
| this.oneway = options.oneway || false; | |
| this.sharemyscreen = options.sharemyscreen || false; | |
| this.browserPrefix = options.prefix; | |
| this.stream = options.stream; | |
| this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels; | |
| this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia; | |
| this.channels = {}; | |
| this.sid = options.sid || Date.now().toString(); | |
| // Create an RTCPeerConnection via the polyfill | |
| this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints); | |
| this.pc.on('ice', this.onIceCandidate.bind(this)); | |
| this.pc.on('offer', function (offer) { | |
| self.send('offer', offer); | |
| }); | |
| this.pc.on('answer', function (offer) { | |
| self.send('answer', offer); | |
| }); | |
| this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this)); | |
| this.pc.on('addChannel', this.handleDataChannelAdded.bind(this)); | |
| this.pc.on('removeStream', this.handleStreamRemoved.bind(this)); | |
| // Just fire negotiation needed events for now | |
| // When browser re-negotiation handling seems to work | |
| // we can use this as the trigger for starting the offer/answer process | |
| // automatically. We'll just leave it be for now while this stabalizes. | |
| this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded')); | |
| this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange')); | |
| this.pc.on('iceConnectionStateChange', function () { | |
| switch (self.pc.iceConnectionState) { | |
| case 'failed': | |
| // currently, in chrome only the initiator goes to failed | |
| // so we need to signal this to the peer | |
| if (self.pc.pc.peerconnection.localDescription.type === 'offer') { | |
| self.parent.emit('iceFailed', self); | |
| self.send('connectivityError'); | |
| } | |
| break; | |
| } | |
| }); | |
| this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange')); | |
| this.logger = this.parent.logger; | |
| // handle screensharing/broadcast mode | |
| if (options.type === 'screen') { | |
| if (this.parent.localScreen && this.sharemyscreen) { | |
| this.logger.log('adding local screen stream to peer connection'); | |
| this.pc.addStream(this.parent.localScreen); | |
| this.broadcaster = options.broadcaster; | |
| } | |
| } else { | |
| this.parent.localStreams.forEach(function (stream) { | |
| self.pc.addStream(stream); | |
| }); | |
| } | |
| // call emitter constructor | |
| WildEmitter.call(this); | |
| this.on('channelOpen', function (channel) { | |
| if (channel.protocol === INBAND_FILETRANSFER_V1) { | |
| channel.onmessage = function (event) { | |
| var metadata = JSON.parse(event.data); | |
| var receiver = new FileTransfer.Receiver(); | |
| receiver.receive(metadata, channel); | |
| self.emit('fileTransfer', metadata, receiver); | |
| receiver.on('receivedFile', function (file, metadata) { | |
| receiver.channel.close(); | |
| }); | |
| }; | |
| } | |
| }); | |
| // proxy events to parent | |
| this.on('*', function () { | |
| self.parent.emit.apply(self.parent, arguments); | |
| }); | |
| } | |
| util.inherits(Peer, WildEmitter); | |
| Peer.prototype.handleMessage = function (message) { | |
| var self = this; | |
| this.logger.log('getting', message.type, message); | |
| if (message.prefix) this.browserPrefix = message.prefix; | |
| if (message.type === 'offer') { | |
| // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1064247 | |
| message.payload.sdp = message.payload.sdp.replace('a=fmtp:0 profile-level-id=0x42e00c;packetization-mode=1\r\n', ''); | |
| this.pc.handleOffer(message.payload, function (err) { | |
| if (err) { | |
| return; | |
| } | |
| // auto-accept | |
| self.pc.answer(self.receiveMedia, function (err, sessionDescription) { | |
| //self.send('answer', sessionDescription); | |
| }); | |
| }); | |
| } else if (message.type === 'answer') { | |
| this.pc.handleAnswer(message.payload); | |
| } else if (message.type === 'candidate') { | |
| this.pc.processIce(message.payload); | |
| } else if (message.type === 'connectivityError') { | |
| this.parent.emit('connectivityError', self); | |
| } else if (message.type === 'mute') { | |
| this.parent.emit('mute', {id: message.from, name: message.payload.name}); | |
| } else if (message.type === 'unmute') { | |
| this.parent.emit('unmute', {id: message.from, name: message.payload.name}); | |
| } | |
| }; | |
| // send via signalling channel | |
| Peer.prototype.send = function (messageType, payload) { | |
| var message = { | |
| to: this.id, | |
| sid: this.sid, | |
| broadcaster: this.broadcaster, | |
| roomType: this.type, | |
| type: messageType, | |
| payload: payload, | |
| prefix: webrtc.prefix | |
| }; | |
| this.logger.log('sending', messageType, message); | |
| this.parent.emit('message', message); | |
| }; | |
| // send via data channel | |
| // returns true when message was sent and false if channel is not open | |
| Peer.prototype.sendDirectly = function (channel, messageType, payload) { | |
| var message = { | |
| type: messageType, | |
| payload: payload | |
| }; | |
| this.logger.log('sending via datachannel', channel, messageType, message); | |
| var dc = this.getDataChannel(channel); | |
| if (dc.readyState != 'open') return false; | |
| dc.send(JSON.stringify(message)); | |
| return true; | |
| }; | |
| // Internal method registering handlers for a data channel and emitting events on the peer | |
| Peer.prototype._observeDataChannel = function (channel) { | |
| var self = this; | |
| channel.onclose = this.emit.bind(this, 'channelClose', channel); | |
| channel.onerror = this.emit.bind(this, 'channelError', channel); | |
| channel.onmessage = function (event) { | |
| self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event); | |
| }; | |
| channel.onopen = this.emit.bind(this, 'channelOpen', channel); | |
| }; | |
| // Fetch or create a data channel by the given name | |
| Peer.prototype.getDataChannel = function (name, opts) { | |
| if (!webrtc.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported')); | |
| var channel = this.channels[name]; | |
| opts || (opts = {}); | |
| if (channel) return channel; | |
| // if we don't have one by this label, create it | |
| channel = this.channels[name] = this.pc.createDataChannel(name, opts); | |
| this._observeDataChannel(channel); | |
| return channel; | |
| }; | |
| Peer.prototype.onIceCandidate = function (candidate) { | |
| if (this.closed) return; | |
| if (candidate) { | |
| this.send('candidate', candidate); | |
| } else { | |
| this.logger.log("End of candidates."); | |
| } | |
| }; | |
| Peer.prototype.start = function () { | |
| var self = this; | |
| // well, the webrtc api requires that we either | |
| // a) create a datachannel a priori | |
| // b) do a renegotiation later to add the SCTP m-line | |
| // Let's do (a) first... | |
| if (this.enableDataChannels) { | |
| this.getDataChannel('simplewebrtc'); | |
| } | |
| this.pc.offer(this.receiveMedia, function (err, sessionDescription) { | |
| //self.send('offer', sessionDescription); | |
| }); | |
| }; | |
| Peer.prototype.icerestart = function () { | |
| var constraints = this.receiveMedia; | |
| constraints.mandatory.IceRestart = true; | |
| this.pc.offer(constraints, function (err, success) { }); | |
| }; | |
| Peer.prototype.end = function () { | |
| if (this.closed) return; | |
| this.pc.close(); | |
| this.handleStreamRemoved(); | |
| }; | |
| Peer.prototype.handleRemoteStreamAdded = function (event) { | |
| var self = this; | |
| if (this.stream) { | |
| this.logger.warn('Already have a remote stream'); | |
| } else { | |
| this.stream = event.stream; | |
| // FIXME: addEventListener('ended', ...) would be nicer | |
| // but does not work in firefox | |
| this.stream.onended = function () { | |
| self.end(); | |
| }; | |
| this.parent.emit('peerStreamAdded', this); | |
| } | |
| }; | |
| Peer.prototype.handleStreamRemoved = function () { | |
| this.parent.peers.splice(this.parent.peers.indexOf(this), 1); | |
| this.closed = true; | |
| this.parent.emit('peerStreamRemoved', this); | |
| }; | |
| Peer.prototype.handleDataChannelAdded = function (channel) { | |
| this.channels[channel.label] = channel; | |
| this._observeDataChannel(channel); | |
| }; | |
| Peer.prototype.sendFile = function (file) { | |
| var sender = new FileTransfer.Sender(); | |
| var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), { | |
| protocol: INBAND_FILETRANSFER_V1 | |
| }); | |
| // override onopen | |
| dc.onopen = function () { | |
| dc.send(JSON.stringify({ | |
| size: file.size, | |
| name: file.name | |
| })); | |
| sender.send(file, dc); | |
| }; | |
| // override onclose | |
| dc.onclose = function () { | |
| console.log('sender received transfer'); | |
| sender.emit('complete'); | |
| }; | |
| return sender; | |
| }; | |
| module.exports = Peer; | |
| },{"filetransfer":11,"rtcpeerconnection":10,"util":2,"webrtcsupport":5,"wildemitter":4}],12:[function(require,module,exports){ | |
| // getUserMedia helper by @HenrikJoreteg | |
| var func = (window.navigator.getUserMedia || | |
| window.navigator.webkitGetUserMedia || | |
| window.navigator.mozGetUserMedia || | |
| window.navigator.msGetUserMedia); | |
| module.exports = function (constraints, cb) { | |
| var options, error; | |
| var haveOpts = arguments.length === 2; | |
| var defaultOpts = {video: true, audio: true}; | |
| var denied = 'PermissionDeniedError'; | |
| var notSatisfied = 'ConstraintNotSatisfiedError'; | |
| // make constraints optional | |
| if (!haveOpts) { | |
| cb = constraints; | |
| constraints = defaultOpts; | |
| } | |
| // treat lack of browser support like an error | |
| if (!func) { | |
| // throw proper error per spec | |
| error = new Error('MediaStreamError'); | |
| error.name = 'NotSupportedError'; | |
| // keep all callbacks async | |
| return window.setTimeout(function () { | |
| cb(error); | |
| }, 0); | |
| } | |
| // make requesting media from non-http sources trigger an error | |
| // current browsers silently drop the request instead | |
| var protocol = window.location.protocol; | |
| if (protocol !== 'http:' && protocol !== 'https:') { | |
| error = new Error('MediaStreamError'); | |
| error.name = 'NotSupportedError'; | |
| // keep all callbacks async | |
| return window.setTimeout(function () { | |
| cb(error); | |
| }, 0); | |
| } | |
| // normalize error handling when no media types are requested | |
| if (!constraints.audio && !constraints.video) { | |
| error = new Error('MediaStreamError'); | |
| error.name = 'NoMediaRequestedError'; | |
| // keep all callbacks async | |
| return window.setTimeout(function () { | |
| cb(error); | |
| }, 0); | |
| } | |
| if (localStorage && localStorage.useFirefoxFakeDevice === "true") { | |
| constraints.fake = true; | |
| } | |
| func.call(window.navigator, constraints, function (stream) { | |
| cb(null, stream); | |
| }, function (err) { | |
| var error; | |
| // coerce into an error object since FF gives us a string | |
| // there are only two valid names according to the spec | |
| // we coerce all non-denied to "constraint not satisfied". | |
| if (typeof err === 'string') { | |
| error = new Error('MediaStreamError'); | |
| if (err === denied) { | |
| error.name = denied; | |
| } else { | |
| error.name = notSatisfied; | |
| } | |
| } else { | |
| // if we get an error object make sure '.name' property is set | |
| // according to spec: http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermediaerror-and-navigatorusermediaerrorcallback | |
| error = err; | |
| if (!error.name) { | |
| // this is likely chrome which | |
| // sets a property called "ERROR_DENIED" on the error object | |
| // if so we make sure to set a name | |
| if (error[denied]) { | |
| err.name = denied; | |
| } else { | |
| err.name = notSatisfied; | |
| } | |
| } | |
| } | |
| cb(error); | |
| }); | |
| }; | |
| },{}],7:[function(require,module,exports){ | |
| var util = require('util'); | |
| var hark = require('hark'); | |
| var webrtc = require('webrtcsupport'); | |
| var getUserMedia = require('getusermedia'); | |
| var getScreenMedia = require('getscreenmedia'); | |
| var WildEmitter = require('wildemitter'); | |
| var GainController = require('mediastream-gain'); | |
| var mockconsole = require('mockconsole'); | |
| function LocalMedia(opts) { | |
| WildEmitter.call(this); | |
| var config = this.config = { | |
| autoAdjustMic: false, | |
| detectSpeakingEvents: true, | |
| media: { | |
| audio: true, | |
| video: true | |
| }, | |
| logger: mockconsole | |
| }; | |
| var item; | |
| for (item in opts) { | |
| this.config[item] = opts[item]; | |
| } | |
| this.logger = config.logger; | |
| this._log = this.logger.log.bind(this.logger, 'LocalMedia:'); | |
| this._logerror = this.logger.error.bind(this.logger, 'LocalMedia:'); | |
| this.screenSharingSupport = webrtc.screenSharing; | |
| this.localStreams = []; | |
| this.localScreens = []; | |
| if (!webrtc.support) { | |
| this._logerror('Your browser does not support local media capture.'); | |
| } | |
| } | |
| util.inherits(LocalMedia, WildEmitter); | |
| LocalMedia.prototype.start = function (mediaConstraints, cb) { | |
| var self = this; | |
| var constraints = mediaConstraints || this.config.media; | |
| getUserMedia(constraints, function (err, stream) { | |
| if (!err) { | |
| if (constraints.audio && self.config.detectSpeakingEvents) { | |
| self.setupAudioMonitor(stream, self.config.harkOptions); | |
| } | |
| self.localStreams.push(stream); | |
| if (self.config.autoAdjustMic) { | |
| self.gainController = new GainController(stream); | |
| // start out somewhat muted if we can track audio | |
| self.setMicIfEnabled(0.5); | |
| } | |
| // TODO: might need to migrate to the video tracks onended | |
| // FIXME: firefox does not seem to trigger this... | |
| stream.onended = function () { | |
| /* | |
| var idx = self.localStreams.indexOf(stream); | |
| if (idx > -1) { | |
| self.localScreens.splice(idx, 1); | |
| } | |
| self.emit('localStreamStopped', stream); | |
| */ | |
| }; | |
| self.emit('localStream', stream); | |
| } | |
| if (cb) { | |
| return cb(err, stream); | |
| } | |
| }); | |
| }; | |
| LocalMedia.prototype.stop = function (stream) { | |
| var self = this; | |
| // FIXME: duplicates cleanup code until fixed in FF | |
| if (stream) { | |
| stream.stop(); | |
| self.emit('localStreamStopped', stream); | |
| var idx = self.localStreams.indexOf(stream); | |
| if (idx > -1) { | |
| self.localStreams = self.localStreams.splice(idx, 1); | |
| } | |
| } else { | |
| if (this.audioMonitor) { | |
| this.audioMonitor.stop(); | |
| delete this.audioMonitor; | |
| } | |
| this.localStreams.forEach(function (stream) { | |
| stream.stop(); | |
| self.emit('localStreamStopped', stream); | |
| }); | |
| this.localStreams = []; | |
| } | |
| }; | |
| LocalMedia.prototype.startScreenShare = function (cb) { | |
| var self = this; | |
| getScreenMedia(function (err, stream) { | |
| if (!err) { | |
| self.localScreens.push(stream); | |
| // TODO: might need to migrate to the video tracks onended | |
| // Firefox does not support .onended but it does not support | |
| // screensharing either | |
| stream.onended = function () { | |
| var idx = self.localScreens.indexOf(stream); | |
| if (idx > -1) { | |
| self.localScreens.splice(idx, 1); | |
| } | |
| self.emit('localScreenStopped', stream); | |
| }; | |
| self.emit('localScreen', stream); | |
| } | |
| // enable the callback | |
| if (cb) { | |
| return cb(err, stream); | |
| } | |
| }); | |
| }; | |
| LocalMedia.prototype.stopScreenShare = function (stream) { | |
| if (stream) { | |
| stream.stop(); | |
| } else { | |
| this.localScreens.forEach(function (stream) { | |
| stream.stop(); | |
| }); | |
| this.localScreens = []; | |
| } | |
| }; | |
| // Audio controls | |
| LocalMedia.prototype.mute = function () { | |
| this._audioEnabled(false); | |
| this.hardMuted = true; | |
| this.emit('audioOff'); | |
| }; | |
| LocalMedia.prototype.unmute = function () { | |
| this._audioEnabled(true); | |
| this.hardMuted = false; | |
| this.emit('audioOn'); | |
| }; | |
| LocalMedia.prototype.setupAudioMonitor = function (stream, harkOptions) { | |
| this._log('Setup audio'); | |
| var audio = this.audioMonitor = hark(stream, harkOptions); | |
| var self = this; | |
| var timeout; | |
| audio.on('speaking', function () { | |
| self.emit('speaking'); | |
| if (self.hardMuted) { | |
| return; | |
| } | |
| self.setMicIfEnabled(1); | |
| }); | |
| audio.on('stopped_speaking', function () { | |
| if (timeout) { | |
| clearTimeout(timeout); | |
| } | |
| timeout = setTimeout(function () { | |
| self.emit('stoppedSpeaking'); | |
| if (self.hardMuted) { | |
| return; | |
| } | |
| self.setMicIfEnabled(0.5); | |
| }, 1000); | |
| }); | |
| audio.on('volume_change', function (volume, treshold) { | |
| self.emit('volumeChange', volume, treshold); | |
| }); | |
| }; | |
| // We do this as a seperate method in order to | |
| // still leave the "setMicVolume" as a working | |
| // method. | |
| LocalMedia.prototype.setMicIfEnabled = function (volume) { | |
| if (!this.config.autoAdjustMic) { | |
| return; | |
| } | |
| this.gainController.setGain(volume); | |
| }; | |
| // Video controls | |
| LocalMedia.prototype.pauseVideo = function () { | |
| this._videoEnabled(false); | |
| this.emit('videoOff'); | |
| }; | |
| LocalMedia.prototype.resumeVideo = function () { | |
| this._videoEnabled(true); | |
| this.emit('videoOn'); | |
| }; | |
| // Combined controls | |
| LocalMedia.prototype.pause = function () { | |
| this.mute(); | |
| this.pauseVideo(); | |
| }; | |
| LocalMedia.prototype.resume = function () { | |
| this.unmute(); | |
| this.resumeVideo(); | |
| }; | |
| // Internal methods for enabling/disabling audio/video | |
| LocalMedia.prototype._audioEnabled = function (bool) { | |
| // work around for chrome 27 bug where disabling tracks | |
| // doesn't seem to work (works in canary, remove when working) | |
| this.setMicIfEnabled(bool ? 1 : 0); | |
| this.localStreams.forEach(function (stream) { | |
| stream.getAudioTracks().forEach(function (track) { | |
| track.enabled = !!bool; | |
| }); | |
| }); | |
| }; | |
| LocalMedia.prototype._videoEnabled = function (bool) { | |
| this.localStreams.forEach(function (stream) { | |
| stream.getVideoTracks().forEach(function (track) { | |
| track.enabled = !!bool; | |
| }); | |
| }); | |
| }; | |
| // check if all audio streams are enabled | |
| LocalMedia.prototype.isAudioEnabled = function () { | |
| var enabled = true; | |
| this.localStreams.forEach(function (stream) { | |
| stream.getAudioTracks().forEach(function (track) { | |
| enabled = enabled && track.enabled; | |
| }); | |
| }); | |
| return enabled; | |
| }; | |
| // check if all video streams are enabled | |
| LocalMedia.prototype.isVideoEnabled = function () { | |
| var enabled = true; | |
| this.localStreams.forEach(function (stream) { | |
| stream.getVideoTracks().forEach(function (track) { | |
| enabled = enabled && track.enabled; | |
| }); | |
| }); | |
| return enabled; | |
| }; | |
| // Backwards Compat | |
| LocalMedia.prototype.startLocalMedia = LocalMedia.prototype.start; | |
| LocalMedia.prototype.stopLocalMedia = LocalMedia.prototype.stop; | |
| // fallback for old .localStream behaviour | |
| Object.defineProperty(LocalMedia.prototype, 'localStream', { | |
| get: function () { | |
| return this.localStreams.length > 0 ? this.localStreams[0] : null; | |
| } | |
| }); | |
| // fallback for old .localScreen behaviour | |
| Object.defineProperty(LocalMedia.prototype, 'localScreen', { | |
| get: function () { | |
| return this.localScreens.length > 0 ? this.localScreens[0] : null; | |
| } | |
| }); | |
| module.exports = LocalMedia; | |
| },{"getscreenmedia":14,"getusermedia":12,"hark":13,"mediastream-gain":15,"mockconsole":6,"util":2,"webrtcsupport":5,"wildemitter":4}],16:[function(require,module,exports){ | |
| // Underscore.js 1.8.2 | |
| // http://underscorejs.org | |
| // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors | |
| // Underscore may be freely distributed under the MIT license. | |
| (function() { | |
| // Baseline setup | |
| // -------------- | |
| // Establish the root object, `window` in the browser, or `exports` on the server. | |
| var root = this; | |
| // Save the previous value of the `_` variable. | |
| var previousUnderscore = root._; | |
| // Save bytes in the minified (but not gzipped) version: | |
| var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; | |
| // Create quick reference variables for speed access to core prototypes. | |
| var | |
| push = ArrayProto.push, | |
| slice = ArrayProto.slice, | |
| toString = ObjProto.toString, | |
| hasOwnProperty = ObjProto.hasOwnProperty; | |
| // All **ECMAScript 5** native function implementations that we hope to use | |
| // are declared here. | |
| var | |
| nativeIsArray = Array.isArray, | |
| nativeKeys = Object.keys, | |
| nativeBind = FuncProto.bind, | |
| nativeCreate = Object.create; | |
| // Naked function reference for surrogate-prototype-swapping. | |
| var Ctor = function(){}; | |
| // Create a safe reference to the Underscore object for use below. | |
| var _ = function(obj) { | |
| if (obj instanceof _) return obj; | |
| if (!(this instanceof _)) return new _(obj); | |
| this._wrapped = obj; | |
| }; | |
| // Export the Underscore object for **Node.js**, with | |
| // backwards-compatibility for the old `require()` API. If we're in | |
| // the browser, add `_` as a global object. | |
| if (typeof exports !== 'undefined') { | |
| if (typeof module !== 'undefined' && module.exports) { | |
| exports = module.exports = _; | |
| } | |
| exports._ = _; | |
| } else { | |
| root._ = _; | |
| } | |
| // Current version. | |
| _.VERSION = '1.8.2'; | |
| // Internal function that returns an efficient (for current engines) version | |
| // of the passed-in callback, to be repeatedly applied in other Underscore | |
| // functions. | |
| var optimizeCb = function(func, context, argCount) { | |
| if (context === void 0) return func; | |
| switch (argCount == null ? 3 : argCount) { | |
| case 1: return function(value) { | |
| return func.call(context, value); | |
| }; | |
| case 2: return function(value, other) { | |
| return func.call(context, value, other); | |
| }; | |
| case 3: return function(value, index, collection) { | |
| return func.call(context, value, index, collection); | |
| }; | |
| case 4: return function(accumulator, value, index, collection) { | |
| return func.call(context, accumulator, value, index, collection); | |
| }; | |
| } | |
| return function() { | |
| return func.apply(context, arguments); | |
| }; | |
| }; | |
| // A mostly-internal function to generate callbacks that can be applied | |
| // to each element in a collection, returning the desired result — either | |
| // identity, an arbitrary callback, a property matcher, or a property accessor. | |
| var cb = function(value, context, argCount) { | |
| if (value == null) return _.identity; | |
| if (_.isFunction(value)) return optimizeCb(value, context, argCount); | |
| if (_.isObject(value)) return _.matcher(value); | |
| return _.property(value); | |
| }; | |
| _.iteratee = function(value, context) { | |
| return cb(value, context, Infinity); | |
| }; | |
| // An internal function for creating assigner functions. | |
| var createAssigner = function(keysFunc, undefinedOnly) { | |
| return function(obj) { | |
| var length = arguments.length; | |
| if (length < 2 || obj == null) return obj; | |
| for (var index = 1; index < length; index++) { | |
| var source = arguments[index], | |
| keys = keysFunc(source), | |
| l = keys.length; | |
| for (var i = 0; i < l; i++) { | |
| var key = keys[i]; | |
| if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; | |
| } | |
| } | |
| return obj; | |
| }; | |
| }; | |
| // An internal function for creating a new object that inherits from another. | |
| var baseCreate = function(prototype) { | |
| if (!_.isObject(prototype)) return {}; | |
| if (nativeCreate) return nativeCreate(prototype); | |
| Ctor.prototype = prototype; | |
| var result = new Ctor; | |
| Ctor.prototype = null; | |
| return result; | |
| }; | |
| // Helper for collection methods to determine whether a collection | |
| // should be iterated as an array or as an object | |
| // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength | |
| var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; | |
| var isArrayLike = function(collection) { | |
| var length = collection && collection.length; | |
| return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; | |
| }; | |
| // Collection Functions | |
| // -------------------- | |
| // The cornerstone, an `each` implementation, aka `forEach`. | |
| // Handles raw objects in addition to array-likes. Treats all | |
| // sparse array-likes as if they were dense. | |
| _.each = _.forEach = function(obj, iteratee, context) { | |
| iteratee = optimizeCb(iteratee, context); | |
| var i, length; | |
| if (isArrayLike(obj)) { | |
| for (i = 0, length = obj.length; i < length; i++) { | |
| iteratee(obj[i], i, obj); | |
| } | |
| } else { | |
| var keys = _.keys(obj); | |
| for (i = 0, length = keys.length; i < length; i++) { | |
| iteratee(obj[keys[i]], keys[i], obj); | |
| } | |
| } | |
| return obj; | |
| }; | |
| // Return the results of applying the iteratee to each element. | |
| _.map = _.collect = function(obj, iteratee, context) { | |
| iteratee = cb(iteratee, context); | |
| var keys = !isArrayLike(obj) && _.keys(obj), | |
| length = (keys || obj).length, | |
| results = Array(length); | |
| for (var index = 0; index < length; index++) { | |
| var currentKey = keys ? keys[index] : index; | |
| results[index] = iteratee(obj[currentKey], currentKey, obj); | |
| } | |
| return results; | |
| }; | |
| // Create a reducing function iterating left or right. | |
| function createReduce(dir) { | |
| // Optimized iterator function as using arguments.length | |
| // in the main function will deoptimize the, see #1991. | |
| function iterator(obj, iteratee, memo, keys, index, length) { | |
| for (; index >= 0 && index < length; index += dir) { | |
| var currentKey = keys ? keys[index] : index; | |
| memo = iteratee(memo, obj[currentKey], currentKey, obj); | |
| } | |
| return memo; | |
| } | |
| return function(obj, iteratee, memo, context) { | |
| iteratee = optimizeCb(iteratee, context, 4); | |
| var keys = !isArrayLike(obj) && _.keys(obj), | |
| length = (keys || obj).length, | |
| index = dir > 0 ? 0 : length - 1; | |
| // Determine the initial value if none is provided. | |
| if (arguments.length < 3) { | |
| memo = obj[keys ? keys[index] : index]; | |
| index += dir; | |
| } | |
| return iterator(obj, iteratee, memo, keys, index, length); | |
| }; | |
| } | |
| // **Reduce** builds up a single result from a list of values, aka `inject`, | |
| // or `foldl`. | |
| _.reduce = _.foldl = _.inject = createReduce(1); | |
| // The right-associative version of reduce, also known as `foldr`. | |
| _.reduceRight = _.foldr = createReduce(-1); | |
| // Return the first value which passes a truth test. Aliased as `detect`. | |
| _.find = _.detect = function(obj, predicate, context) { | |
| var key; | |
| if (isArrayLike(obj)) { | |
| key = _.findIndex(obj, predicate, context); | |
| } else { | |
| key = _.findKey(obj, predicate, context); | |
| } | |
| if (key !== void 0 && key !== -1) return obj[key]; | |
| }; | |
| // Return all the elements that pass a truth test. | |
| // Aliased as `select`. | |
| _.filter = _.select = function(obj, predicate, context) { | |
| var results = []; | |
| predicate = cb(predicate, context); | |
| _.each(obj, function(value, index, list) { | |
| if (predicate(value, index, list)) results.push(value); | |
| }); | |
| return results; | |
| }; | |
| // Return all the elements for which a truth test fails. | |
| _.reject = function(obj, predicate, context) { | |
| return _.filter(obj, _.negate(cb(predicate)), context); | |
| }; | |
| // Determine whether all of the elements match a truth test. | |
| // Aliased as `all`. | |
| _.every = _.all = function(obj, predicate, context) { | |
| predicate = cb(predicate, context); | |
| var keys = !isArrayLike(obj) && _.keys(obj), | |
| length = (keys || obj).length; | |
| for (var index = 0; index < length; index++) { | |
| var currentKey = keys ? keys[index] : index; | |
| if (!predicate(obj[currentKey], currentKey, obj)) return false; | |
| } | |
| return true; | |
| }; | |
| // Determine if at least one element in the object matches a truth test. | |
| // Aliased as `any`. | |
| _.some = _.any = function(obj, predicate, context) { | |
| predicate = cb(predicate, context); | |
| var keys = !isArrayLike(obj) && _.keys(obj), | |
| length = (keys || obj).length; | |
| for (var index = 0; index < length; index++) { | |
| var currentKey = keys ? keys[index] : index; | |
| if (predicate(obj[currentKey], currentKey, obj)) return true; | |
| } | |
| return false; | |
| }; | |
| // Determine if the array or object contains a given value (using `===`). | |
| // Aliased as `includes` and `include`. | |
| _.contains = _.includes = _.include = function(obj, target, fromIndex) { | |
| if (!isArrayLike(obj)) obj = _.values(obj); | |
| return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; | |
| }; | |
| // Invoke a method (with arguments) on every item in a collection. | |
| _.invoke = function(obj, method) { | |
| var args = slice.call(arguments, 2); | |
| var isFunc = _.isFunction(method); | |
| return _.map(obj, function(value) { | |
| var func = isFunc ? method : value[method]; | |
| return func == null ? func : func.apply(value, args); | |
| }); | |
| }; | |
| // Convenience version of a common use case of `map`: fetching a property. | |
| _.pluck = function(obj, key) { | |
| return _.map(obj, _.property(key)); | |
| }; | |
| // Convenience version of a common use case of `filter`: selecting only objects | |
| // containing specific `key:value` pairs. | |
| _.where = function(obj, attrs) { | |
| return _.filter(obj, _.matcher(attrs)); | |
| }; | |
| // Convenience version of a common use case of `find`: getting the first object | |
| // containing specific `key:value` pairs. | |
| _.findWhere = function(obj, attrs) { | |
| return _.find(obj, _.matcher(attrs)); | |
| }; | |
| // Return the maximum element (or element-based computation). | |
| _.max = function(obj, iteratee, context) { | |
| var result = -Infinity, lastComputed = -Infinity, | |
| value, computed; | |
| if (iteratee == null && obj != null) { | |
| obj = isArrayLike(obj) ? obj : _.values(obj); | |
| for (var i = 0, length = obj.length; i < length; i++) { | |
| value = obj[i]; | |
| if (value > result) { | |
| result = value; | |
| } | |
| } | |
| } else { | |
| iteratee = cb(iteratee, context); | |
| _.each(obj, function(value, index, list) { | |
| computed = iteratee(value, index, list); | |
| if (computed > lastComputed || computed === -Infinity && result === -Infinity) { | |
| result = value; | |
| lastComputed = computed; | |
| } | |
| }); | |
| } | |
| return result; | |
| }; | |
| // Return the minimum element (or element-based computation). | |
| _.min = function(obj, iteratee, context) { | |
| var result = Infinity, lastComputed = Infinity, | |
| value, computed; | |
| if (iteratee == null && obj != null) { | |
| obj = isArrayLike(obj) ? obj : _.values(obj); | |
| for (var i = 0, length = obj.length; i < length; i++) { | |
| value = obj[i]; | |
| if (value < result) { | |
| result = value; | |
| } | |
| } | |
| } else { | |
| iteratee = cb(iteratee, context); | |
| _.each(obj, function(value, index, list) { | |
| computed = iteratee(value, index, list); | |
| if (computed < lastComputed || computed === Infinity && result === Infinity) { | |
| result = value; | |
| lastComputed = computed; | |
| } | |
| }); | |
| } | |
| return result; | |
| }; | |
| // Shuffle a collection, using the modern version of the | |
| // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). | |
| _.shuffle = function(obj) { | |
| var set = isArrayLike(obj) ? obj : _.values(obj); | |
| var length = set.length; | |
| var shuffled = Array(length); | |
| for (var index = 0, rand; index < length; index++) { | |
| rand = _.random(0, index); | |
| if (rand !== index) shuffled[index] = shuffled[rand]; | |
| shuffled[rand] = set[index]; | |
| } | |
| return shuffled; | |
| }; | |
| // Sample **n** random values from a collection. | |
| // If **n** is not specified, returns a single random element. | |
| // The internal `guard` argument allows it to work with `map`. | |
| _.sample = function(obj, n, guard) { | |
| if (n == null || guard) { | |
| if (!isArrayLike(obj)) obj = _.values(obj); | |
| return obj[_.random(obj.length - 1)]; | |
| } | |
| return _.shuffle(obj).slice(0, Math.max(0, n)); | |
| }; | |
| // Sort the object's values by a criterion produced by an iteratee. | |
| _.sortBy = function(obj, iteratee, context) { | |
| iteratee = cb(iteratee, context); | |
| return _.pluck(_.map(obj, function(value, index, list) { | |
| return { | |
| value: value, | |
| index: index, | |
| criteria: iteratee(value, index, list) | |
| }; | |
| }).sort(function(left, right) { | |
| var a = left.criteria; | |
| var b = right.criteria; | |
| if (a !== b) { | |
| if (a > b || a === void 0) return 1; | |
| if (a < b || b === void 0) return -1; | |
| } | |
| return left.index - right.index; | |
| }), 'value'); | |
| }; | |
| // An internal function used for aggregate "group by" operations. | |
| var group = function(behavior) { | |
| return function(obj, iteratee, context) { | |
| var result = {}; | |
| iteratee = cb(iteratee, context); | |
| _.each(obj, function(value, index) { | |
| var key = iteratee(value, index, obj); | |
| behavior(result, value, key); | |
| }); | |
| return result; | |
| }; | |
| }; | |
| // Groups the object's values by a criterion. Pass either a string attribute | |
| // to group by, or a function that returns the criterion. | |
| _.groupBy = group(function(result, value, key) { | |
| if (_.has(result, key)) result[key].push(value); else result[key] = [value]; | |
| }); | |
| // Indexes the object's values by a criterion, similar to `groupBy`, but for | |
| // when you know that your index values will be unique. | |
| _.indexBy = group(function(result, value, key) { | |
| result[key] = value; | |
| }); | |
| // Counts instances of an object that group by a certain criterion. Pass | |
| // either a string attribute to count by, or a function that returns the | |
| // criterion. | |
| _.countBy = group(function(result, value, key) { | |
| if (_.has(result, key)) result[key]++; else result[key] = 1; | |
| }); | |
| // Safely create a real, live array from anything iterable. | |
| _.toArray = function(obj) { | |
| if (!obj) return []; | |
| if (_.isArray(obj)) return slice.call(obj); | |
| if (isArrayLike(obj)) return _.map(obj, _.identity); | |
| return _.values(obj); | |
| }; | |
| // Return the number of elements in an object. | |
| _.size = function(obj) { | |
| if (obj == null) return 0; | |
| return isArrayLike(obj) ? obj.length : _.keys(obj).length; | |
| }; | |
| // Split a collection into two arrays: one whose elements all satisfy the given | |
| // predicate, and one whose elements all do not satisfy the predicate. | |
| _.partition = function(obj, predicate, context) { | |
| predicate = cb(predicate, context); | |
| var pass = [], fail = []; | |
| _.each(obj, function(value, key, obj) { | |
| (predicate(value, key, obj) ? pass : fail).push(value); | |
| }); | |
| return [pass, fail]; | |
| }; | |
| // Array Functions | |
| // --------------- | |
| // Get the first element of an array. Passing **n** will return the first N | |
| // values in the array. Aliased as `head` and `take`. The **guard** check | |
| // allows it to work with `_.map`. | |
| _.first = _.head = _.take = function(array, n, guard) { | |
| if (array == null) return void 0; | |
| if (n == null || guard) return array[0]; | |
| return _.initial(array, array.length - n); | |
| }; | |
| // Returns everything but the last entry of the array. Especially useful on | |
| // the arguments object. Passing **n** will return all the values in | |
| // the array, excluding the last N. | |
| _.initial = function(array, n, guard) { | |
| return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); | |
| }; | |
| // Get the last element of an array. Passing **n** will return the last N | |
| // values in the array. | |
| _.last = function(array, n, guard) { | |
| if (array == null) return void 0; | |
| if (n == null || guard) return array[array.length - 1]; | |
| return _.rest(array, Math.max(0, array.length - n)); | |
| }; | |
| // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. | |
| // Especially useful on the arguments object. Passing an **n** will return | |
| // the rest N values in the array. | |
| _.rest = _.tail = _.drop = function(array, n, guard) { | |
| return slice.call(array, n == null || guard ? 1 : n); | |
| }; | |
| // Trim out all falsy values from an array. | |
| _.compact = function(array) { | |
| return _.filter(array, _.identity); | |
| }; | |
| // Internal implementation of a recursive `flatten` function. | |
| var flatten = function(input, shallow, strict, startIndex) { | |
| var output = [], idx = 0; | |
| for (var i = startIndex || 0, length = input && input.length; i < length; i++) { | |
| var value = input[i]; | |
| if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { | |
| //flatten current level of array or arguments object | |
| if (!shallow) value = flatten(value, shallow, strict); | |
| var j = 0, len = value.length; | |
| output.length += len; | |
| while (j < len) { | |
| output[idx++] = value[j++]; | |
| } | |
| } else if (!strict) { | |
| output[idx++] = value; | |
| } | |
| } | |
| return output; | |
| }; | |
| // Flatten out an array, either recursively (by default), or just one level. | |
| _.flatten = function(array, shallow) { | |
| return flatten(array, shallow, false); | |
| }; | |
| // Return a version of the array that does not contain the specified value(s). | |
| _.without = function(array) { | |
| return _.difference(array, slice.call(arguments, 1)); | |
| }; | |
| // Produce a duplicate-free version of the array. If the array has already | |
| // been sorted, you have the option of using a faster algorithm. | |
| // Aliased as `unique`. | |
| _.uniq = _.unique = function(array, isSorted, iteratee, context) { | |
| if (array == null) return []; | |
| if (!_.isBoolean(isSorted)) { | |
| context = iteratee; | |
| iteratee = isSorted; | |
| isSorted = false; | |
| } | |
| if (iteratee != null) iteratee = cb(iteratee, context); | |
| var result = []; | |
| var seen = []; | |
| for (var i = 0, length = array.length; i < length; i++) { | |
| var value = array[i], | |
| computed = iteratee ? iteratee(value, i, array) : value; | |
| if (isSorted) { | |
| if (!i || seen !== computed) result.push(value); | |
| seen = computed; | |
| } else if (iteratee) { | |
| if (!_.contains(seen, computed)) { | |
| seen.push(computed); | |
| result.push(value); | |
| } | |
| } else if (!_.contains(result, value)) { | |
| result.push(value); | |
| } | |
| } | |
| return result; | |
| }; | |
| // Produce an array that contains the union: each distinct element from all of | |
| // the passed-in arrays. | |
| _.union = function() { | |
| return _.uniq(flatten(arguments, true, true)); | |
| }; | |
| // Produce an array that contains every item shared between all the | |
| // passed-in arrays. | |
| _.intersection = function(array) { | |
| if (array == null) return []; | |
| var result = []; | |
| var argsLength = arguments.length; | |
| for (var i = 0, length = array.length; i < length; i++) { | |
| var item = array[i]; | |
| if (_.contains(result, item)) continue; | |
| for (var j = 1; j < argsLength; j++) { | |
| if (!_.contains(arguments[j], item)) break; | |
| } | |
| if (j === argsLength) result.push(item); | |
| } | |
| return result; | |
| }; | |
| // Take the difference between one array and a number of other arrays. | |
| // Only the elements present in just the first array will remain. | |
| _.difference = function(array) { | |
| var rest = flatten(arguments, true, true, 1); | |
| return _.filter(array, function(value){ | |
| return !_.contains(rest, value); | |
| }); | |
| }; | |
| // Zip together multiple lists into a single array -- elements that share | |
| // an index go together. | |
| _.zip = function() { | |
| return _.unzip(arguments); | |
| }; | |
| // Complement of _.zip. Unzip accepts an array of arrays and groups | |
| // each array's elements on shared indices | |
| _.unzip = function(array) { | |
| var length = array && _.max(array, 'length').length || 0; | |
| var result = Array(length); | |
| for (var index = 0; index < length; index++) { | |
| result[index] = _.pluck(array, index); | |
| } | |
| return result; | |
| }; | |
| // Converts lists into objects. Pass either a single array of `[key, value]` | |
| // pairs, or two parallel arrays of the same length -- one of keys, and one of | |
| // the corresponding values. | |
| _.object = function(list, values) { | |
| var result = {}; | |
| for (var i = 0, length = list && list.length; i < length; i++) { | |
| if (values) { | |
| result[list[i]] = values[i]; | |
| } else { | |
| result[list[i][0]] = list[i][1]; | |
| } | |
| } | |
| return result; | |
| }; | |
| // Return the position of the first occurrence of an item in an array, | |
| // or -1 if the item is not included in the array. | |
| // If the array is large and already in sort order, pass `true` | |
| // for **isSorted** to use binary search. | |
| _.indexOf = function(array, item, isSorted) { | |
| var i = 0, length = array && array.length; | |
| if (typeof isSorted == 'number') { | |
| i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; | |
| } else if (isSorted && length) { | |
| i = _.sortedIndex(array, item); | |
| return array[i] === item ? i : -1; | |
| } | |
| if (item !== item) { | |
| return _.findIndex(slice.call(array, i), _.isNaN); | |
| } | |
| for (; i < length; i++) if (array[i] === item) return i; | |
| return -1; | |
| }; | |
| _.lastIndexOf = function(array, item, from) { | |
| var idx = array ? array.length : 0; | |
| if (typeof from == 'number') { | |
| idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); | |
| } | |
| if (item !== item) { | |
| return _.findLastIndex(slice.call(array, 0, idx), _.isNaN); | |
| } | |
| while (--idx >= 0) if (array[idx] === item) return idx; | |
| return -1; | |
| }; | |
| // Generator function to create the findIndex and findLastIndex functions | |
| function createIndexFinder(dir) { | |
| return function(array, predicate, context) { | |
| predicate = cb(predicate, context); | |
| var length = array != null && array.length; | |
| var index = dir > 0 ? 0 : length - 1; | |
| for (; index >= 0 && index < length; index += dir) { | |
| if (predicate(array[index], index, array)) return index; | |
| } | |
| return -1; | |
| }; | |
| } | |
| // Returns the first index on an array-like that passes a predicate test | |
| _.findIndex = createIndexFinder(1); | |
| _.findLastIndex = createIndexFinder(-1); | |
| // Use a comparator function to figure out the smallest index at which | |
| // an object should be inserted so as to maintain order. Uses binary search. | |
| _.sortedIndex = function(array, obj, iteratee, context) { | |
| iteratee = cb(iteratee, context, 1); | |
| var value = iteratee(obj); | |
| var low = 0, high = array.length; | |
| while (low < high) { | |
| var mid = Math.floor((low + high) / 2); | |
| if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; | |
| } | |
| return low; | |
| }; | |
| // Generate an integer Array containing an arithmetic progression. A port of | |
| // the native Python `range()` function. See | |
| // [the Python documentation](http://docs.python.org/library/functions.html#range). | |
| _.range = function(start, stop, step) { | |
| if (arguments.length <= 1) { | |
| stop = start || 0; | |
| start = 0; | |
| } | |
| step = step || 1; | |
| var length = Math.max(Math.ceil((stop - start) / step), 0); | |
| var range = Array(length); | |
| for (var idx = 0; idx < length; idx++, start += step) { | |
| range[idx] = start; | |
| } | |
| return range; | |
| }; | |
| // Function (ahem) Functions | |
| // ------------------ | |
| // Determines whether to execute a function as a constructor | |
| // or a normal function with the provided arguments | |
| var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { | |
| if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); | |
| var self = baseCreate(sourceFunc.prototype); | |
| var result = sourceFunc.apply(self, args); | |
| if (_.isObject(result)) return result; | |
| return self; | |
| }; | |
| // Create a function bound to a given object (assigning `this`, and arguments, | |
| // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if | |
| // available. | |
| _.bind = function(func, context) { | |
| if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); | |
| if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); | |
| var args = slice.call(arguments, 2); | |
| var bound = function() { | |
| return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); | |
| }; | |
| return bound; | |
| }; | |
| // Partially apply a function by creating a version that has had some of its | |
| // arguments pre-filled, without changing its dynamic `this` context. _ acts | |
| // as a placeholder, allowing any combination of arguments to be pre-filled. | |
| _.partial = function(func) { | |
| var boundArgs = slice.call(arguments, 1); | |
| var bound = function() { | |
| var position = 0, length = boundArgs.length; | |
| var args = Array(length); | |
| for (var i = 0; i < length; i++) { | |
| args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; | |
| } | |
| while (position < arguments.length) args.push(arguments[position++]); | |
| return executeBound(func, bound, this, this, args); | |
| }; | |
| return bound; | |
| }; | |
| // Bind a number of an object's methods to that object. Remaining arguments | |
| // are the method names to be bound. Useful for ensuring that all callbacks | |
| // defined on an object belong to it. | |
| _.bindAll = function(obj) { | |
| var i, length = arguments.length, key; | |
| if (length <= 1) throw new Error('bindAll must be passed function names'); | |
| for (i = 1; i < length; i++) { | |
| key = arguments[i]; | |
| obj[key] = _.bind(obj[key], obj); | |
| } | |
| return obj; | |
| }; | |
| // Memoize an expensive function by storing its results. | |
| _.memoize = function(func, hasher) { | |
| var memoize = function(key) { | |
| var cache = memoize.cache; | |
| var address = '' + (hasher ? hasher.apply(this, arguments) : key); | |
| if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); | |
| return cache[address]; | |
| }; | |
| memoize.cache = {}; | |
| return memoize; | |
| }; | |
| // Delays a function for the given number of milliseconds, and then calls | |
| // it with the arguments supplied. | |
| _.delay = function(func, wait) { | |
| var args = slice.call(arguments, 2); | |
| return setTimeout(function(){ | |
| return func.apply(null, args); | |
| }, wait); | |
| }; | |
| // Defers a function, scheduling it to run after the current call stack has | |
| // cleared. | |
| _.defer = _.partial(_.delay, _, 1); | |
| // Returns a function, that, when invoked, will only be triggered at most once | |
| // during a given window of time. Normally, the throttled function will run | |
| // as much as it can, without ever going more than once per `wait` duration; | |
| // but if you'd like to disable the execution on the leading edge, pass | |
| // `{leading: false}`. To disable execution on the trailing edge, ditto. | |
| _.throttle = function(func, wait, options) { | |
| var context, args, result; | |
| var timeout = null; | |
| var previous = 0; | |
| if (!options) options = {}; | |
| var later = function() { | |
| previous = options.leading === false ? 0 : _.now(); | |
| timeout = null; | |
| result = func.apply(context, args); | |
| if (!timeout) context = args = null; | |
| }; | |
| return function() { | |
| var now = _.now(); | |
| if (!previous && options.leading === false) previous = now; | |
| var remaining = wait - (now - previous); | |
| context = this; | |
| args = arguments; | |
| if (remaining <= 0 || remaining > wait) { | |
| if (timeout) { | |
| clearTimeout(timeout); | |
| timeout = null; | |
| } | |
| previous = now; | |
| result = func.apply(context, args); | |
| if (!timeout) context = args = null; | |
| } else if (!timeout && options.trailing !== false) { | |
| timeout = setTimeout(later, remaining); | |
| } | |
| return result; | |
| }; | |
| }; | |
| // Returns a function, that, as long as it continues to be invoked, will not | |
| // be triggered. The function will be called after it stops being called for | |
| // N milliseconds. If `immediate` is passed, trigger the function on the | |
| // leading edge, instead of the trailing. | |
| _.debounce = function(func, wait, immediate) { | |
| var timeout, args, context, timestamp, result; | |
| var later = function() { | |
| var last = _.now() - timestamp; | |
| if (last < wait && last >= 0) { | |
| timeout = setTimeout(later, wait - last); | |
| } else { | |
| timeout = null; | |
| if (!immediate) { | |
| result = func.apply(context, args); | |
| if (!timeout) context = args = null; | |
| } | |
| } | |
| }; | |
| return function() { | |
| context = this; | |
| args = arguments; | |
| timestamp = _.now(); | |
| var callNow = immediate && !timeout; | |
| if (!timeout) timeout = setTimeout(later, wait); | |
| if (callNow) { | |
| result = func.apply(context, args); | |
| context = args = null; | |
| } | |
| return result; | |
| }; | |
| }; | |
| // Returns the first function passed as an argument to the second, | |
| // allowing you to adjust arguments, run code before and after, and | |
| // conditionally execute the original function. | |
| _.wrap = function(func, wrapper) { | |
| return _.partial(wrapper, func); | |
| }; | |
| // Returns a negated version of the passed-in predicate. | |
| _.negate = function(predicate) { | |
| return function() { | |
| return !predicate.apply(this, arguments); | |
| }; | |
| }; | |
| // Returns a function that is the composition of a list of functions, each | |
| // consuming the return value of the function that follows. | |
| _.compose = function() { | |
| var args = arguments; | |
| var start = args.length - 1; | |
| return function() { | |
| var i = start; | |
| var result = args[start].apply(this, arguments); | |
| while (i--) result = args[i].call(this, result); | |
| return result; | |
| }; | |
| }; | |
| // Returns a function that will only be executed on and after the Nth call. | |
| _.after = function(times, func) { | |
| return function() { | |
| if (--times < 1) { | |
| return func.apply(this, arguments); | |
| } | |
| }; | |
| }; | |
| // Returns a function that will only be executed up to (but not including) the Nth call. | |
| _.before = function(times, func) { | |
| var memo; | |
| return function() { | |
| if (--times > 0) { | |
| memo = func.apply(this, arguments); | |
| } | |
| if (times <= 1) func = null; | |
| return memo; | |
| }; | |
| }; | |
| // Returns a function that will be executed at most one time, no matter how | |
| // often you call it. Useful for lazy initialization. | |
| _.once = _.partial(_.before, 2); | |
| // Object Functions | |
| // ---------------- | |
| // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. | |
| var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); | |
| var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', | |
| 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; | |
| function collectNonEnumProps(obj, keys) { | |
| var nonEnumIdx = nonEnumerableProps.length; | |
| var constructor = obj.constructor; | |
| var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; | |
| // Constructor is a special case. | |
| var prop = 'constructor'; | |
| if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); | |
| while (nonEnumIdx--) { | |
| prop = nonEnumerableProps[nonEnumIdx]; | |
| if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { | |
| keys.push(prop); | |
| } | |
| } | |
| } | |
| // Retrieve the names of an object's own properties. | |
| // Delegates to **ECMAScript 5**'s native `Object.keys` | |
| _.keys = function(obj) { | |
| if (!_.isObject(obj)) return []; | |
| if (nativeKeys) return nativeKeys(obj); | |
| var keys = []; | |
| for (var key in obj) if (_.has(obj, key)) keys.push(key); | |
| // Ahem, IE < 9. | |
| if (hasEnumBug) collectNonEnumProps(obj, keys); | |
| return keys; | |
| }; | |
| // Retrieve all the property names of an object. | |
| _.allKeys = function(obj) { | |
| if (!_.isObject(obj)) return []; | |
| var keys = []; | |
| for (var key in obj) keys.push(key); | |
| // Ahem, IE < 9. | |
| if (hasEnumBug) collectNonEnumProps(obj, keys); | |
| return keys; | |
| }; | |
| // Retrieve the values of an object's properties. | |
| _.values = function(obj) { | |
| var keys = _.keys(obj); | |
| var length = keys.length; | |
| var values = Array(length); | |
| for (var i = 0; i < length; i++) { | |
| values[i] = obj[keys[i]]; | |
| } | |
| return values; | |
| }; | |
| // Returns the results of applying the iteratee to each element of the object | |
| // In contrast to _.map it returns an object | |
| _.mapObject = function(obj, iteratee, context) { | |
| iteratee = cb(iteratee, context); | |
| var keys = _.keys(obj), | |
| length = keys.length, | |
| results = {}, | |
| currentKey; | |
| for (var index = 0; index < length; index++) { | |
| currentKey = keys[index]; | |
| results[currentKey] = iteratee(obj[currentKey], currentKey, obj); | |
| } | |
| return results; | |
| }; | |
| // Convert an object into a list of `[key, value]` pairs. | |
| _.pairs = function(obj) { | |
| var keys = _.keys(obj); | |
| var length = keys.length; | |
| var pairs = Array(length); | |
| for (var i = 0; i < length; i++) { | |
| pairs[i] = [keys[i], obj[keys[i]]]; | |
| } | |
| return pairs; | |
| }; | |
| // Invert the keys and values of an object. The values must be serializable. | |
| _.invert = function(obj) { | |
| var result = {}; | |
| var keys = _.keys(obj); | |
| for (var i = 0, length = keys.length; i < length; i++) { | |
| result[obj[keys[i]]] = keys[i]; | |
| } | |
| return result; | |
| }; | |
| // Return a sorted list of the function names available on the object. | |
| // Aliased as `methods` | |
| _.functions = _.methods = function(obj) { | |
| var names = []; | |
| for (var key in obj) { | |
| if (_.isFunction(obj[key])) names.push(key); | |
| } | |
| return names.sort(); | |
| }; | |
| // Extend a given object with all the properties in passed-in object(s). | |
| _.extend = createAssigner(_.allKeys); | |
| // Assigns a given object with all the own properties in the passed-in object(s) | |
| // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) | |
| _.extendOwn = _.assign = createAssigner(_.keys); | |
| // Returns the first key on an object that passes a predicate test | |
| _.findKey = function(obj, predicate, context) { | |
| predicate = cb(predicate, context); | |
| var keys = _.keys(obj), key; | |
| for (var i = 0, length = keys.length; i < length; i++) { | |
| key = keys[i]; | |
| if (predicate(obj[key], key, obj)) return key; | |
| } | |
| }; | |
| // Return a copy of the object only containing the whitelisted properties. | |
| _.pick = function(object, oiteratee, context) { | |
| var result = {}, obj = object, iteratee, keys; | |
| if (obj == null) return result; | |
| if (_.isFunction(oiteratee)) { | |
| keys = _.allKeys(obj); | |
| iteratee = optimizeCb(oiteratee, context); | |
| } else { | |
| keys = flatten(arguments, false, false, 1); | |
| iteratee = function(value, key, obj) { return key in obj; }; | |
| obj = Object(obj); | |
| } | |
| for (var i = 0, length = keys.length; i < length; i++) { | |
| var key = keys[i]; | |
| var value = obj[key]; | |
| if (iteratee(value, key, obj)) result[key] = value; | |
| } | |
| return result; | |
| }; | |
| // Return a copy of the object without the blacklisted properties. | |
| _.omit = function(obj, iteratee, context) { | |
| if (_.isFunction(iteratee)) { | |
| iteratee = _.negate(iteratee); | |
| } else { | |
| var keys = _.map(flatten(arguments, false, false, 1), String); | |
| iteratee = function(value, key) { | |
| return !_.contains(keys, key); | |
| }; | |
| } | |
| return _.pick(obj, iteratee, context); | |
| }; | |
| // Fill in a given object with default properties. | |
| _.defaults = createAssigner(_.allKeys, true); | |
| // Create a (shallow-cloned) duplicate of an object. | |
| _.clone = function(obj) { | |
| if (!_.isObject(obj)) return obj; | |
| return _.isArray(obj) ? obj.slice() : _.extend({}, obj); | |
| }; | |
| // Invokes interceptor with the obj, and then returns obj. | |
| // The primary purpose of this method is to "tap into" a method chain, in | |
| // order to perform operations on intermediate results within the chain. | |
| _.tap = function(obj, interceptor) { | |
| interceptor(obj); | |
| return obj; | |
| }; | |
| // Returns whether an object has a given set of `key:value` pairs. | |
| _.isMatch = function(object, attrs) { | |
| var keys = _.keys(attrs), length = keys.length; | |
| if (object == null) return !length; | |
| var obj = Object(object); | |
| for (var i = 0; i < length; i++) { | |
| var key = keys[i]; | |
| if (attrs[key] !== obj[key] || !(key in obj)) return false; | |
| } | |
| return true; | |
| }; | |
| // Internal recursive comparison function for `isEqual`. | |
| var eq = function(a, b, aStack, bStack) { | |
| // Identical objects are equal. `0 === -0`, but they aren't identical. | |
| // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). | |
| if (a === b) return a !== 0 || 1 / a === 1 / b; | |
| // A strict comparison is necessary because `null == undefined`. | |
| if (a == null || b == null) return a === b; | |
| // Unwrap any wrapped objects. | |
| if (a instanceof _) a = a._wrapped; | |
| if (b instanceof _) b = b._wrapped; | |
| // Compare `[[Class]]` names. | |
| var className = toString.call(a); | |
| if (className !== toString.call(b)) return false; | |
| switch (className) { | |
| // Strings, numbers, regular expressions, dates, and booleans are compared by value. | |
| case '[object RegExp]': | |
| // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') | |
| case '[object String]': | |
| // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is | |
| // equivalent to `new String("5")`. | |
| return '' + a === '' + b; | |
| case '[object Number]': | |
| // `NaN`s are equivalent, but non-reflexive. | |
| // Object(NaN) is equivalent to NaN | |
| if (+a !== +a) return +b !== +b; | |
| // An `egal` comparison is performed for other numeric values. | |
| return +a === 0 ? 1 / +a === 1 / b : +a === +b; | |
| case '[object Date]': | |
| case '[object Boolean]': | |
| // Coerce dates and booleans to numeric primitive values. Dates are compared by their | |
| // millisecond representations. Note that invalid dates with millisecond representations | |
| // of `NaN` are not equivalent. | |
| return +a === +b; | |
| } | |
| var areArrays = className === '[object Array]'; | |
| if (!areArrays) { | |
| if (typeof a != 'object' || typeof b != 'object') return false; | |
| // Objects with different constructors are not equivalent, but `Object`s or `Array`s | |
| // from different frames are. | |
| var aCtor = a.constructor, bCtor = b.constructor; | |
| if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && | |
| _.isFunction(bCtor) && bCtor instanceof bCtor) | |
| && ('constructor' in a && 'constructor' in b)) { | |
| return false; | |
| } | |
| } | |
| // Assume equality for cyclic structures. The algorithm for detecting cyclic | |
| // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. | |
| // Initializing stack of traversed objects. | |
| // It's done here since we only need them for objects and arrays comparison. | |
| aStack = aStack || []; | |
| bStack = bStack || []; | |
| var length = aStack.length; | |
| while (length--) { | |
| // Linear search. Performance is inversely proportional to the number of | |
| // unique nested structures. | |
| if (aStack[length] === a) return bStack[length] === b; | |
| } | |
| // Add the first object to the stack of traversed objects. | |
| aStack.push(a); | |
| bStack.push(b); | |
| // Recursively compare objects and arrays. | |
| if (areArrays) { | |
| // Compare array lengths to determine if a deep comparison is necessary. | |
| length = a.length; | |
| if (length !== b.length) return false; | |
| // Deep compare the contents, ignoring non-numeric properties. | |
| while (length--) { | |
| if (!eq(a[length], b[length], aStack, bStack)) return false; | |
| } | |
| } else { | |
| // Deep compare objects. | |
| var keys = _.keys(a), key; | |
| length = keys.length; | |
| // Ensure that both objects contain the same number of properties before comparing deep equality. | |
| if (_.keys(b).length !== length) return false; | |
| while (length--) { | |
| // Deep compare each member | |
| key = keys[length]; | |
| if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; | |
| } | |
| } | |
| // Remove the first object from the stack of traversed objects. | |
| aStack.pop(); | |
| bStack.pop(); | |
| return true; | |
| }; | |
| // Perform a deep comparison to check if two objects are equal. | |
| _.isEqual = function(a, b) { | |
| return eq(a, b); | |
| }; | |
| // Is a given array, string, or object empty? | |
| // An "empty" object has no enumerable own-properties. | |
| _.isEmpty = function(obj) { | |
| if (obj == null) return true; | |
| if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; | |
| return _.keys(obj).length === 0; | |
| }; | |
| // Is a given value a DOM element? | |
| _.isElement = function(obj) { | |
| return !!(obj && obj.nodeType === 1); | |
| }; | |
| // Is a given value an array? | |
| // Delegates to ECMA5's native Array.isArray | |
| _.isArray = nativeIsArray || function(obj) { | |
| return toString.call(obj) === '[object Array]'; | |
| }; | |
| // Is a given variable an object? | |
| _.isObject = function(obj) { | |
| var type = typeof obj; | |
| return type === 'function' || type === 'object' && !!obj; | |
| }; | |
| // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. | |
| _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { | |
| _['is' + name] = function(obj) { | |
| return toString.call(obj) === '[object ' + name + ']'; | |
| }; | |
| }); | |
| // Define a fallback version of the method in browsers (ahem, IE < 9), where | |
| // there isn't any inspectable "Arguments" type. | |
| if (!_.isArguments(arguments)) { | |
| _.isArguments = function(obj) { | |
| return _.has(obj, 'callee'); | |
| }; | |
| } | |
| // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, | |
| // IE 11 (#1621), and in Safari 8 (#1929). | |
| if (typeof /./ != 'function' && typeof Int8Array != 'object') { | |
| _.isFunction = function(obj) { | |
| return typeof obj == 'function' || false; | |
| }; | |
| } | |
| // Is a given object a finite number? | |
| _.isFinite = function(obj) { | |
| return isFinite(obj) && !isNaN(parseFloat(obj)); | |
| }; | |
| // Is the given value `NaN`? (NaN is the only number which does not equal itself). | |
| _.isNaN = function(obj) { | |
| return _.isNumber(obj) && obj !== +obj; | |
| }; | |
| // Is a given value a boolean? | |
| _.isBoolean = function(obj) { | |
| return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; | |
| }; | |
| // Is a given value equal to null? | |
| _.isNull = function(obj) { | |
| return obj === null; | |
| }; | |
| // Is a given variable undefined? | |
| _.isUndefined = function(obj) { | |
| return obj === void 0; | |
| }; | |
| // Shortcut function for checking if an object has a given property directly | |
| // on itself (in other words, not on a prototype). | |
| _.has = function(obj, key) { | |
| return obj != null && hasOwnProperty.call(obj, key); | |
| }; | |
| // Utility Functions | |
| // ----------------- | |
| // Run Underscore.js in *noConflict* mode, returning the `_` variable to its | |
| // previous owner. Returns a reference to the Underscore object. | |
| _.noConflict = function() { | |
| root._ = previousUnderscore; | |
| return this; | |
| }; | |
| // Keep the identity function around for default iteratees. | |
| _.identity = function(value) { | |
| return value; | |
| }; | |
| // Predicate-generating functions. Often useful outside of Underscore. | |
| _.constant = function(value) { | |
| return function() { | |
| return value; | |
| }; | |
| }; | |
| _.noop = function(){}; | |
| _.property = function(key) { | |
| return function(obj) { | |
| return obj == null ? void 0 : obj[key]; | |
| }; | |
| }; | |
| // Generates a function for a given object that returns a given property. | |
| _.propertyOf = function(obj) { | |
| return obj == null ? function(){} : function(key) { | |
| return obj[key]; | |
| }; | |
| }; | |
| // Returns a predicate for checking whether an object has a given set of | |
| // `key:value` pairs. | |
| _.matcher = _.matches = function(attrs) { | |
| attrs = _.extendOwn({}, attrs); | |
| return function(obj) { | |
| return _.isMatch(obj, attrs); | |
| }; | |
| }; | |
| // Run a function **n** times. | |
| _.times = function(n, iteratee, context) { | |
| var accum = Array(Math.max(0, n)); | |
| iteratee = optimizeCb(iteratee, context, 1); | |
| for (var i = 0; i < n; i++) accum[i] = iteratee(i); | |
| return accum; | |
| }; | |
| // Return a random integer between min and max (inclusive). | |
| _.random = function(min, max) { | |
| if (max == null) { | |
| max = min; | |
| min = 0; | |
| } | |
| return min + Math.floor(Math.random() * (max - min + 1)); | |
| }; | |
| // A (possibly faster) way to get the current timestamp as an integer. | |
| _.now = Date.now || function() { | |
| return new Date().getTime(); | |
| }; | |
| // List of HTML entities for escaping. | |
| var escapeMap = { | |
| '&': '&', | |
| '<': '<', | |
| '>': '>', | |
| '"': '"', | |
| "'": ''', | |
| '`': '`' | |
| }; | |
| var unescapeMap = _.invert(escapeMap); | |
| // Functions for escaping and unescaping strings to/from HTML interpolation. | |
| var createEscaper = function(map) { | |
| var escaper = function(match) { | |
| return map[match]; | |
| }; | |
| // Regexes for identifying a key that needs to be escaped | |
| var source = '(?:' + _.keys(map).join('|') + ')'; | |
| var testRegexp = RegExp(source); | |
| var replaceRegexp = RegExp(source, 'g'); | |
| return function(string) { | |
| string = string == null ? '' : '' + string; | |
| return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; | |
| }; | |
| }; | |
| _.escape = createEscaper(escapeMap); | |
| _.unescape = createEscaper(unescapeMap); | |
| // If the value of the named `property` is a function then invoke it with the | |
| // `object` as context; otherwise, return it. | |
| _.result = function(object, property, fallback) { | |
| var value = object == null ? void 0 : object[property]; | |
| if (value === void 0) { | |
| value = fallback; | |
| } | |
| return _.isFunction(value) ? value.call(object) : value; | |
| }; | |
| // Generate a unique integer id (unique within the entire client session). | |
| // Useful for temporary DOM ids. | |
| var idCounter = 0; | |
| _.uniqueId = function(prefix) { | |
| var id = ++idCounter + ''; | |
| return prefix ? prefix + id : id; | |
| }; | |
| // By default, Underscore uses ERB-style template delimiters, change the | |
| // following template settings to use alternative delimiters. | |
| _.templateSettings = { | |
| evaluate : /<%([\s\S]+?)%>/g, | |
| interpolate : /<%=([\s\S]+?)%>/g, | |
| escape : /<%-([\s\S]+?)%>/g | |
| }; | |
| // When customizing `templateSettings`, if you don't want to define an | |
| // interpolation, evaluation or escaping regex, we need one that is | |
| // guaranteed not to match. | |
| var noMatch = /(.)^/; | |
| // Certain characters need to be escaped so that they can be put into a | |
| // string literal. | |
| var escapes = { | |
| "'": "'", | |
| '\\': '\\', | |
| '\r': 'r', | |
| '\n': 'n', | |
| '\u2028': 'u2028', | |
| '\u2029': 'u2029' | |
| }; | |
| var escaper = /\\|'|\r|\n|\u2028|\u2029/g; | |
| var escapeChar = function(match) { | |
| return '\\' + escapes[match]; | |
| }; | |
| // JavaScript micro-templating, similar to John Resig's implementation. | |
| // Underscore templating handles arbitrary delimiters, preserves whitespace, | |
| // and correctly escapes quotes within interpolated code. | |
| // NB: `oldSettings` only exists for backwards compatibility. | |
| _.template = function(text, settings, oldSettings) { | |
| if (!settings && oldSettings) settings = oldSettings; | |
| settings = _.defaults({}, settings, _.templateSettings); | |
| // Combine delimiters into one regular expression via alternation. | |
| var matcher = RegExp([ | |
| (settings.escape || noMatch).source, | |
| (settings.interpolate || noMatch).source, | |
| (settings.evaluate || noMatch).source | |
| ].join('|') + '|$', 'g'); | |
| // Compile the template source, escaping string literals appropriately. | |
| var index = 0; | |
| var source = "__p+='"; | |
| text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { | |
| source += text.slice(index, offset).replace(escaper, escapeChar); | |
| index = offset + match.length; | |
| if (escape) { | |
| source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; | |
| } else if (interpolate) { | |
| source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; | |
| } else if (evaluate) { | |
| source += "';\n" + evaluate + "\n__p+='"; | |
| } | |
| // Adobe VMs need the match returned to produce the correct offest. | |
| return match; | |
| }); | |
| source += "';\n"; | |
| // If a variable is not specified, place data values in local scope. | |
| if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; | |
| source = "var __t,__p='',__j=Array.prototype.join," + | |
| "print=function(){__p+=__j.call(arguments,'');};\n" + | |
| source + 'return __p;\n'; | |
| try { | |
| var render = new Function(settings.variable || 'obj', '_', source); | |
| } catch (e) { | |
| e.source = source; | |
| throw e; | |
| } | |
| var template = function(data) { | |
| return render.call(this, data, _); | |
| }; | |
| // Provide the compiled source as a convenience for precompilation. | |
| var argument = settings.variable || 'obj'; | |
| template.source = 'function(' + argument + '){\n' + source + '}'; | |
| return template; | |
| }; | |
| // Add a "chain" function. Start chaining a wrapped Underscore object. | |
| _.chain = function(obj) { | |
| var instance = _(obj); | |
| instance._chain = true; | |
| return instance; | |
| }; | |
| // OOP | |
| // --------------- | |
| // If Underscore is called as a function, it returns a wrapped object that | |
| // can be used OO-style. This wrapper holds altered versions of all the | |
| // underscore functions. Wrapped objects may be chained. | |
| // Helper function to continue chaining intermediate results. | |
| var result = function(instance, obj) { | |
| return instance._chain ? _(obj).chain() : obj; | |
| }; | |
| // Add your own custom functions to the Underscore object. | |
| _.mixin = function(obj) { | |
| _.each(_.functions(obj), function(name) { | |
| var func = _[name] = obj[name]; | |
| _.prototype[name] = function() { | |
| var args = [this._wrapped]; | |
| push.apply(args, arguments); | |
| return result(this, func.apply(_, args)); | |
| }; | |
| }); | |
| }; | |
| // Add all of the Underscore functions to the wrapper object. | |
| _.mixin(_); | |
| // Add all mutator Array functions to the wrapper. | |
| _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { | |
| var method = ArrayProto[name]; | |
| _.prototype[name] = function() { | |
| var obj = this._wrapped; | |
| method.apply(obj, arguments); | |
| if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; | |
| return result(this, obj); | |
| }; | |
| }); | |
| // Add all accessor Array functions to the wrapper. | |
| _.each(['concat', 'join', 'slice'], function(name) { | |
| var method = ArrayProto[name]; | |
| _.prototype[name] = function() { | |
| return result(this, method.apply(this._wrapped, arguments)); | |
| }; | |
| }); | |
| // Extracts the result from a wrapped and chained object. | |
| _.prototype.value = function() { | |
| return this._wrapped; | |
| }; | |
| // Provide unwrapping proxy for some methods used in engine operations | |
| // such as arithmetic and JSON stringification. | |
| _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; | |
| _.prototype.toString = function() { | |
| return '' + this._wrapped; | |
| }; | |
| // AMD registration happens at the end for compatibility with AMD loaders | |
| // that may not enforce next-turn semantics on modules. Even though general | |
| // practice for AMD registration is to be anonymous, underscore registers | |
| // as a named module because, like jQuery, it is a base library that is | |
| // popular enough to be bundled in a third party lib, but not be part of | |
| // an AMD load request. Those cases could generate an error when an | |
| // anonymous define() is called outside of a loader request. | |
| if (typeof define === 'function' && define.amd) { | |
| define('underscore', [], function() { | |
| return _; | |
| }); | |
| } | |
| }.call(this)); | |
| },{}],11:[function(require,module,exports){ | |
| var WildEmitter = require('wildemitter'); | |
| var util = require('util'); | |
| function Sender(opts) { | |
| WildEmitter.call(this); | |
| var options = opts || {}; | |
| this.config = { | |
| chunksize: 16384, | |
| pacing: 0 | |
| }; | |
| // set our config from options | |
| var item; | |
| for (item in options) { | |
| this.config[item] = options[item]; | |
| } | |
| this.file = null; | |
| this.channel = null; | |
| } | |
| util.inherits(Sender, WildEmitter); | |
| Sender.prototype.send = function (file, channel) { | |
| var self = this; | |
| this.file = file; | |
| this.channel = channel; | |
| var sliceFile = function(offset) { | |
| var reader = new window.FileReader(); | |
| reader.onload = (function() { | |
| return function(e) { | |
| self.channel.send(e.target.result); | |
| self.emit('progress', offset, file.size, e.target.result); | |
| if (file.size > offset + e.target.result.byteLength) { | |
| window.setTimeout(sliceFile, self.config.pacing, offset + self.config.chunksize); | |
| } else { | |
| self.emit('progress', file.size, file.size, null); | |
| self.emit('sentFile'); | |
| } | |
| }; | |
| })(file); | |
| var slice = file.slice(offset, offset + self.config.chunksize); | |
| reader.readAsArrayBuffer(slice); | |
| }; | |
| window.setTimeout(sliceFile, 0, 0); | |
| }; | |
| function Receiver() { | |
| WildEmitter.call(this); | |
| this.receiveBuffer = []; | |
| this.received = 0; | |
| this.metadata = {}; | |
| this.channel = null; | |
| } | |
| util.inherits(Receiver, WildEmitter); | |
| Receiver.prototype.receive = function (metadata, channel) { | |
| var self = this; | |
| if (metadata) { | |
| this.metadata = metadata; | |
| } | |
| this.channel = channel; | |
| // chrome only supports arraybuffers and those make it easier to calc the hash | |
| channel.binaryType = 'arraybuffer'; | |
| this.channel.onmessage = function (event) { | |
| var len = event.data.byteLength; | |
| self.received += len; | |
| self.receiveBuffer.push(event.data); | |
| self.emit('progress', self.received, self.metadata.size, event.data); | |
| if (self.received === self.metadata.size) { | |
| self.emit('receivedFile', new window.Blob(self.receiveBuffer), self.metadata); | |
| self.receiveBuffer = []; // discard receivebuffer | |
| } else if (self.received > self.metadata.size) { | |
| // FIXME | |
| console.error('received more than expected, discarding...'); | |
| self.receiveBuffer = []; // just discard... | |
| } | |
| }; | |
| }; | |
| module.exports = {}; | |
| module.exports.support = window && window.File && window.FileReader && window.Blob; | |
| module.exports.Sender = Sender; | |
| module.exports.Receiver = Receiver; | |
| },{"util":2,"wildemitter":4}],10:[function(require,module,exports){ | |
| var _ = require('underscore'); | |
| var util = require('util'); | |
| var webrtc = require('webrtcsupport'); | |
| var SJJ = require('sdp-jingle-json'); | |
| var WildEmitter = require('wildemitter'); | |
| var peerconn = require('traceablepeerconnection'); | |
| function PeerConnection(config, constraints) { | |
| var self = this; | |
| var item; | |
| WildEmitter.call(this); | |
| config = config || {}; | |
| config.iceServers = config.iceServers || []; | |
| // make sure this only gets enabled in Google Chrome | |
| // EXPERIMENTAL FLAG, might get removed without notice | |
| this.enableChromeNativeSimulcast = false; | |
| if (constraints && constraints.optional && | |
| webrtc.prefix === 'webkit' && | |
| navigator.appVersion.match(/Chromium\//) === null) { | |
| constraints.optional.forEach(function (constraint, idx) { | |
| if (constraint.enableChromeNativeSimulcast) { | |
| self.enableChromeNativeSimulcast = true; | |
| } | |
| }); | |
| } | |
| // EXPERIMENTAL FLAG, might get removed without notice | |
| this.enableMultiStreamHacks = false; | |
| if (constraints && constraints.optional) { | |
| constraints.optional.forEach(function (constraint, idx) { | |
| if (constraint.enableMultiStreamHacks) { | |
| self.enableMultiStreamHacks = true; | |
| } | |
| }); | |
| } | |
| // EXPERIMENTAL FLAG, might get removed without notice | |
| this.restrictBandwidth = 0; | |
| if (constraints && constraints.optional) { | |
| constraints.optional.forEach(function (constraint, idx) { | |
| if (constraint.andyetRestrictBandwidth) { | |
| self.restrictBandwidth = constraint.andyetRestrictBandwidth; | |
| } | |
| }); | |
| } | |
| // EXPERIMENTAL FLAG, might get removed without notice | |
| // bundle up ice candidates, only works for jingle mode | |
| // number > 0 is the delay to wait for additional candidates | |
| // ~20ms seems good | |
| this.batchIceCandidates = 0; | |
| if (constraints && constraints.optional) { | |
| constraints.optional.forEach(function (constraint, idx) { | |
| if (constraint.andyetBatchIce) { | |
| self.batchIceCandidates = constraint.andyetBatchIce; | |
| } | |
| }); | |
| } | |
| this.batchedIceCandidates = []; | |
| // EXPERIMENTAL FLAG, might get removed without notice | |
| this.assumeSetLocalSuccess = false; | |
| if (constraints && constraints.optional) { | |
| constraints.optional.forEach(function (constraint, idx) { | |
| if (constraint.andyetAssumeSetLocalSuccess) { | |
| self.assumeSetLocalSuccess = constraint.andyetAssumeSetLocalSuccess; | |
| } | |
| }); | |
| } | |
| this.pc = new peerconn(config, constraints); | |
| this.getLocalStreams = this.pc.getLocalStreams.bind(this.pc); | |
| this.getRemoteStreams = this.pc.getRemoteStreams.bind(this.pc); | |
| this.addStream = this.pc.addStream.bind(this.pc); | |
| this.removeStream = this.pc.removeStream.bind(this.pc); | |
| // proxy events | |
| this.pc.on('*', function () { | |
| self.emit.apply(self, arguments); | |
| }); | |
| // proxy some events directly | |
| this.pc.onremovestream = this.emit.bind(this, 'removeStream'); | |
| this.pc.onaddstream = this.emit.bind(this, 'addStream'); | |
| this.pc.onnegotiationneeded = this.emit.bind(this, 'negotiationNeeded'); | |
| this.pc.oniceconnectionstatechange = this.emit.bind(this, 'iceConnectionStateChange'); | |
| this.pc.onsignalingstatechange = this.emit.bind(this, 'signalingStateChange'); | |
| // handle ice candidate and data channel events | |
| this.pc.onicecandidate = this._onIce.bind(this); | |
| this.pc.ondatachannel = this._onDataChannel.bind(this); | |
| this.localDescription = { | |
| contents: [] | |
| }; | |
| this.remoteDescription = { | |
| contents: [] | |
| }; | |
| this.config = { | |
| debug: false, | |
| ice: {}, | |
| sid: '', | |
| isInitiator: true, | |
| sdpSessionID: Date.now(), | |
| useJingle: false | |
| }; | |
| // apply our config | |
| for (item in config) { | |
| this.config[item] = config[item]; | |
| } | |
| if (this.config.debug) { | |
| this.on('*', function (eventName, event) { | |
| var logger = config.logger || console; | |
| logger.log('PeerConnection event:', arguments); | |
| }); | |
| } | |
| this.hadLocalStunCandidate = false; | |
| this.hadRemoteStunCandidate = false; | |
| this.hadLocalRelayCandidate = false; | |
| this.hadRemoteRelayCandidate = false; | |
| this.hadLocalIPv6Candidate = false; | |
| this.hadRemoteIPv6Candidate = false; | |
| // keeping references for all our data channels | |
| // so they dont get garbage collected | |
| // can be removed once the following bugs have been fixed | |
| // https://crbug.com/405545 | |
| // https://bugzilla.mozilla.org/show_bug.cgi?id=964092 | |
| // to be filed for opera | |
| this._remoteDataChannels = []; | |
| this._localDataChannels = []; | |
| } | |
| util.inherits(PeerConnection, WildEmitter); | |
| Object.defineProperty(PeerConnection.prototype, 'signalingState', { | |
| get: function () { | |
| return this.pc.signalingState; | |
| } | |
| }); | |
| Object.defineProperty(PeerConnection.prototype, 'iceConnectionState', { | |
| get: function () { | |
| return this.pc.iceConnectionState; | |
| } | |
| }); | |
| PeerConnection.prototype._role = function () { | |
| return this.isInitiator ? 'initiator' : 'responder'; | |
| }; | |
| // Add a stream to the peer connection object | |
| PeerConnection.prototype.addStream = function (stream) { | |
| this.localStream = stream; | |
| this.pc.addStream(stream); | |
| }; | |
| // helper function to check if a remote candidate is a stun/relay | |
| // candidate or an ipv6 candidate | |
| PeerConnection.prototype._checkLocalCandidate = function (candidate) { | |
| var cand = SJJ.toCandidateJSON(candidate); | |
| if (cand.type == 'srflx') { | |
| this.hadLocalStunCandidate = true; | |
| } else if (cand.type == 'relay') { | |
| this.hadLocalRelayCandidate = true; | |
| } | |
| if (cand.ip.indexOf(':') != -1) { | |
| this.hadLocalIPv6Candidate = true; | |
| } | |
| }; | |
| // helper function to check if a remote candidate is a stun/relay | |
| // candidate or an ipv6 candidate | |
| PeerConnection.prototype._checkRemoteCandidate = function (candidate) { | |
| var cand = SJJ.toCandidateJSON(candidate); | |
| if (cand.type == 'srflx') { | |
| this.hadRemoteStunCandidate = true; | |
| } else if (cand.type == 'relay') { | |
| this.hadRemoteRelayCandidate = true; | |
| } | |
| if (cand.ip.indexOf(':') != -1) { | |
| this.hadRemoteIPv6Candidate = true; | |
| } | |
| }; | |
| // Init and add ice candidate object with correct constructor | |
| PeerConnection.prototype.processIce = function (update, cb) { | |
| cb = cb || function () {}; | |
| var self = this; | |
| // ignore any added ice candidates to avoid errors. why does the | |
| // spec not do this? | |
| if (this.pc.signalingState === 'closed') return cb(); | |
| if (update.contents || (update.jingle && update.jingle.contents)) { | |
| var contentNames = _.pluck(this.remoteDescription.contents, 'name'); | |
| var contents = update.contents || update.jingle.contents; | |
| contents.forEach(function (content) { | |
| var transport = content.transport || {}; | |
| var candidates = transport.candidates || []; | |
| var mline = contentNames.indexOf(content.name); | |
| var mid = content.name; | |
| candidates.forEach( | |
| function (candidate) { | |
| var iceCandidate = SJJ.toCandidateSDP(candidate) + '\r\n'; | |
| self.pc.addIceCandidate( | |
| new webrtc.IceCandidate({ | |
| candidate: iceCandidate, | |
| sdpMLineIndex: mline, | |
| sdpMid: mid | |
| }), function () { | |
| // well, this success callback is pretty meaningless | |
| }, | |
| function (err) { | |
| self.emit('error', err); | |
| } | |
| ); | |
| self._checkRemoteCandidate(iceCandidate); | |
| }); | |
| }); | |
| } else { | |
| // working around https://code.google.com/p/webrtc/issues/detail?id=3669 | |
| if (update.candidate && update.candidate.candidate.indexOf('a=') !== 0) { | |
| update.candidate.candidate = 'a=' + update.candidate.candidate; | |
| } | |
| self.pc.addIceCandidate( | |
| new webrtc.IceCandidate(update.candidate), | |
| function () { }, | |
| function (err) { | |
| self.emit('error', err); | |
| } | |
| ); | |
| self._checkRemoteCandidate(update.candidate.candidate); | |
| } | |
| cb(); | |
| }; | |
| // Generate and emit an offer with the given constraints | |
| PeerConnection.prototype.offer = function (constraints, cb) { | |
| var self = this; | |
| var hasConstraints = arguments.length === 2; | |
| var mediaConstraints = hasConstraints ? constraints : { | |
| mandatory: { | |
| OfferToReceiveAudio: true, | |
| OfferToReceiveVideo: true | |
| } | |
| }; | |
| cb = hasConstraints ? cb : constraints; | |
| cb = cb || function () {}; | |
| if (this.pc.signalingState === 'closed') return cb('Already closed'); | |
| // Actually generate the offer | |
| this.pc.createOffer( | |
| function (offer) { | |
| // does not work for jingle, but jingle.js doesn't need | |
| // this hack... | |
| if (self.assumeSetLocalSuccess) { | |
| self.emit('offer', offer); | |
| cb(null, offer); | |
| } | |
| self.pc.setLocalDescription(offer, | |
| function () { | |
| var jingle; | |
| var expandedOffer = { | |
| type: 'offer', | |
| sdp: offer.sdp | |
| }; | |
| if (self.config.useJingle) { | |
| jingle = SJJ.toSessionJSON(offer.sdp, { | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| jingle.sid = self.config.sid; | |
| self.localDescription = jingle; | |
| // Save ICE credentials | |
| _.each(jingle.contents, function (content) { | |
| var transport = content.transport || {}; | |
| if (transport.ufrag) { | |
| self.config.ice[content.name] = { | |
| ufrag: transport.ufrag, | |
| pwd: transport.pwd | |
| }; | |
| } | |
| }); | |
| expandedOffer.jingle = jingle; | |
| } | |
| expandedOffer.sdp.split('\r\n').forEach(function (line) { | |
| if (line.indexOf('a=candidate:') === 0) { | |
| self._checkLocalCandidate(line); | |
| } | |
| }); | |
| if (!self.assumeSetLocalSuccess) { | |
| self.emit('offer', expandedOffer); | |
| cb(null, expandedOffer); | |
| } | |
| }, | |
| function (err) { | |
| self.emit('error', err); | |
| cb(err); | |
| } | |
| ); | |
| }, | |
| function (err) { | |
| self.emit('error', err); | |
| cb(err); | |
| }, | |
| mediaConstraints | |
| ); | |
| }; | |
| // Process an incoming offer so that ICE may proceed before deciding | |
| // to answer the request. | |
| PeerConnection.prototype.handleOffer = function (offer, cb) { | |
| cb = cb || function () {}; | |
| var self = this; | |
| offer.type = 'offer'; | |
| if (offer.jingle) { | |
| if (this.enableChromeNativeSimulcast) { | |
| offer.jingle.contents.forEach(function (content) { | |
| if (content.name === 'video') { | |
| content.description.googConferenceFlag = true; | |
| } | |
| }); | |
| } | |
| /* | |
| if (this.enableMultiStreamHacks) { | |
| // add a mixed video stream as first stream | |
| offer.jingle.contents.forEach(function (content) { | |
| if (content.name === 'video') { | |
| var sources = content.description.sources || []; | |
| if (sources.length === 0 || sources[0].ssrc !== "3735928559") { | |
| sources.unshift({ | |
| ssrc: "3735928559", // 0xdeadbeef | |
| parameters: [ | |
| { | |
| key: "cname", | |
| value: "deadbeef" | |
| }, | |
| { | |
| key: "msid", | |
| value: "mixyourfecintothis please" | |
| } | |
| ] | |
| }); | |
| content.description.sources = sources; | |
| } | |
| } | |
| }); | |
| } | |
| */ | |
| if (self.restrictBandwidth > 0) { | |
| offer.jingle = SJJ.toSessionJSON(offer.sdp, { | |
| role: self._role(), | |
| direction: 'incoming' | |
| }); | |
| if (offer.jingle.contents.length >= 2 && offer.jingle.contents[1].name === 'video') { | |
| var content = offer.jingle.contents[1]; | |
| var hasBw = content.description && content.description.bandwidth; | |
| if (!hasBw) { | |
| offer.jingle.contents[1].description.bandwidth = { type:'AS', bandwidth: self.restrictBandwidth.toString() }; | |
| offer.sdp = SJJ.toSessionSDP(offer.jingle, { | |
| sid: self.config.sdpSessionID, | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| } | |
| } | |
| } | |
| offer.sdp = SJJ.toSessionSDP(offer.jingle, { | |
| sid: self.config.sdpSessionID, | |
| role: self._role(), | |
| direction: 'incoming' | |
| }); | |
| self.remoteDescription = offer.jingle; | |
| } | |
| offer.sdp.split('\r\n').forEach(function (line) { | |
| if (line.indexOf('a=candidate:') === 0) { | |
| self._checkRemoteCandidate(line); | |
| } | |
| }); | |
| self.pc.setRemoteDescription(new webrtc.SessionDescription(offer), | |
| function () { | |
| cb(); | |
| }, | |
| cb | |
| ); | |
| }; | |
| // Answer an offer with audio only | |
| PeerConnection.prototype.answerAudioOnly = function (cb) { | |
| var mediaConstraints = { | |
| mandatory: { | |
| OfferToReceiveAudio: true, | |
| OfferToReceiveVideo: false | |
| } | |
| }; | |
| this._answer(mediaConstraints, cb); | |
| }; | |
| // Answer an offer without offering to recieve | |
| PeerConnection.prototype.answerBroadcastOnly = function (cb) { | |
| var mediaConstraints = { | |
| mandatory: { | |
| OfferToReceiveAudio: false, | |
| OfferToReceiveVideo: false | |
| } | |
| }; | |
| this._answer(mediaConstraints, cb); | |
| }; | |
| // Answer an offer with given constraints default is audio/video | |
| PeerConnection.prototype.answer = function (constraints, cb) { | |
| var self = this; | |
| var hasConstraints = arguments.length === 2; | |
| var callback = hasConstraints ? cb : constraints; | |
| var mediaConstraints = hasConstraints ? constraints : { | |
| mandatory: { | |
| OfferToReceiveAudio: true, | |
| OfferToReceiveVideo: true | |
| } | |
| }; | |
| this._answer(mediaConstraints, callback); | |
| }; | |
| // Process an answer | |
| PeerConnection.prototype.handleAnswer = function (answer, cb) { | |
| cb = cb || function () {}; | |
| var self = this; | |
| if (answer.jingle) { | |
| answer.sdp = SJJ.toSessionSDP(answer.jingle, { | |
| sid: self.config.sdpSessionID, | |
| role: self._role(), | |
| direction: 'incoming' | |
| }); | |
| self.remoteDescription = answer.jingle; | |
| } | |
| answer.sdp.split('\r\n').forEach(function (line) { | |
| if (line.indexOf('a=candidate:') === 0) { | |
| self._checkRemoteCandidate(line); | |
| } | |
| }); | |
| self.pc.setRemoteDescription( | |
| new webrtc.SessionDescription(answer), | |
| function () { | |
| cb(null); | |
| }, | |
| cb | |
| ); | |
| }; | |
| // Close the peer connection | |
| PeerConnection.prototype.close = function () { | |
| this.pc.close(); | |
| this._localDataChannels = []; | |
| this._remoteDataChannels = []; | |
| this.emit('close'); | |
| }; | |
| // Internal code sharing for various types of answer methods | |
| PeerConnection.prototype._answer = function (constraints, cb) { | |
| cb = cb || function () {}; | |
| var self = this; | |
| if (!this.pc.remoteDescription) { | |
| // the old API is used, call handleOffer | |
| throw new Error('remoteDescription not set'); | |
| } | |
| if (this.pc.signalingState === 'closed') return cb('Already closed'); | |
| self.pc.createAnswer( | |
| function (answer) { | |
| var sim = []; | |
| var rtx = []; | |
| if (self.enableChromeNativeSimulcast) { | |
| // native simulcast part 1: add another SSRC | |
| answer.jingle = SJJ.toSessionJSON(answer.sdp, { | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| if (answer.jingle.contents.length >= 2 && answer.jingle.contents[1].name === 'video') { | |
| var hasSimgroup = false; | |
| var groups = answer.jingle.contents[1].description.sourceGroups || []; | |
| var hasSim = false; | |
| groups.forEach(function (group) { | |
| if (group.semantics == 'SIM') hasSim = true; | |
| }); | |
| if (!hasSim && | |
| answer.jingle.contents[1].description.sources.length) { | |
| var newssrc = JSON.parse(JSON.stringify(answer.jingle.contents[1].description.sources[0])); | |
| newssrc.ssrc = '' + Math.floor(Math.random() * 0xffffffff); // FIXME: look for conflicts | |
| answer.jingle.contents[1].description.sources.push(newssrc); | |
| sim.push(answer.jingle.contents[1].description.sources[0].ssrc); | |
| sim.push(newssrc.ssrc); | |
| groups.push({ | |
| semantics: 'SIM', | |
| sources: sim | |
| }); | |
| // also create an RTX one for the SIM one | |
| var rtxssrc = JSON.parse(JSON.stringify(newssrc)); | |
| rtxssrc.ssrc = '' + Math.floor(Math.random() * 0xffffffff); // FIXME: look for conflicts | |
| answer.jingle.contents[1].description.sources.push(rtxssrc); | |
| groups.push({ | |
| semantics: 'FID', | |
| sources: [newssrc.ssrc, rtxssrc.ssrc] | |
| }); | |
| answer.jingle.contents[1].description.sourceGroups = groups; | |
| answer.sdp = SJJ.toSessionSDP(answer.jingle, { | |
| sid: self.config.sdpSessionID, | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| } | |
| } | |
| } | |
| if (self.assumeSetLocalSuccess) { | |
| // not safe to do when doing simulcast mangling | |
| self.emit('answer', answer); | |
| cb(null, answer); | |
| } | |
| self.pc.setLocalDescription(answer, | |
| function () { | |
| var expandedAnswer = { | |
| type: 'answer', | |
| sdp: answer.sdp | |
| }; | |
| if (self.config.useJingle) { | |
| var jingle = SJJ.toSessionJSON(answer.sdp, { | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| jingle.sid = self.config.sid; | |
| self.localDescription = jingle; | |
| expandedAnswer.jingle = jingle; | |
| } | |
| if (self.enableChromeNativeSimulcast) { | |
| // native simulcast part 2: | |
| // signal multiple tracks to the receiver | |
| // for anything in the SIM group | |
| if (!expandedAnswer.jingle) { | |
| expandedAnswer.jingle = SJJ.toSessionJSON(answer.sdp, { | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| } | |
| var groups = expandedAnswer.jingle.contents[1].description.sourceGroups || []; | |
| expandedAnswer.jingle.contents[1].description.sources.forEach(function (source, idx) { | |
| // the floor idx/2 is a hack that relies on a particular order | |
| // of groups, alternating between sim and rtx | |
| source.parameters = source.parameters.map(function (parameter) { | |
| if (parameter.key === 'msid') { | |
| parameter.value += '-' + Math.floor(idx / 2); | |
| } | |
| return parameter; | |
| }); | |
| }); | |
| expandedAnswer.sdp = SJJ.toSessionSDP(expandedAnswer.jingle, { | |
| sid: self.sdpSessionID, | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| } | |
| expandedAnswer.sdp.split('\r\n').forEach(function (line) { | |
| if (line.indexOf('a=candidate:') === 0) { | |
| self._checkLocalCandidate(line); | |
| } | |
| }); | |
| if (!self.assumeSetLocalSuccess) { | |
| self.emit('answer', expandedAnswer); | |
| cb(null, expandedAnswer); | |
| } | |
| }, | |
| function (err) { | |
| self.emit('error', err); | |
| cb(err); | |
| } | |
| ); | |
| }, | |
| function (err) { | |
| self.emit('error', err); | |
| cb(err); | |
| }, | |
| constraints | |
| ); | |
| }; | |
| // Internal method for emitting ice candidates on our peer object | |
| PeerConnection.prototype._onIce = function (event) { | |
| var self = this; | |
| if (event.candidate) { | |
| var ice = event.candidate; | |
| var expandedCandidate = { | |
| candidate: event.candidate | |
| }; | |
| this._checkLocalCandidate(ice.candidate); | |
| var cand = SJJ.toCandidateJSON(ice.candidate); | |
| if (self.config.useJingle) { | |
| if (!ice.sdpMid) { // firefox doesn't set this | |
| ice.sdpMid = self.localDescription.contents[ice.sdpMLineIndex].name; | |
| } | |
| if (!self.config.ice[ice.sdpMid]) { | |
| var jingle = SJJ.toSessionJSON(self.pc.localDescription.sdp, { | |
| role: self._role(), | |
| direction: 'outgoing' | |
| }); | |
| _.each(jingle.contents, function (content) { | |
| var transport = content.transport || {}; | |
| if (transport.ufrag) { | |
| self.config.ice[content.name] = { | |
| ufrag: transport.ufrag, | |
| pwd: transport.pwd | |
| }; | |
| } | |
| }); | |
| } | |
| expandedCandidate.jingle = { | |
| contents: [{ | |
| name: ice.sdpMid, | |
| creator: self._role(), | |
| transport: { | |
| transType: 'iceUdp', | |
| ufrag: self.config.ice[ice.sdpMid].ufrag, | |
| pwd: self.config.ice[ice.sdpMid].pwd, | |
| candidates: [ | |
| cand | |
| ] | |
| } | |
| }] | |
| }; | |
| if (self.batchIceCandidates > 0) { | |
| if (self.batchedIceCandidates.length === 0) { | |
| window.setTimeout(function () { | |
| var contents = {}; | |
| self.batchedIceCandidates.forEach(function (content) { | |
| content = content.contents[0]; | |
| if (!contents[content.name]) contents[content.name] = content; | |
| contents[content.name].transport.candidates.push(content.transport.candidates[0]); | |
| }); | |
| var newCand = { | |
| jingle: { | |
| contents: [] | |
| } | |
| }; | |
| Object.keys(contents).forEach(function (name) { | |
| newCand.jingle.contents.push(contents[name]); | |
| }); | |
| self.batchedIceCandidates = []; | |
| self.emit('ice', newCand); | |
| }, self.batchIceCandidates); | |
| } | |
| self.batchedIceCandidates.push(expandedCandidate.jingle); | |
| return; | |
| } | |
| } | |
| this.emit('ice', expandedCandidate); | |
| } else { | |
| this.emit('endOfCandidates'); | |
| } | |
| }; | |
| // Internal method for processing a new data channel being added by the | |
| // other peer. | |
| PeerConnection.prototype._onDataChannel = function (event) { | |
| // make sure we keep a reference so this doesn't get garbage collected | |
| var channel = event.channel; | |
| this._remoteDataChannels.push(channel); | |
| this.emit('addChannel', channel); | |
| }; | |
| // Create a data channel spec reference: | |
| // http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCDataChannelInit | |
| PeerConnection.prototype.createDataChannel = function (name, opts) { | |
| var channel = this.pc.createDataChannel(name, opts); | |
| // make sure we keep a reference so this doesn't get garbage collected | |
| this._localDataChannels.push(channel); | |
| return channel; | |
| }; | |
| // a wrapper around getStats which hides the differences (where possible) | |
| PeerConnection.prototype.getStats = function (cb) { | |
| if (webrtc.prefix === 'moz') { | |
| this.pc.getStats( | |
| function (res) { | |
| var items = []; | |
| for (var result in res) { | |
| if (typeof res[result] === 'object') { | |
| items.push(res[result]); | |
| } | |
| } | |
| cb(null, items); | |
| }, | |
| cb | |
| ); | |
| } else { | |
| this.pc.getStats(function (res) { | |
| var items = []; | |
| res.result().forEach(function (result) { | |
| var item = {}; | |
| result.names().forEach(function (name) { | |
| item[name] = result.stat(name); | |
| }); | |
| item.id = result.id; | |
| item.type = result.type; | |
| item.timestamp = result.timestamp; | |
| items.push(item); | |
| }); | |
| cb(null, items); | |
| }); | |
| } | |
| }; | |
| module.exports = PeerConnection; | |
| },{"sdp-jingle-json":17,"traceablepeerconnection":18,"underscore":16,"util":2,"webrtcsupport":5,"wildemitter":4}],17:[function(require,module,exports){ | |
| var toSDP = require('./lib/tosdp'); | |
| var toJSON = require('./lib/tojson'); | |
| // Converstion from JSON to SDP | |
| exports.toIncomingSDPOffer = function (session) { | |
| return toSDP.toSessionSDP(session, { | |
| role: 'responder', | |
| direction: 'incoming' | |
| }); | |
| }; | |
| exports.toOutgoingSDPOffer = function (session) { | |
| return toSDP.toSessionSDP(session, { | |
| role: 'initiator', | |
| direction: 'outgoing' | |
| }); | |
| }; | |
| exports.toIncomingSDPAnswer = function (session) { | |
| return toSDP.toSessionSDP(session, { | |
| role: 'initiator', | |
| direction: 'incoming' | |
| }); | |
| }; | |
| exports.toOutgoingSDPAnswer = function (session) { | |
| return toSDP.toSessionSDP(session, { | |
| role: 'responder', | |
| direction: 'outgoing' | |
| }); | |
| }; | |
| exports.toIncomingMediaSDPOffer = function (media) { | |
| return toSDP.toMediaSDP(media, { | |
| role: 'responder', | |
| direction: 'incoming' | |
| }); | |
| }; | |
| exports.toOutgoingMediaSDPOffer = function (media) { | |
| return toSDP.toMediaSDP(media, { | |
| role: 'initiator', | |
| direction: 'outgoing' | |
| }); | |
| }; | |
| exports.toIncomingMediaSDPAnswer = function (media) { | |
| return toSDP.toMediaSDP(media, { | |
| role: 'initiator', | |
| direction: 'incoming' | |
| }); | |
| }; | |
| exports.toOutgoingMediaSDPAnswer = function (media) { | |
| return toSDP.toMediaSDP(media, { | |
| role: 'responder', | |
| direction: 'outgoing' | |
| }); | |
| }; | |
| exports.toCandidateSDP = toSDP.toCandidateSDP; | |
| exports.toMediaSDP = toSDP.toMediaSDP; | |
| exports.toSessionSDP = toSDP.toSessionSDP; | |
| // Conversion from SDP to JSON | |
| exports.toIncomingJSONOffer = function (sdp, creators) { | |
| return toJSON.toSessionJSON(sdp, { | |
| role: 'responder', | |
| direction: 'incoming', | |
| creators: creators | |
| }); | |
| }; | |
| exports.toOutgoingJSONOffer = function (sdp, creators) { | |
| return toJSON.toSessionJSON(sdp, { | |
| role: 'initiator', | |
| direction: 'outgoing', | |
| creators: creators | |
| }); | |
| }; | |
| exports.toIncomingJSONAnswer = function (sdp, creators) { | |
| return toJSON.toSessionJSON(sdp, { | |
| role: 'initiator', | |
| direction: 'incoming', | |
| creators: creators | |
| }); | |
| }; | |
| exports.toOutgoingJSONAnswer = function (sdp, creators) { | |
| return toJSON.toSessionJSON(sdp, { | |
| role: 'responder', | |
| direction: 'outgoing', | |
| creators: creators | |
| }); | |
| }; | |
| exports.toIncomingMediaJSONOffer = function (sdp, creator) { | |
| return toJSON.toMediaJSON(sdp, { | |
| role: 'responder', | |
| direction: 'incoming', | |
| creator: creator | |
| }); | |
| }; | |
| exports.toOutgoingMediaJSONOffer = function (sdp, creator) { | |
| return toJSON.toMediaJSON(sdp, { | |
| role: 'initiator', | |
| direction: 'outgoing', | |
| creator: creator | |
| }); | |
| }; | |
| exports.toIncomingMediaJSONAnswer = function (sdp, creator) { | |
| return toJSON.toMediaJSON(sdp, { | |
| role: 'initiator', | |
| direction: 'incoming', | |
| creator: creator | |
| }); | |
| }; | |
| exports.toOutgoingMediaJSONAnswer = function (sdp, creator) { | |
| return toJSON.toMediaJSON(sdp, { | |
| role: 'responder', | |
| direction: 'outgoing', | |
| creator: creator | |
| }); | |
| }; | |
| exports.toCandidateJSON = toJSON.toCandidateJSON; | |
| exports.toMediaJSON = toJSON.toMediaJSON; | |
| exports.toSessionJSON = toJSON.toSessionJSON; | |
| },{"./lib/tojson":19,"./lib/tosdp":20}],14:[function(require,module,exports){ | |
| // getScreenMedia helper by @HenrikJoreteg | |
| var getUserMedia = require('getusermedia'); | |
| // cache for constraints and callback | |
| var cache = {}; | |
| module.exports = function (constraints, cb) { | |
| var hasConstraints = arguments.length === 2; | |
| var callback = hasConstraints ? cb : constraints; | |
| var error; | |
| if (typeof window === 'undefined' || window.location.protocol === 'http:') { | |
| error = new Error('NavigatorUserMediaError'); | |
| error.name = 'HTTPS_REQUIRED'; | |
| return callback(error); | |
| } | |
| if (window.navigator.userAgent.match('Chrome')) { | |
| var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); | |
| var maxver = 33; | |
| var isCef = !window.chrome.webstore; | |
| // "known" crash in chrome 34 and 35 on linux | |
| if (window.navigator.userAgent.match('Linux')) maxver = 35; | |
| if (isCef || (chromever >= 26 && chromever <= maxver)) { | |
| // chrome 26 - chrome 33 way to do it -- requires bad chrome://flags | |
| // note: this is basically in maintenance mode and will go away soon | |
| constraints = (hasConstraints && constraints) || { | |
| video: { | |
| mandatory: { | |
| googLeakyBucket: true, | |
| maxWidth: window.screen.width, | |
| maxHeight: window.screen.height, | |
| maxFrameRate: 3, | |
| chromeMediaSource: 'screen' | |
| } | |
| } | |
| }; | |
| getUserMedia(constraints, callback); | |
| } else { | |
| // chrome 34+ way requiring an extension | |
| var pending = window.setTimeout(function () { | |
| error = new Error('NavigatorUserMediaError'); | |
| error.name = 'EXTENSION_UNAVAILABLE'; | |
| return callback(error); | |
| }, 1000); | |
| cache[pending] = [callback, hasConstraints ? constraint : null]; | |
| window.postMessage({ type: 'getScreen', id: pending }, '*'); | |
| } | |
| } else if (window.navigator.userAgent.match('Firefox')) { | |
| var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); | |
| if (ffver >= 33) { | |
| constraints = (hasConstraints && constraints) || { | |
| video: { | |
| mozMediaSource: 'window', | |
| mediaSource: 'window' | |
| } | |
| } | |
| getUserMedia(constraints, function (err, stream) { | |
| callback(err, stream); | |
| // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 | |
| if (!err) { | |
| var lastTime = stream.currentTime; | |
| var polly = window.setInterval(function () { | |
| if (!stream) window.clearInterval(polly); | |
| if (stream.currentTime == lastTime) { | |
| window.clearInterval(polly); | |
| if (stream.onended) { | |
| stream.onended(); | |
| } | |
| } | |
| lastTime = stream.currentTime; | |
| }, 500); | |
| } | |
| }); | |
| } else { | |
| error = new Error('NavigatorUserMediaError'); | |
| error.name = 'EXTENSION_UNAVAILABLE'; // does not make much sense but... | |
| } | |
| } | |
| }; | |
| window.addEventListener('message', function (event) { | |
| if (event.origin != window.location.origin) { | |
| return; | |
| } | |
| if (event.data.type == 'gotScreen' && cache[event.data.id]) { | |
| var data = cache[event.data.id]; | |
| var constraints = data[1]; | |
| var callback = data[0]; | |
| delete cache[event.data.id]; | |
| if (event.data.sourceId === '') { // user canceled | |
| var error = new Error('NavigatorUserMediaError'); | |
| error.name = 'PERMISSION_DENIED'; | |
| callback(error); | |
| } else { | |
| constraints = constraints || {audio: false, video: { | |
| mandatory: { | |
| chromeMediaSource: 'desktop', | |
| maxWidth: window.screen.width, | |
| maxHeight: window.screen.height, | |
| maxFrameRate: 3 | |
| }, | |
| optional: [ | |
| {googLeakyBucket: true}, | |
| {googTemporalLayeredScreencast: true} | |
| ] | |
| }}; | |
| constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId; | |
| getUserMedia(constraints, callback); | |
| } | |
| } else if (event.data.type == 'getScreenPending') { | |
| window.clearTimeout(event.data.id); | |
| } | |
| }); | |
| },{"getusermedia":12}],13:[function(require,module,exports){ | |
| var WildEmitter = require('wildemitter'); | |
| function getMaxVolume (analyser, fftBins) { | |
| var maxVolume = -Infinity; | |
| analyser.getFloatFrequencyData(fftBins); | |
| for(var i=4, ii=fftBins.length; i < ii; i++) { | |
| if (fftBins[i] > maxVolume && fftBins[i] < 0) { | |
| maxVolume = fftBins[i]; | |
| } | |
| }; | |
| return maxVolume; | |
| } | |
| var audioContextType = window.AudioContext || window.webkitAudioContext; | |
| // use a single audio context due to hardware limits | |
| var audioContext = null; | |
| module.exports = function(stream, options) { | |
| var harker = new WildEmitter(); | |
| // make it not break in non-supported browsers | |
| if (!audioContextType) return harker; | |
| //Config | |
| var options = options || {}, | |
| smoothing = (options.smoothing || 0.1), | |
| interval = (options.interval || 50), | |
| threshold = options.threshold, | |
| play = options.play, | |
| history = options.history || 10, | |
| running = true; | |
| //Setup Audio Context | |
| if (!audioContext) { | |
| audioContext = new audioContextType(); | |
| } | |
| var sourceNode, fftBins, analyser; | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 512; | |
| analyser.smoothingTimeConstant = smoothing; | |
| fftBins = new Float32Array(analyser.fftSize); | |
| if (stream.jquery) stream = stream[0]; | |
| if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) { | |
| //Audio Tag | |
| sourceNode = audioContext.createMediaElementSource(stream); | |
| if (typeof play === 'undefined') play = true; | |
| threshold = threshold || -50; | |
| } else { | |
| //WebRTC Stream | |
| sourceNode = audioContext.createMediaStreamSource(stream); | |
| threshold = threshold || -50; | |
| } | |
| sourceNode.connect(analyser); | |
| if (play) analyser.connect(audioContext.destination); | |
| harker.speaking = false; | |
| harker.setThreshold = function(t) { | |
| threshold = t; | |
| }; | |
| harker.setInterval = function(i) { | |
| interval = i; | |
| }; | |
| harker.stop = function() { | |
| running = false; | |
| harker.emit('volume_change', -100, threshold); | |
| if (harker.speaking) { | |
| harker.speaking = false; | |
| harker.emit('stopped_speaking'); | |
| } | |
| }; | |
| harker.speakingHistory = []; | |
| for (var i = 0; i < history; i++) { | |
| harker.speakingHistory.push(0); | |
| } | |
| // Poll the analyser node to determine if speaking | |
| // and emit events if changed | |
| var looper = function() { | |
| setTimeout(function() { | |
| //check if stop has been called | |
| if(!running) { | |
| return; | |
| } | |
| var currentVolume = getMaxVolume(analyser, fftBins); | |
| harker.emit('volume_change', currentVolume, threshold); | |
| var history = 0; | |
| if (currentVolume > threshold && !harker.speaking) { | |
| // trigger quickly, short history | |
| for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) { | |
| history += harker.speakingHistory[i]; | |
| } | |
| if (history >= 2) { | |
| harker.speaking = true; | |
| harker.emit('speaking'); | |
| } | |
| } else if (currentVolume < threshold && harker.speaking) { | |
| for (var i = 0; i < harker.speakingHistory.length; i++) { | |
| history += harker.speakingHistory[i]; | |
| } | |
| if (history == 0) { | |
| harker.speaking = false; | |
| harker.emit('stopped_speaking'); | |
| } | |
| } | |
| harker.speakingHistory.shift(); | |
| harker.speakingHistory.push(0 + (currentVolume > threshold)); | |
| looper(); | |
| }, interval); | |
| }; | |
| looper(); | |
| return harker; | |
| } | |
| },{"wildemitter":4}],15:[function(require,module,exports){ | |
| var support = require('webrtcsupport'); | |
| function GainController(stream) { | |
| this.support = support.webAudio && support.mediaStream; | |
| // set our starting value | |
| this.gain = 1; | |
| if (this.support) { | |
| var context = this.context = new support.AudioContext(); | |
| this.microphone = context.createMediaStreamSource(stream); | |
| this.gainFilter = context.createGain(); | |
| this.destination = context.createMediaStreamDestination(); | |
| this.outputStream = this.destination.stream; | |
| this.microphone.connect(this.gainFilter); | |
| this.gainFilter.connect(this.destination); | |
| stream.addTrack(this.outputStream.getAudioTracks()[0]); | |
| stream.removeTrack(stream.getAudioTracks()[0]); | |
| } | |
| this.stream = stream; | |
| } | |
| // setting | |
| GainController.prototype.setGain = function (val) { | |
| // check for support | |
| if (!this.support) return; | |
| this.gainFilter.gain.value = val; | |
| this.gain = val; | |
| }; | |
| GainController.prototype.getGain = function () { | |
| return this.gain; | |
| }; | |
| GainController.prototype.off = function () { | |
| return this.setGain(0); | |
| }; | |
| GainController.prototype.on = function () { | |
| this.setGain(1); | |
| }; | |
| module.exports = GainController; | |
| },{"webrtcsupport":5}],20:[function(require,module,exports){ | |
| var SENDERS = require('./senders'); | |
| exports.toSessionSDP = function (session, opts) { | |
| var role = opts.role || 'initiator'; | |
| var direction = opts.direction || 'outgoing'; | |
| var sid = opts.sid || session.sid || Date.now(); | |
| var time = opts.time || Date.now(); | |
| var sdp = [ | |
| 'v=0', | |
| 'o=- ' + sid + ' ' + time + ' IN IP4 0.0.0.0', | |
| 's=-', | |
| 't=0 0' | |
| ]; | |
| var groups = session.groups || []; | |
| groups.forEach(function (group) { | |
| sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' ')); | |
| }); | |
| var contents = session.contents || []; | |
| contents.forEach(function (content) { | |
| sdp.push(exports.toMediaSDP(content, opts)); | |
| }); | |
| return sdp.join('\r\n') + '\r\n'; | |
| }; | |
| exports.toMediaSDP = function (content, opts) { | |
| var sdp = []; | |
| var role = opts.role || 'initiator'; | |
| var direction = opts.direction || 'outgoing'; | |
| var desc = content.description; | |
| var transport = content.transport; | |
| var payloads = desc.payloads || []; | |
| var fingerprints = (transport && transport.fingerprints) || []; | |
| var mline = []; | |
| if (desc.descType == 'datachannel') { | |
| mline.push('application'); | |
| mline.push('1'); | |
| mline.push('DTLS/SCTP'); | |
| if (transport.sctp) { | |
| transport.sctp.forEach(function (map) { | |
| mline.push(map.number); | |
| }); | |
| } | |
| } else { | |
| mline.push(desc.media); | |
| mline.push('1'); | |
| if ((desc.encryption && desc.encryption.length > 0) || (fingerprints.length > 0)) { | |
| mline.push('RTP/SAVPF'); | |
| } else { | |
| mline.push('RTP/AVPF'); | |
| } | |
| payloads.forEach(function (payload) { | |
| mline.push(payload.id); | |
| }); | |
| } | |
| sdp.push('m=' + mline.join(' ')); | |
| sdp.push('c=IN IP4 0.0.0.0'); | |
| if (desc.bandwidth && desc.bandwidth.type && desc.bandwidth.bandwidth) { | |
| sdp.push('b=' + desc.bandwidth.type + ':' + desc.bandwidth.bandwidth); | |
| } | |
| if (desc.descType == 'rtp') { | |
| sdp.push('a=rtcp:1 IN IP4 0.0.0.0'); | |
| } | |
| if (transport) { | |
| if (transport.ufrag) { | |
| sdp.push('a=ice-ufrag:' + transport.ufrag); | |
| } | |
| if (transport.pwd) { | |
| sdp.push('a=ice-pwd:' + transport.pwd); | |
| } | |
| var pushedSetup = false; | |
| fingerprints.forEach(function (fingerprint) { | |
| sdp.push('a=fingerprint:' + fingerprint.hash + ' ' + fingerprint.value); | |
| if (fingerprint.setup && !pushedSetup) { | |
| sdp.push('a=setup:' + fingerprint.setup); | |
| } | |
| }); | |
| if (transport.sctp) { | |
| transport.sctp.forEach(function (map) { | |
| sdp.push('a=sctpmap:' + map.number + ' ' + map.protocol + ' ' + map.streams); | |
| }); | |
| } | |
| } | |
| if (desc.descType == 'rtp') { | |
| sdp.push('a=' + (SENDERS[role][direction][content.senders] || 'sendrecv')); | |
| } | |
| sdp.push('a=mid:' + content.name); | |
| if (desc.mux) { | |
| sdp.push('a=rtcp-mux'); | |
| } | |
| var encryption = desc.encryption || []; | |
| encryption.forEach(function (crypto) { | |
| sdp.push('a=crypto:' + crypto.tag + ' ' + crypto.cipherSuite + ' ' + crypto.keyParams + (crypto.sessionParams ? ' ' + crypto.sessionParams : '')); | |
| }); | |
| if (desc.googConferenceFlag) { | |
| sdp.push('a=x-google-flag:conference'); | |
| } | |
| payloads.forEach(function (payload) { | |
| var rtpmap = 'a=rtpmap:' + payload.id + ' ' + payload.name + '/' + payload.clockrate; | |
| if (payload.channels && payload.channels != '1') { | |
| rtpmap += '/' + payload.channels; | |
| } | |
| sdp.push(rtpmap); | |
| if (payload.parameters && payload.parameters.length) { | |
| var fmtp = ['a=fmtp:' + payload.id]; | |
| var parameters = []; | |
| payload.parameters.forEach(function (param) { | |
| parameters.push((param.key ? param.key + '=' : '') + param.value); | |
| }); | |
| fmtp.push(parameters.join(';')); | |
| sdp.push(fmtp.join(' ')); | |
| } | |
| if (payload.feedback) { | |
| payload.feedback.forEach(function (fb) { | |
| if (fb.type === 'trr-int') { | |
| sdp.push('a=rtcp-fb:' + payload.id + ' trr-int ' + (fb.value ? fb.value : '0')); | |
| } else { | |
| sdp.push('a=rtcp-fb:' + payload.id + ' ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); | |
| } | |
| }); | |
| } | |
| }); | |
| if (desc.feedback) { | |
| desc.feedback.forEach(function (fb) { | |
| if (fb.type === 'trr-int') { | |
| sdp.push('a=rtcp-fb:* trr-int ' + (fb.value ? fb.value : '0')); | |
| } else { | |
| sdp.push('a=rtcp-fb:* ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); | |
| } | |
| }); | |
| } | |
| var hdrExts = desc.headerExtensions || []; | |
| hdrExts.forEach(function (hdr) { | |
| sdp.push('a=extmap:' + hdr.id + (hdr.senders ? '/' + SENDERS[role][direction][hdr.senders] : '') + ' ' + hdr.uri); | |
| }); | |
| var ssrcGroups = desc.sourceGroups || []; | |
| ssrcGroups.forEach(function (ssrcGroup) { | |
| sdp.push('a=ssrc-group:' + ssrcGroup.semantics + ' ' + ssrcGroup.sources.join(' ')); | |
| }); | |
| var ssrcs = desc.sources || []; | |
| ssrcs.forEach(function (ssrc) { | |
| for (var i = 0; i < ssrc.parameters.length; i++) { | |
| var param = ssrc.parameters[i]; | |
| sdp.push('a=ssrc:' + (ssrc.ssrc || desc.ssrc) + ' ' + param.key + (param.value ? (':' + param.value) : '')); | |
| } | |
| }); | |
| var candidates = transport.candidates || []; | |
| candidates.forEach(function (candidate) { | |
| sdp.push(exports.toCandidateSDP(candidate)); | |
| }); | |
| return sdp.join('\r\n'); | |
| }; | |
| exports.toCandidateSDP = function (candidate) { | |
| var sdp = []; | |
| sdp.push(candidate.foundation); | |
| sdp.push(candidate.component); | |
| sdp.push(candidate.protocol.toUpperCase()); | |
| sdp.push(candidate.priority); | |
| sdp.push(candidate.ip); | |
| sdp.push(candidate.port); | |
| var type = candidate.type; | |
| sdp.push('typ'); | |
| sdp.push(type); | |
| if (type === 'srflx' || type === 'prflx' || type === 'relay') { | |
| if (candidate.relAddr && candidate.relPort) { | |
| sdp.push('raddr'); | |
| sdp.push(candidate.relAddr); | |
| sdp.push('rport'); | |
| sdp.push(candidate.relPort); | |
| } | |
| } | |
| if (candidate.tcpType && candidate.protocol.toUpperCase() == 'TCP') { | |
| sdp.push('tcptype'); | |
| sdp.push(candidate.tcpType); | |
| } | |
| sdp.push('generation'); | |
| sdp.push(candidate.generation || '0'); | |
| // FIXME: apparently this is wrong per spec | |
| // but then, we need this when actually putting this into | |
| // SDP so it's going to stay. | |
| // decision needs to be revisited when browsers dont | |
| // accept this any longer | |
| return 'a=candidate:' + sdp.join(' '); | |
| }; | |
| },{"./senders":21}],19:[function(require,module,exports){ | |
| var SENDERS = require('./senders'); | |
| var parsers = require('./parsers'); | |
| var idCounter = Math.random(); | |
| exports._setIdCounter = function (counter) { | |
| idCounter = counter; | |
| }; | |
| exports.toSessionJSON = function (sdp, opts) { | |
| var i; | |
| var creators = opts.creators || []; | |
| var role = opts.role || 'initiator'; | |
| var direction = opts.direction || 'outgoing'; | |
| // Divide the SDP into session and media sections. | |
| var media = sdp.split('\r\nm='); | |
| for (i = 1; i < media.length; i++) { | |
| media[i] = 'm=' + media[i]; | |
| if (i !== media.length - 1) { | |
| media[i] += '\r\n'; | |
| } | |
| } | |
| var session = media.shift() + '\r\n'; | |
| var sessionLines = parsers.lines(session); | |
| var parsed = {}; | |
| var contents = []; | |
| for (i = 0; i < media.length; i++) { | |
| contents.push(exports.toMediaJSON(media[i], session, { | |
| role: role, | |
| direction: direction, | |
| creator: creators[i] || 'initiator' | |
| })); | |
| } | |
| parsed.contents = contents; | |
| var groupLines = parsers.findLines('a=group:', sessionLines); | |
| if (groupLines.length) { | |
| parsed.groups = parsers.groups(groupLines); | |
| } | |
| return parsed; | |
| }; | |
| exports.toMediaJSON = function (media, session, opts) { | |
| var creator = opts.creator || 'initiator'; | |
| var role = opts.role || 'initiator'; | |
| var direction = opts.direction || 'outgoing'; | |
| var lines = parsers.lines(media); | |
| var sessionLines = parsers.lines(session); | |
| var mline = parsers.mline(lines[0]); | |
| var content = { | |
| creator: creator, | |
| name: mline.media, | |
| description: { | |
| descType: 'rtp', | |
| media: mline.media, | |
| payloads: [], | |
| encryption: [], | |
| feedback: [], | |
| headerExtensions: [] | |
| }, | |
| transport: { | |
| transType: 'iceUdp', | |
| candidates: [], | |
| fingerprints: [] | |
| } | |
| }; | |
| if (mline.media == 'application') { | |
| // FIXME: the description is most likely to be independent | |
| // of the SDP and should be processed by other parts of the library | |
| content.description = { | |
| descType: 'datachannel' | |
| }; | |
| content.transport.sctp = []; | |
| } | |
| var desc = content.description; | |
| var trans = content.transport; | |
| // If we have a mid, use that for the content name instead. | |
| var mid = parsers.findLine('a=mid:', lines); | |
| if (mid) { | |
| content.name = mid.substr(6); | |
| } | |
| if (parsers.findLine('a=sendrecv', lines, sessionLines)) { | |
| content.senders = 'both'; | |
| } else if (parsers.findLine('a=sendonly', lines, sessionLines)) { | |
| content.senders = SENDERS[role][direction].sendonly; | |
| } else if (parsers.findLine('a=recvonly', lines, sessionLines)) { | |
| content.senders = SENDERS[role][direction].recvonly; | |
| } else if (parsers.findLine('a=inactive', lines, sessionLines)) { | |
| content.senders = 'none'; | |
| } | |
| if (desc.descType == 'rtp') { | |
| var bandwidth = parsers.findLine('b=', lines); | |
| if (bandwidth) { | |
| desc.bandwidth = parsers.bandwidth(bandwidth); | |
| } | |
| var ssrc = parsers.findLine('a=ssrc:', lines); | |
| if (ssrc) { | |
| desc.ssrc = ssrc.substr(7).split(' ')[0]; | |
| } | |
| var rtpmapLines = parsers.findLines('a=rtpmap:', lines); | |
| rtpmapLines.forEach(function (line) { | |
| var payload = parsers.rtpmap(line); | |
| payload.parameters = []; | |
| payload.feedback = []; | |
| var fmtpLines = parsers.findLines('a=fmtp:' + payload.id, lines); | |
| // There should only be one fmtp line per payload | |
| fmtpLines.forEach(function (line) { | |
| payload.parameters = parsers.fmtp(line); | |
| }); | |
| var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines); | |
| fbLines.forEach(function (line) { | |
| payload.feedback.push(parsers.rtcpfb(line)); | |
| }); | |
| desc.payloads.push(payload); | |
| }); | |
| var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines); | |
| cryptoLines.forEach(function (line) { | |
| desc.encryption.push(parsers.crypto(line)); | |
| }); | |
| if (parsers.findLine('a=rtcp-mux', lines)) { | |
| desc.mux = true; | |
| } | |
| var fbLines = parsers.findLines('a=rtcp-fb:*', lines); | |
| fbLines.forEach(function (line) { | |
| desc.feedback.push(parsers.rtcpfb(line)); | |
| }); | |
| var extLines = parsers.findLines('a=extmap:', lines); | |
| extLines.forEach(function (line) { | |
| var ext = parsers.extmap(line); | |
| ext.senders = SENDERS[role][direction][ext.senders]; | |
| desc.headerExtensions.push(ext); | |
| }); | |
| var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines); | |
| desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []); | |
| var ssrcLines = parsers.findLines('a=ssrc:', lines); | |
| desc.sources = parsers.sources(ssrcLines || []); | |
| if (parsers.findLine('a=x-google-flag:conference', lines, sessionLines)) { | |
| desc.googConferenceFlag = true; | |
| } | |
| } | |
| // transport specific attributes | |
| var fingerprintLines = parsers.findLines('a=fingerprint:', lines, sessionLines); | |
| var setup = parsers.findLine('a=setup:', lines, sessionLines); | |
| fingerprintLines.forEach(function (line) { | |
| var fp = parsers.fingerprint(line); | |
| if (setup) { | |
| fp.setup = setup.substr(8); | |
| } | |
| trans.fingerprints.push(fp); | |
| }); | |
| var ufragLine = parsers.findLine('a=ice-ufrag:', lines, sessionLines); | |
| var pwdLine = parsers.findLine('a=ice-pwd:', lines, sessionLines); | |
| if (ufragLine && pwdLine) { | |
| trans.ufrag = ufragLine.substr(12); | |
| trans.pwd = pwdLine.substr(10); | |
| trans.candidates = []; | |
| var candidateLines = parsers.findLines('a=candidate:', lines, sessionLines); | |
| candidateLines.forEach(function (line) { | |
| trans.candidates.push(exports.toCandidateJSON(line)); | |
| }); | |
| } | |
| if (desc.descType == 'datachannel') { | |
| var sctpmapLines = parsers.findLines('a=sctpmap:', lines); | |
| sctpmapLines.forEach(function (line) { | |
| var sctp = parsers.sctpmap(line); | |
| trans.sctp.push(sctp); | |
| }); | |
| } | |
| return content; | |
| }; | |
| exports.toCandidateJSON = function (line) { | |
| var candidate = parsers.candidate(line.split('\r\n')[0]); | |
| candidate.id = (idCounter++).toString(36).substr(0, 12); | |
| return candidate; | |
| }; | |
| },{"./parsers":22,"./senders":21}],22:[function(require,module,exports){ | |
| exports.lines = function (sdp) { | |
| return sdp.split('\r\n').filter(function (line) { | |
| return line.length > 0; | |
| }); | |
| }; | |
| exports.findLine = function (prefix, mediaLines, sessionLines) { | |
| var prefixLength = prefix.length; | |
| for (var i = 0; i < mediaLines.length; i++) { | |
| if (mediaLines[i].substr(0, prefixLength) === prefix) { | |
| return mediaLines[i]; | |
| } | |
| } | |
| // Continue searching in parent session section | |
| if (!sessionLines) { | |
| return false; | |
| } | |
| for (var j = 0; j < sessionLines.length; j++) { | |
| if (sessionLines[j].substr(0, prefixLength) === prefix) { | |
| return sessionLines[j]; | |
| } | |
| } | |
| return false; | |
| }; | |
| exports.findLines = function (prefix, mediaLines, sessionLines) { | |
| var results = []; | |
| var prefixLength = prefix.length; | |
| for (var i = 0; i < mediaLines.length; i++) { | |
| if (mediaLines[i].substr(0, prefixLength) === prefix) { | |
| results.push(mediaLines[i]); | |
| } | |
| } | |
| if (results.length || !sessionLines) { | |
| return results; | |
| } | |
| for (var j = 0; j < sessionLines.length; j++) { | |
| if (sessionLines[j].substr(0, prefixLength) === prefix) { | |
| results.push(sessionLines[j]); | |
| } | |
| } | |
| return results; | |
| }; | |
| exports.mline = function (line) { | |
| var parts = line.substr(2).split(' '); | |
| var parsed = { | |
| media: parts[0], | |
| port: parts[1], | |
| proto: parts[2], | |
| formats: [] | |
| }; | |
| for (var i = 3; i < parts.length; i++) { | |
| if (parts[i]) { | |
| parsed.formats.push(parts[i]); | |
| } | |
| } | |
| return parsed; | |
| }; | |
| exports.rtpmap = function (line) { | |
| var parts = line.substr(9).split(' '); | |
| var parsed = { | |
| id: parts.shift() | |
| }; | |
| parts = parts[0].split('/'); | |
| parsed.name = parts[0]; | |
| parsed.clockrate = parts[1]; | |
| parsed.channels = parts.length == 3 ? parts[2] : '1'; | |
| return parsed; | |
| }; | |
| exports.sctpmap = function (line) { | |
| // based on -05 draft | |
| var parts = line.substr(10).split(' '); | |
| var parsed = { | |
| number: parts.shift(), | |
| protocol: parts.shift(), | |
| streams: parts.shift() | |
| }; | |
| return parsed; | |
| }; | |
| exports.fmtp = function (line) { | |
| var kv, key, value; | |
| var parts = line.substr(line.indexOf(' ') + 1).split(';'); | |
| var parsed = []; | |
| for (var i = 0; i < parts.length; i++) { | |
| kv = parts[i].split('='); | |
| key = kv[0].trim(); | |
| value = kv[1]; | |
| if (key && value) { | |
| parsed.push({key: key, value: value}); | |
| } else if (key) { | |
| parsed.push({key: '', value: key}); | |
| } | |
| } | |
| return parsed; | |
| }; | |
| exports.crypto = function (line) { | |
| var parts = line.substr(9).split(' '); | |
| var parsed = { | |
| tag: parts[0], | |
| cipherSuite: parts[1], | |
| keyParams: parts[2], | |
| sessionParams: parts.slice(3).join(' ') | |
| }; | |
| return parsed; | |
| }; | |
| exports.fingerprint = function (line) { | |
| var parts = line.substr(14).split(' '); | |
| return { | |
| hash: parts[0], | |
| value: parts[1] | |
| }; | |
| }; | |
| exports.extmap = function (line) { | |
| var parts = line.substr(9).split(' '); | |
| var parsed = {}; | |
| var idpart = parts.shift(); | |
| var sp = idpart.indexOf('/'); | |
| if (sp >= 0) { | |
| parsed.id = idpart.substr(0, sp); | |
| parsed.senders = idpart.substr(sp + 1); | |
| } else { | |
| parsed.id = idpart; | |
| parsed.senders = 'sendrecv'; | |
| } | |
| parsed.uri = parts.shift() || ''; | |
| return parsed; | |
| }; | |
| exports.rtcpfb = function (line) { | |
| var parts = line.substr(10).split(' '); | |
| var parsed = {}; | |
| parsed.id = parts.shift(); | |
| parsed.type = parts.shift(); | |
| if (parsed.type === 'trr-int') { | |
| parsed.value = parts.shift(); | |
| } else { | |
| parsed.subtype = parts.shift() || ''; | |
| } | |
| parsed.parameters = parts; | |
| return parsed; | |
| }; | |
| exports.candidate = function (line) { | |
| var parts; | |
| if (line.indexOf('a=candidate:') === 0) { | |
| parts = line.substring(12).split(' '); | |
| } else { // no a=candidate | |
| parts = line.substring(10).split(' '); | |
| } | |
| var candidate = { | |
| foundation: parts[0], | |
| component: parts[1], | |
| protocol: parts[2].toLowerCase(), | |
| priority: parts[3], | |
| ip: parts[4], | |
| port: parts[5], | |
| // skip parts[6] == 'typ' | |
| type: parts[7], | |
| generation: '0' | |
| }; | |
| for (var i = 8; i < parts.length; i += 2) { | |
| if (parts[i] === 'raddr') { | |
| candidate.relAddr = parts[i + 1]; | |
| } else if (parts[i] === 'rport') { | |
| candidate.relPort = parts[i + 1]; | |
| } else if (parts[i] === 'generation') { | |
| candidate.generation = parts[i + 1]; | |
| } else if (parts[i] === 'tcptype') { | |
| candidate.tcpType = parts[i + 1]; | |
| } | |
| } | |
| candidate.network = '1'; | |
| return candidate; | |
| }; | |
| exports.sourceGroups = function (lines) { | |
| var parsed = []; | |
| for (var i = 0; i < lines.length; i++) { | |
| var parts = lines[i].substr(13).split(' '); | |
| parsed.push({ | |
| semantics: parts.shift(), | |
| sources: parts | |
| }); | |
| } | |
| return parsed; | |
| }; | |
| exports.sources = function (lines) { | |
| // http://tools.ietf.org/html/rfc5576 | |
| var parsed = []; | |
| var sources = {}; | |
| for (var i = 0; i < lines.length; i++) { | |
| var parts = lines[i].substr(7).split(' '); | |
| var ssrc = parts.shift(); | |
| if (!sources[ssrc]) { | |
| var source = { | |
| ssrc: ssrc, | |
| parameters: [] | |
| }; | |
| parsed.push(source); | |
| // Keep an index | |
| sources[ssrc] = source; | |
| } | |
| parts = parts.join(' ').split(':'); | |
| var attribute = parts.shift(); | |
| var value = parts.join(':') || null; | |
| sources[ssrc].parameters.push({ | |
| key: attribute, | |
| value: value | |
| }); | |
| } | |
| return parsed; | |
| }; | |
| exports.groups = function (lines) { | |
| // http://tools.ietf.org/html/rfc5888 | |
| var parsed = []; | |
| var parts; | |
| for (var i = 0; i < lines.length; i++) { | |
| parts = lines[i].substr(8).split(' '); | |
| parsed.push({ | |
| semantics: parts.shift(), | |
| contents: parts | |
| }); | |
| } | |
| return parsed; | |
| }; | |
| exports.bandwidth = function (line) { | |
| var parts = line.substr(2).split(':'); | |
| var parsed = {}; | |
| parsed.type = parts.shift(); | |
| parsed.bandwidth = parts.shift(); | |
| return parsed; | |
| }; | |
| },{}],21:[function(require,module,exports){ | |
| module.exports = { | |
| initiator: { | |
| incoming: { | |
| initiator: 'recvonly', | |
| responder: 'sendonly', | |
| both: 'sendrecv', | |
| none: 'inactive', | |
| recvonly: 'initiator', | |
| sendonly: 'responder', | |
| sendrecv: 'both', | |
| inactive: 'none' | |
| }, | |
| outgoing: { | |
| initiator: 'sendonly', | |
| responder: 'recvonly', | |
| both: 'sendrecv', | |
| none: 'inactive', | |
| recvonly: 'responder', | |
| sendonly: 'initiator', | |
| sendrecv: 'both', | |
| inactive: 'none' | |
| } | |
| }, | |
| responder: { | |
| incoming: { | |
| initiator: 'sendonly', | |
| responder: 'recvonly', | |
| both: 'sendrecv', | |
| none: 'inactive', | |
| recvonly: 'responder', | |
| sendonly: 'initiator', | |
| sendrecv: 'both', | |
| inactive: 'none' | |
| }, | |
| outgoing: { | |
| initiator: 'recvonly', | |
| responder: 'sendonly', | |
| both: 'sendrecv', | |
| none: 'inactive', | |
| recvonly: 'initiator', | |
| sendonly: 'responder', | |
| sendrecv: 'both', | |
| inactive: 'none' | |
| } | |
| } | |
| }; | |
| },{}],18:[function(require,module,exports){ | |
| // based on https://github.com/ESTOS/strophe.jingle/ | |
| // adds wildemitter support | |
| var util = require('util'); | |
| var webrtc = require('webrtcsupport'); | |
| var WildEmitter = require('wildemitter'); | |
| function dumpSDP(description) { | |
| return { | |
| type: description.type, | |
| sdp: description.sdp | |
| }; | |
| } | |
| function dumpStream(stream) { | |
| var info = { | |
| label: stream.id, | |
| }; | |
| if (stream.getAudioTracks().length) { | |
| info.audio = stream.getAudioTracks().map(function (track) { | |
| return track.id; | |
| }); | |
| } | |
| if (stream.getVideoTracks().length) { | |
| info.video = stream.getVideoTracks().map(function (track) { | |
| return track.id; | |
| }); | |
| } | |
| return info; | |
| } | |
| function TraceablePeerConnection(config, constraints) { | |
| var self = this; | |
| WildEmitter.call(this); | |
| this.peerconnection = new webrtc.PeerConnection(config, constraints); | |
| this.trace = function (what, info) { | |
| self.emit('PeerConnectionTrace', { | |
| time: new Date(), | |
| type: what, | |
| value: info || "" | |
| }); | |
| }; | |
| this.onicecandidate = null; | |
| this.peerconnection.onicecandidate = function (event) { | |
| self.trace('onicecandidate', event.candidate); | |
| if (self.onicecandidate !== null) { | |
| self.onicecandidate(event); | |
| } | |
| }; | |
| this.onaddstream = null; | |
| this.peerconnection.onaddstream = function (event) { | |
| self.trace('onaddstream', dumpStream(event.stream)); | |
| if (self.onaddstream !== null) { | |
| self.onaddstream(event); | |
| } | |
| }; | |
| this.onremovestream = null; | |
| this.peerconnection.onremovestream = function (event) { | |
| self.trace('onremovestream', dumpStream(event.stream)); | |
| if (self.onremovestream !== null) { | |
| self.onremovestream(event); | |
| } | |
| }; | |
| this.onsignalingstatechange = null; | |
| this.peerconnection.onsignalingstatechange = function (event) { | |
| self.trace('onsignalingstatechange', self.signalingState); | |
| if (self.onsignalingstatechange !== null) { | |
| self.onsignalingstatechange(event); | |
| } | |
| }; | |
| this.oniceconnectionstatechange = null; | |
| this.peerconnection.oniceconnectionstatechange = function (event) { | |
| self.trace('oniceconnectionstatechange', self.iceConnectionState); | |
| if (self.oniceconnectionstatechange !== null) { | |
| self.oniceconnectionstatechange(event); | |
| } | |
| }; | |
| this.onnegotiationneeded = null; | |
| this.peerconnection.onnegotiationneeded = function (event) { | |
| self.trace('onnegotiationneeded'); | |
| if (self.onnegotiationneeded !== null) { | |
| self.onnegotiationneeded(event); | |
| } | |
| }; | |
| self.ondatachannel = null; | |
| this.peerconnection.ondatachannel = function (event) { | |
| self.trace('ondatachannel', event); | |
| if (self.ondatachannel !== null) { | |
| self.ondatachannel(event); | |
| } | |
| }; | |
| this.getLocalStreams = this.peerconnection.getLocalStreams.bind(this.peerconnection); | |
| this.getRemoteStreams = this.peerconnection.getRemoteStreams.bind(this.peerconnection); | |
| } | |
| util.inherits(TraceablePeerConnection, WildEmitter); | |
| Object.defineProperty(TraceablePeerConnection.prototype, 'signalingState', { | |
| get: function () { | |
| return this.peerconnection.signalingState; | |
| } | |
| }); | |
| Object.defineProperty(TraceablePeerConnection.prototype, 'iceConnectionState', { | |
| get: function () { | |
| return this.peerconnection.iceConnectionState; | |
| } | |
| }); | |
| Object.defineProperty(TraceablePeerConnection.prototype, 'localDescription', { | |
| get: function () { | |
| return this.peerconnection.localDescription; | |
| } | |
| }); | |
| Object.defineProperty(TraceablePeerConnection.prototype, 'remoteDescription', { | |
| get: function () { | |
| return this.peerconnection.remoteDescription; | |
| } | |
| }); | |
| TraceablePeerConnection.prototype.addStream = function (stream) { | |
| this.trace('addStream', dumpStream(stream)); | |
| this.peerconnection.addStream(stream); | |
| }; | |
| TraceablePeerConnection.prototype.removeStream = function (stream) { | |
| this.trace('removeStream', dumpStream(stream)); | |
| this.peerconnection.removeStream(stream); | |
| }; | |
| TraceablePeerConnection.prototype.createDataChannel = function (label, opts) { | |
| this.trace('createDataChannel', label, opts); | |
| return this.peerconnection.createDataChannel(label, opts); | |
| }; | |
| TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) { | |
| var self = this; | |
| this.trace('setLocalDescription', dumpSDP(description)); | |
| this.peerconnection.setLocalDescription(description, | |
| function () { | |
| self.trace('setLocalDescriptionOnSuccess'); | |
| successCallback(); | |
| }, | |
| function (err) { | |
| self.trace('setLocalDescriptionOnFailure', err); | |
| failureCallback(err); | |
| } | |
| ); | |
| }; | |
| TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) { | |
| var self = this; | |
| this.trace('setRemoteDescription', dumpSDP(description)); | |
| this.peerconnection.setRemoteDescription(description, | |
| function () { | |
| self.trace('setRemoteDescriptionOnSuccess'); | |
| successCallback(); | |
| }, | |
| function (err) { | |
| self.trace('setRemoteDescriptionOnFailure', err); | |
| failureCallback(err); | |
| } | |
| ); | |
| }; | |
| TraceablePeerConnection.prototype.close = function () { | |
| this.trace('stop'); | |
| if (this.statsinterval !== null) { | |
| window.clearInterval(this.statsinterval); | |
| this.statsinterval = null; | |
| } | |
| if (this.peerconnection.signalingState != 'closed') { | |
| this.peerconnection.close(); | |
| } | |
| }; | |
| TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) { | |
| var self = this; | |
| this.trace('createOffer', constraints); | |
| this.peerconnection.createOffer( | |
| function (offer) { | |
| self.trace('createOfferOnSuccess', dumpSDP(offer)); | |
| successCallback(offer); | |
| }, | |
| function (err) { | |
| self.trace('createOfferOnFailure', err); | |
| failureCallback(err); | |
| }, | |
| constraints | |
| ); | |
| }; | |
| TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) { | |
| var self = this; | |
| this.trace('createAnswer', constraints); | |
| this.peerconnection.createAnswer( | |
| function (answer) { | |
| self.trace('createAnswerOnSuccess', dumpSDP(answer)); | |
| successCallback(answer); | |
| }, | |
| function (err) { | |
| self.trace('createAnswerOnFailure', err); | |
| failureCallback(err); | |
| }, | |
| constraints | |
| ); | |
| }; | |
| TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) { | |
| var self = this; | |
| this.trace('addIceCandidate', candidate); | |
| this.peerconnection.addIceCandidate(candidate, | |
| function () { | |
| //self.trace('addIceCandidateOnSuccess'); | |
| if (successCallback) successCallback(); | |
| }, | |
| function (err) { | |
| self.trace('addIceCandidateOnFailure', err); | |
| if (failureCallback) failureCallback(err); | |
| } | |
| ); | |
| }; | |
| TraceablePeerConnection.prototype.getStats = function (callback, errback) { | |
| if (navigator.mozGetUserMedia) { | |
| this.peerconnection.getStats(null, callback, errback); | |
| } else { | |
| this.peerconnection.getStats(callback); | |
| } | |
| }; | |
| module.exports = TraceablePeerConnection; | |
| },{"util":2,"webrtcsupport":5,"wildemitter":4}]},{},[1])(1) | |
| }); | |
| ; |