diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2019-03-27 12:42:37 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-27 12:42:37 +0300 |
commit | cd90f8a43a33f0cf284d019c9379970c00f15065 (patch) | |
tree | ac2e8c03e8c24c2427720b4b71b21f73ba0e285d | |
parent | 9b082ea8894f54ebd58e0408009ab3f4a5b9b720 (diff) | |
parent | 807ae4732aeadd1c50cb92f9d2453b1482b373b3 (diff) |
Merge pull request #1524 from nextcloud/add-support-for-calls-in-other-browsers
Add support for calls in other browsers
-rw-r--r-- | .eslintignore | 2 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | js/merged-files.json | 2 | ||||
-rw-r--r-- | js/merged-guest.json | 2 | ||||
-rw-r--r-- | js/merged-share-auth.json | 2 | ||||
-rw-r--r-- | js/merged.json | 2 | ||||
-rw-r--r-- | js/simplewebrtc.js | 18562 | ||||
-rw-r--r-- | js/simplewebrtc/bundled.js | 8245 | ||||
-rw-r--r-- | js/simplewebrtc/getscreenmedia.js | 182 | ||||
-rw-r--r-- | js/simplewebrtc/localmedia.js | 337 | ||||
-rw-r--r-- | js/simplewebrtc/peer.js | 306 | ||||
-rw-r--r-- | js/simplewebrtc/simplewebrtc.js | 422 | ||||
-rw-r--r-- | js/simplewebrtc/webrtc.js | 164 | ||||
-rw-r--r-- | js/webrtc.js | 23 | ||||
-rw-r--r-- | package.json | 13 |
15 files changed, 9688 insertions, 18582 deletions
diff --git a/.eslintignore b/.eslintignore index dc3276491..6a71b494f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,7 @@ /js/admin/* /js/tests/* /js/vendor/* -/js/simplewebrtc.js +/js/simplewebrtc/* /js/**/templates.js /karma.conf.js /tests/* @@ -34,6 +34,12 @@ install-npm-deps-dev: compile-handlebars-templates: dev-setup bash compile-handlebars-templates.sh +bundle-simplewebrtc: dev-setup + # webrtc-adapter uses JavaScript features not supported by browserify, + # so the sources need to be transformed using babel to a compatible + # version of JavaScript. + npx browserify --standalone SimpleWebRTC --transform [ babelify --global --presets [ @babel/env ] ] js/simplewebrtc/simplewebrtc.js > js/simplewebrtc/bundled.js + dev-setup: install-npm-deps-dev appstore: clean install-deps diff --git a/js/merged-files.json b/js/merged-files.json index 6e139e1ba..79094e011 100644 --- a/js/merged-files.json +++ b/js/merged-files.json @@ -6,6 +6,7 @@ "vendor/jshashes/hashes.min.js", "vendor/Caret.js/dist/jquery.caret.min.js", "vendor/At.js/dist/js/jquery.atwho.min.js", + "simplewebrtc/bundled.js", "models/chatmessage.js", "models/chatmessagecollection.js", "models/room.js", @@ -21,7 +22,6 @@ "views/templates.js", "views/videoview.js", "views/virtuallist.js", - "simplewebrtc.js", "webrtc.js", "signaling.js", "connection.js", diff --git a/js/merged-guest.json b/js/merged-guest.json index 755155f2f..2d67565b3 100644 --- a/js/merged-guest.json +++ b/js/merged-guest.json @@ -6,6 +6,7 @@ "vendor/jshashes/hashes.min.js", "vendor/Caret.js/dist/jquery.caret.min.js", "vendor/At.js/dist/js/jquery.atwho.min.js", + "simplewebrtc/bundled.js", "models/chatmessage.js", "models/chatmessagecollection.js", "models/localstoragemodel.js", @@ -30,7 +31,6 @@ "views/templates.js", "views/videoview.js", "views/virtuallist.js", - "simplewebrtc.js", "webrtc.js", "signaling.js", "connection.js", diff --git a/js/merged-share-auth.json b/js/merged-share-auth.json index 567fc1f5e..11a6d21c8 100644 --- a/js/merged-share-auth.json +++ b/js/merged-share-auth.json @@ -6,6 +6,7 @@ "vendor/jshashes/hashes.min.js", "vendor/Caret.js/dist/jquery.caret.min.js", "vendor/At.js/dist/js/jquery.atwho.min.js", + "simplewebrtc/bundled.js", "models/chatmessage.js", "models/chatmessagecollection.js", "models/localstoragemodel.js", @@ -30,7 +31,6 @@ "views/templates.js", "views/videoview.js", "views/virtuallist.js", - "simplewebrtc.js", "webrtc.js", "signaling.js", "connection.js", diff --git a/js/merged.json b/js/merged.json index db9906fe5..b06afba55 100644 --- a/js/merged.json +++ b/js/merged.json @@ -6,6 +6,7 @@ "vendor/jshashes/hashes.min.js", "vendor/Caret.js/dist/jquery.caret.min.js", "vendor/At.js/dist/js/jquery.atwho.min.js", + "simplewebrtc/bundled.js", "models/chatmessage.js", "models/chatmessagecollection.js", "models/room.js", @@ -29,7 +30,6 @@ "views/templates.js", "views/videoview.js", "views/virtuallist.js", - "simplewebrtc.js", "webrtc.js", "signaling.js", "connection.js", diff --git a/js/simplewebrtc.js b/js/simplewebrtc.js deleted file mode 100644 index eaa7370f6..000000000 --- a/js/simplewebrtc.js +++ /dev/null @@ -1,18562 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.SimpleWebRTC = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ - module.exports = after - - function after(count, callback, err_cb) { - var bail = false - err_cb = err_cb || noop - proxy.count = count - - return (count === 0) ? callback() : proxy - - function proxy(err, result) { - if (proxy.count <= 0) { - throw new Error('after called too many times') - } - --proxy.count - - // after first error, rest are passed to err_cb - if (err) { - bail = true - callback(err) - // future error callbacks will go to error handler - callback = err_cb - } else if (proxy.count === 0 && !bail) { - callback(null, result) - } - } - } - - function noop() {} - -},{}],2:[function(require,module,exports){ - /** - * An abstraction for slicing an arraybuffer even when - * ArrayBuffer.prototype.slice is not supported - * - * @api public - */ - - module.exports = function(arraybuffer, start, end) { - var bytes = arraybuffer.byteLength; - start = start || 0; - end = end || bytes; - - if (arraybuffer.slice) { return arraybuffer.slice(start, end); } - - if (start < 0) { start += bytes; } - if (end < 0) { end += bytes; } - if (end > bytes) { end = bytes; } - - if (start >= bytes || start >= end || bytes === 0) { - return new ArrayBuffer(0); - } - - var abv = new Uint8Array(arraybuffer); - var result = new Uint8Array(end - start); - for (var i = start, ii = 0; i < end; i++, ii++) { - result[ii] = abv[i]; - } - return result.buffer; - }; - -},{}],3:[function(require,module,exports){ - var adapter = require('webrtc-adapter'); - module.exports = function (stream, el, options) { - var item; - var URL = window.URL; - var element = el; - var opts = { - autoplay: true, - mirror: false, - muted: false, - audio: false, - disableContextMenu: false - }; - - if (options) { - for (item in options) { - opts[item] = options[item]; - } - } - - if (!element) { - element = document.createElement(opts.audio ? 'audio' : 'video'); - } else if (element.tagName.toLowerCase() === 'audio') { - opts.audio = true; - } - - if (opts.disableContextMenu) { - element.oncontextmenu = function (e) { - e.preventDefault(); - }; - } - - if (opts.autoplay) element.autoplay = 'autoplay'; - if (opts.muted) element.muted = true; - if (!opts.audio && opts.mirror) { - ['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) { - var styleName = prefix ? prefix + 'Transform' : 'transform'; - element.style[styleName] = 'scaleX(-1)'; - }); - } - - element.srcObject = stream; - return element; - }; - -},{"webrtc-adapter":90}],4:[function(require,module,exports){ - - /** - * Expose `Backoff`. - */ - - module.exports = Backoff; - - /** - * Initialize backoff timer with `opts`. - * - * - `min` initial timeout in milliseconds [100] - * - `max` max timeout [10000] - * - `jitter` [0] - * - `factor` [2] - * - * @param {Object} opts - * @api public - */ - - function Backoff(opts) { - opts = opts || {}; - this.ms = opts.min || 100; - this.max = opts.max || 10000; - this.factor = opts.factor || 2; - this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; - this.attempts = 0; - } - - /** - * Return the backoff duration. - * - * @return {Number} - * @api public - */ - - Backoff.prototype.duration = function(){ - var ms = this.ms * Math.pow(this.factor, this.attempts++); - if (this.jitter) { - var rand = Math.random(); - var deviation = Math.floor(rand * this.jitter * ms); - ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; - } - return Math.min(ms, this.max) | 0; - }; - - /** - * Reset the number of attempts. - * - * @api public - */ - - Backoff.prototype.reset = function(){ - this.attempts = 0; - }; - - /** - * Set the minimum duration - * - * @api public - */ - - Backoff.prototype.setMin = function(min){ - this.ms = min; - }; - - /** - * Set the maximum duration - * - * @api public - */ - - Backoff.prototype.setMax = function(max){ - this.max = max; - }; - - /** - * Set the jitter - * - * @api public - */ - - Backoff.prototype.setJitter = function(jitter){ - this.jitter = jitter; - }; - - -},{}],5:[function(require,module,exports){ - /* - * base64-arraybuffer - * https://github.com/niklasvh/base64-arraybuffer - * - * Copyright (c) 2012 Niklas von Hertzen - * Licensed under the MIT license. - */ - (function(chars){ - "use strict"; - - exports.encode = function(arraybuffer) { - var bytes = new Uint8Array(arraybuffer), - i, len = bytes.length, base64 = ""; - - for (i = 0; i < len; i+=3) { - base64 += chars[bytes[i] >> 2]; - base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; - base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; - base64 += chars[bytes[i + 2] & 63]; - } - - if ((len % 3) === 2) { - base64 = base64.substring(0, base64.length - 1) + "="; - } else if (len % 3 === 1) { - base64 = base64.substring(0, base64.length - 2) + "=="; - } - - return base64; - }; - - exports.decode = function(base64) { - var bufferLength = base64.length * 0.75, - len = base64.length, i, p = 0, - encoded1, encoded2, encoded3, encoded4; - - if (base64[base64.length - 1] === "=") { - bufferLength--; - if (base64[base64.length - 2] === "=") { - bufferLength--; - } - } - - var arraybuffer = new ArrayBuffer(bufferLength), - bytes = new Uint8Array(arraybuffer); - - for (i = 0; i < len; i+=4) { - encoded1 = chars.indexOf(base64[i]); - encoded2 = chars.indexOf(base64[i+1]); - encoded3 = chars.indexOf(base64[i+2]); - encoded4 = chars.indexOf(base64[i+3]); - - bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); - bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); - bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); - } - - return arraybuffer; - }; - })("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); - -},{}],6:[function(require,module,exports){ - (function (global){ - /** - * Create a blob builder even when vendor prefixes exist - */ - - var BlobBuilder = global.BlobBuilder - || global.WebKitBlobBuilder - || global.MSBlobBuilder - || global.MozBlobBuilder; - - /** - * Check if Blob constructor is supported - */ - - var blobSupported = (function() { - try { - var a = new Blob(['hi']); - return a.size === 2; - } catch(e) { - return false; - } - })(); - - /** - * Check if Blob constructor supports ArrayBufferViews - * Fails in Safari 6, so we need to map to ArrayBuffers there. - */ - - var blobSupportsArrayBufferView = blobSupported && (function() { - try { - var b = new Blob([new Uint8Array([1,2])]); - return b.size === 2; - } catch(e) { - return false; - } - })(); - - /** - * Check if BlobBuilder is supported - */ - - var blobBuilderSupported = BlobBuilder - && BlobBuilder.prototype.append - && BlobBuilder.prototype.getBlob; - - /** - * Helper function that maps ArrayBufferViews to ArrayBuffers - * Used by BlobBuilder constructor and old browsers that didn't - * support it in the Blob constructor. - */ - - function mapArrayBufferViews(ary) { - for (var i = 0; i < ary.length; i++) { - var chunk = ary[i]; - if (chunk.buffer instanceof ArrayBuffer) { - var buf = chunk.buffer; - - // if this is a subarray, make a copy so we only - // include the subarray region from the underlying buffer - if (chunk.byteLength !== buf.byteLength) { - var copy = new Uint8Array(chunk.byteLength); - copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); - buf = copy.buffer; - } - - ary[i] = buf; - } - } - } - - function BlobBuilderConstructor(ary, options) { - options = options || {}; - - var bb = new BlobBuilder(); - mapArrayBufferViews(ary); - - for (var i = 0; i < ary.length; i++) { - bb.append(ary[i]); - } - - return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); - }; - - function BlobConstructor(ary, options) { - mapArrayBufferViews(ary); - return new Blob(ary, options || {}); - }; - - module.exports = (function() { - if (blobSupported) { - return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; - } else if (blobBuilderSupported) { - return BlobBuilderConstructor; - } else { - return undefined; - } - })(); - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],7:[function(require,module,exports){ - /** - * Slice reference. - */ - - var slice = [].slice; - - /** - * Bind `obj` to `fn`. - * - * @param {Object} obj - * @param {Function|String} fn or string - * @return {Function} - * @api public - */ - - module.exports = function(obj, fn){ - if ('string' == typeof fn) fn = obj[fn]; - if ('function' != typeof fn) throw new Error('bind() requires a function'); - var args = slice.call(arguments, 2); - return function(){ - return fn.apply(obj, args.concat(slice.call(arguments))); - } - }; - -},{}],8:[function(require,module,exports){ - - /** - * Expose `Emitter`. - */ - - module.exports = Emitter; - - /** - * Initialize a new `Emitter`. - * - * @api public - */ - - function Emitter(obj) { - if (obj) return mixin(obj); - }; - - /** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - - function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; - } - - /** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.on = - Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; - }; - - /** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; - }; - - /** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.off = - Emitter.prototype.removeListener = - Emitter.prototype.removeAllListeners = - Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; - }; - - /** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - - Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; - }; - - /** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - - Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; - }; - - /** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - - Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; - }; - -},{}],9:[function(require,module,exports){ - - module.exports = function(a, b){ - var fn = function(){}; - fn.prototype = b.prototype; - a.prototype = new fn; - a.prototype.constructor = a; - }; -},{}],10:[function(require,module,exports){ - - module.exports = require('./lib/'); - -},{"./lib/":11}],11:[function(require,module,exports){ - - module.exports = require('./socket'); - - /** - * Exports parser - * - * @api public - * - */ - module.exports.parser = require('engine.io-parser'); - -},{"./socket":12,"engine.io-parser":24}],12:[function(require,module,exports){ - (function (global){ - /** - * Module dependencies. - */ - - var transports = require('./transports'); - var Emitter = require('component-emitter'); - var debug = require('debug')('engine.io-client:socket'); - var index = require('indexof'); - var parser = require('engine.io-parser'); - var parseuri = require('parseuri'); - var parsejson = require('parsejson'); - var parseqs = require('parseqs'); - - /** - * Module exports. - */ - - module.exports = Socket; - - /** - * Noop function. - * - * @api private - */ - - function noop(){} - - /** - * Socket constructor. - * - * @param {String|Object} uri or options - * @param {Object} options - * @api public - */ - - function Socket(uri, opts){ - if (!(this instanceof Socket)) return new Socket(uri, opts); - - opts = opts || {}; - - if (uri && 'object' == typeof uri) { - opts = uri; - uri = null; - } - - if (uri) { - uri = parseuri(uri); - opts.host = uri.host; - opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; - opts.port = uri.port; - if (uri.query) opts.query = uri.query; - } - - this.secure = null != opts.secure ? opts.secure : - (global.location && 'https:' == location.protocol); - - if (opts.host) { - var pieces = opts.host.split(':'); - opts.hostname = pieces.shift(); - if (pieces.length) { - opts.port = pieces.pop(); - } else if (!opts.port) { - // if no port is specified manually, use the protocol default - opts.port = this.secure ? '443' : '80'; - } - } - - this.agent = opts.agent || false; - this.hostname = opts.hostname || - (global.location ? location.hostname : 'localhost'); - this.port = opts.port || (global.location && location.port ? - location.port : - (this.secure ? 443 : 80)); - this.query = opts.query || {}; - if ('string' == typeof this.query) this.query = parseqs.decode(this.query); - this.upgrade = false !== opts.upgrade; - this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; - this.forceJSONP = !!opts.forceJSONP; - this.jsonp = false !== opts.jsonp; - this.forceBase64 = !!opts.forceBase64; - this.enablesXDR = !!opts.enablesXDR; - this.timestampParam = opts.timestampParam || 't'; - this.timestampRequests = opts.timestampRequests; - this.transports = opts.transports || ['polling', 'websocket']; - this.readyState = ''; - this.writeBuffer = []; - this.callbackBuffer = []; - this.policyPort = opts.policyPort || 843; - this.rememberUpgrade = opts.rememberUpgrade || false; - this.binaryType = null; - this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; - - // SSL options for Node.js client - this.pfx = opts.pfx || null; - this.key = opts.key || null; - this.passphrase = opts.passphrase || null; - this.cert = opts.cert || null; - this.ca = opts.ca || null; - this.ciphers = opts.ciphers || null; - this.rejectUnauthorized = opts.rejectUnauthorized || null; - - this.open(); - } - - Socket.priorWebsocketSuccess = false; - - /** - * Mix in `Emitter`. - */ - - Emitter(Socket.prototype); - - /** - * Protocol version. - * - * @api public - */ - - Socket.protocol = parser.protocol; // this is an int - - /** - * Expose deps for legacy compatibility - * and standalone browser access. - */ - - Socket.Socket = Socket; - Socket.Transport = require('./transport'); - Socket.transports = require('./transports'); - Socket.parser = require('engine.io-parser'); - - /** - * Creates transport of the given type. - * - * @param {String} transport name - * @return {Transport} - * @api private - */ - - Socket.prototype.createTransport = function (name) { - debug('creating transport "%s"', name); - var query = clone(this.query); - - // append engine.io protocol identifier - query.EIO = parser.protocol; - - // transport name - query.transport = name; - - // session id if we already have one - if (this.id) query.sid = this.id; - - var transport = new transports[name]({ - agent: this.agent, - hostname: this.hostname, - port: this.port, - secure: this.secure, - path: this.path, - query: query, - forceJSONP: this.forceJSONP, - jsonp: this.jsonp, - forceBase64: this.forceBase64, - enablesXDR: this.enablesXDR, - timestampRequests: this.timestampRequests, - timestampParam: this.timestampParam, - policyPort: this.policyPort, - socket: this, - pfx: this.pfx, - key: this.key, - passphrase: this.passphrase, - cert: this.cert, - ca: this.ca, - ciphers: this.ciphers, - rejectUnauthorized: this.rejectUnauthorized - }); - - return transport; - }; - - function clone (obj) { - var o = {}; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - o[i] = obj[i]; - } - } - return o; - } - - /** - * Initializes transport to use and starts probe. - * - * @api private - */ - Socket.prototype.open = function () { - var transport; - if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { - transport = 'websocket'; - } else if (0 == this.transports.length) { - // Emit error on next tick so it can be listened to - var self = this; - setTimeout(function() { - self.emit('error', 'No transports available'); - }, 0); - return; - } else { - transport = this.transports[0]; - } - this.readyState = 'opening'; - - // Retry with the next transport if the transport is disabled (jsonp: false) - var transport; - try { - transport = this.createTransport(transport); - } catch (e) { - this.transports.shift(); - this.open(); - return; - } - - transport.open(); - this.setTransport(transport); - }; - - /** - * Sets the current transport. Disables the existing one (if any). - * - * @api private - */ - - Socket.prototype.setTransport = function(transport){ - debug('setting transport %s', transport.name); - var self = this; - - if (this.transport) { - debug('clearing existing transport %s', this.transport.name); - this.transport.removeAllListeners(); - } - - // set up transport - this.transport = transport; - - // set up transport listeners - transport - .on('drain', function(){ - self.onDrain(); - }) - .on('packet', function(packet){ - self.onPacket(packet); - }) - .on('error', function(e){ - self.onError(e); - }) - .on('close', function(){ - self.onClose('transport close'); - }); - }; - - /** - * Probes a transport. - * - * @param {String} transport name - * @api private - */ - - Socket.prototype.probe = function (name) { - debug('probing transport "%s"', name); - var transport = this.createTransport(name, { probe: 1 }) - , failed = false - , self = this; - - Socket.priorWebsocketSuccess = false; - - function onTransportOpen(){ - if (self.onlyBinaryUpgrades) { - var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; - failed = failed || upgradeLosesBinary; - } - if (failed) return; - - debug('probe transport "%s" opened', name); - transport.send([{ type: 'ping', data: 'probe' }]); - transport.once('packet', function (msg) { - if (failed) return; - if ('pong' == msg.type && 'probe' == msg.data) { - debug('probe transport "%s" pong', name); - self.upgrading = true; - self.emit('upgrading', transport); - if (!transport) return; - Socket.priorWebsocketSuccess = 'websocket' == transport.name; - - debug('pausing current transport "%s"', self.transport.name); - self.transport.pause(function () { - if (failed) return; - if ('closed' == self.readyState) return; - debug('changing transport and sending upgrade packet'); - - cleanup(); - - self.setTransport(transport); - transport.send([{ type: 'upgrade' }]); - self.emit('upgrade', transport); - transport = null; - self.upgrading = false; - self.flush(); - }); - } else { - debug('probe transport "%s" failed', name); - var err = new Error('probe error'); - err.transport = transport.name; - self.emit('upgradeError', err); - } - }); - } - - function freezeTransport() { - if (failed) return; - - // Any callback called by transport should be ignored since now - failed = true; - - cleanup(); - - transport.close(); - transport = null; - } - - //Handle any error that happens while probing - function onerror(err) { - var error = new Error('probe error: ' + err); - error.transport = transport.name; - - freezeTransport(); - - debug('probe transport "%s" failed because of error: %s', name, err); - - self.emit('upgradeError', error); - } - - function onTransportClose(){ - onerror("transport closed"); - } - - //When the socket is closed while we're probing - function onclose(){ - onerror("socket closed"); - } - - //When the socket is upgraded while we're probing - function onupgrade(to){ - if (transport && to.name != transport.name) { - debug('"%s" works - aborting "%s"', to.name, transport.name); - freezeTransport(); - } - } - - //Remove all listeners on the transport and on self - function cleanup(){ - transport.removeListener('open', onTransportOpen); - transport.removeListener('error', onerror); - transport.removeListener('close', onTransportClose); - self.removeListener('close', onclose); - self.removeListener('upgrading', onupgrade); - } - - transport.once('open', onTransportOpen); - transport.once('error', onerror); - transport.once('close', onTransportClose); - - this.once('close', onclose); - this.once('upgrading', onupgrade); - - transport.open(); - - }; - - /** - * Called when connection is deemed open. - * - * @api public - */ - - Socket.prototype.onOpen = function () { - debug('socket open'); - this.readyState = 'open'; - Socket.priorWebsocketSuccess = 'websocket' == this.transport.name; - this.emit('open'); - this.flush(); - - // we check for `readyState` in case an `open` - // listener already closed the socket - if ('open' == this.readyState && this.upgrade && this.transport.pause) { - debug('starting upgrade probes'); - for (var i = 0, l = this.upgrades.length; i < l; i++) { - this.probe(this.upgrades[i]); - } - } - }; - - /** - * Handles a packet. - * - * @api private - */ - - Socket.prototype.onPacket = function (packet) { - if ('opening' == this.readyState || 'open' == this.readyState) { - debug('socket receive: type "%s", data "%s"', packet.type, packet.data); - - this.emit('packet', packet); - - // Socket is live - any packet counts - this.emit('heartbeat'); - - switch (packet.type) { - case 'open': - this.onHandshake(parsejson(packet.data)); - break; - - case 'pong': - this.setPing(); - break; - - case 'error': - var err = new Error('server error'); - err.code = packet.data; - this.emit('error', err); - break; - - case 'message': - this.emit('data', packet.data); - this.emit('message', packet.data); - break; - } - } else { - debug('packet received with socket readyState "%s"', this.readyState); - } - }; - - /** - * Called upon handshake completion. - * - * @param {Object} handshake obj - * @api private - */ - - Socket.prototype.onHandshake = function (data) { - this.emit('handshake', data); - this.id = data.sid; - this.transport.query.sid = data.sid; - this.upgrades = this.filterUpgrades(data.upgrades); - this.pingInterval = data.pingInterval; - this.pingTimeout = data.pingTimeout; - this.onOpen(); - // In case open handler closes socket - if ('closed' == this.readyState) return; - this.setPing(); - - // Prolong liveness of socket on heartbeat - this.removeListener('heartbeat', this.onHeartbeat); - this.on('heartbeat', this.onHeartbeat); - }; - - /** - * Resets ping timeout. - * - * @api private - */ - - Socket.prototype.onHeartbeat = function (timeout) { - clearTimeout(this.pingTimeoutTimer); - var self = this; - self.pingTimeoutTimer = setTimeout(function () { - if ('closed' == self.readyState) return; - self.onClose('ping timeout'); - }, timeout || (self.pingInterval + self.pingTimeout)); - }; - - /** - * Pings server every `this.pingInterval` and expects response - * within `this.pingTimeout` or closes connection. - * - * @api private - */ - - Socket.prototype.setPing = function () { - var self = this; - clearTimeout(self.pingIntervalTimer); - self.pingIntervalTimer = setTimeout(function () { - debug('writing ping packet - expecting pong within %sms', self.pingTimeout); - self.ping(); - self.onHeartbeat(self.pingTimeout); - }, self.pingInterval); - }; - - /** - * Sends a ping packet. - * - * @api public - */ - - Socket.prototype.ping = function () { - this.sendPacket('ping'); - }; - - /** - * Called on `drain` event - * - * @api private - */ - - Socket.prototype.onDrain = function() { - for (var i = 0; i < this.prevBufferLen; i++) { - if (this.callbackBuffer[i]) { - this.callbackBuffer[i](); - } - } - - this.writeBuffer.splice(0, this.prevBufferLen); - this.callbackBuffer.splice(0, this.prevBufferLen); - - // setting prevBufferLen = 0 is very important - // for example, when upgrading, upgrade packet is sent over, - // and a nonzero prevBufferLen could cause problems on `drain` - this.prevBufferLen = 0; - - if (this.writeBuffer.length == 0) { - this.emit('drain'); - } else { - this.flush(); - } - }; - - /** - * Flush write buffers. - * - * @api private - */ - - Socket.prototype.flush = function () { - if ('closed' != this.readyState && this.transport.writable && - !this.upgrading && this.writeBuffer.length) { - debug('flushing %d packets in socket', this.writeBuffer.length); - this.transport.send(this.writeBuffer); - // keep track of current length of writeBuffer - // splice writeBuffer and callbackBuffer on `drain` - this.prevBufferLen = this.writeBuffer.length; - this.emit('flush'); - } - }; - - /** - * Sends a message. - * - * @param {String} message. - * @param {Function} callback function. - * @return {Socket} for chaining. - * @api public - */ - - Socket.prototype.write = - Socket.prototype.send = function (msg, fn) { - this.sendPacket('message', msg, fn); - return this; - }; - - /** - * Sends a packet. - * - * @param {String} packet type. - * @param {String} data. - * @param {Function} callback function. - * @api private - */ - - Socket.prototype.sendPacket = function (type, data, fn) { - if ('closing' == this.readyState || 'closed' == this.readyState) { - return; - } - - var packet = { type: type, data: data }; - this.emit('packetCreate', packet); - this.writeBuffer.push(packet); - this.callbackBuffer.push(fn); - this.flush(); - }; - - /** - * Closes the connection. - * - * @api private - */ - - Socket.prototype.close = function () { - if ('opening' == this.readyState || 'open' == this.readyState) { - this.readyState = 'closing'; - - var self = this; - - function close() { - self.onClose('forced close'); - debug('socket closing - telling transport to close'); - self.transport.close(); - } - - function cleanupAndClose() { - self.removeListener('upgrade', cleanupAndClose); - self.removeListener('upgradeError', cleanupAndClose); - close(); - } - - function waitForUpgrade() { - // wait for upgrade to finish since we can't send packets while pausing a transport - self.once('upgrade', cleanupAndClose); - self.once('upgradeError', cleanupAndClose); - } - - if (this.writeBuffer.length) { - this.once('drain', function() { - if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - }); - } else if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - } - - return this; - }; - - /** - * Called upon transport error - * - * @api private - */ - - Socket.prototype.onError = function (err) { - debug('socket error %j', err); - Socket.priorWebsocketSuccess = false; - this.emit('error', err); - this.onClose('transport error', err); - }; - - /** - * Called upon transport close. - * - * @api private - */ - - Socket.prototype.onClose = function (reason, desc) { - if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) { - debug('socket close with reason: "%s"', reason); - var self = this; - - // clear timers - clearTimeout(this.pingIntervalTimer); - clearTimeout(this.pingTimeoutTimer); - - // clean buffers in next tick, so developers can still - // grab the buffers on `close` event - setTimeout(function() { - self.writeBuffer = []; - self.callbackBuffer = []; - self.prevBufferLen = 0; - }, 0); - - // stop event from firing again for transport - this.transport.removeAllListeners('close'); - - // ensure transport won't stay open - this.transport.close(); - - // ignore further transport communication - this.transport.removeAllListeners(); - - // set ready state - this.readyState = 'closed'; - - // clear session id - this.id = null; - - // emit close event - this.emit('close', reason, desc); - } - }; - - /** - * Filters upgrades, returning only those matching client transports. - * - * @param {Array} server upgrades - * @api private - * - */ - - Socket.prototype.filterUpgrades = function (upgrades) { - var filteredUpgrades = []; - for (var i = 0, j = upgrades.length; i<j; i++) { - if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); - } - return filteredUpgrades; - }; - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./transport":13,"./transports":14,"component-emitter":8,"debug":20,"engine.io-parser":24,"indexof":44,"parsejson":52,"parseqs":53,"parseuri":23}],13:[function(require,module,exports){ - /** - * Module dependencies. - */ - - var parser = require('engine.io-parser'); - var Emitter = require('component-emitter'); - - /** - * Module exports. - */ - - module.exports = Transport; - - /** - * Transport abstract constructor. - * - * @param {Object} options. - * @api private - */ - - function Transport (opts) { - this.path = opts.path; - this.hostname = opts.hostname; - this.port = opts.port; - this.secure = opts.secure; - this.query = opts.query; - this.timestampParam = opts.timestampParam; - this.timestampRequests = opts.timestampRequests; - this.readyState = ''; - this.agent = opts.agent || false; - this.socket = opts.socket; - this.enablesXDR = opts.enablesXDR; - - // SSL options for Node.js client - this.pfx = opts.pfx; - this.key = opts.key; - this.passphrase = opts.passphrase; - this.cert = opts.cert; - this.ca = opts.ca; - this.ciphers = opts.ciphers; - this.rejectUnauthorized = opts.rejectUnauthorized; - } - - /** - * Mix in `Emitter`. - */ - - Emitter(Transport.prototype); - - /** - * A counter used to prevent collisions in the timestamps used - * for cache busting. - */ - - Transport.timestamps = 0; - - /** - * Emits an error. - * - * @param {String} str - * @return {Transport} for chaining - * @api public - */ - - Transport.prototype.onError = function (msg, desc) { - var err = new Error(msg); - err.type = 'TransportError'; - err.description = desc; - this.emit('error', err); - return this; - }; - - /** - * Opens the transport. - * - * @api public - */ - - Transport.prototype.open = function () { - if ('closed' == this.readyState || '' == this.readyState) { - this.readyState = 'opening'; - this.doOpen(); - } - - return this; - }; - - /** - * Closes the transport. - * - * @api private - */ - - Transport.prototype.close = function () { - if ('opening' == this.readyState || 'open' == this.readyState) { - this.doClose(); - this.onClose(); - } - - return this; - }; - - /** - * Sends multiple packets. - * - * @param {Array} packets - * @api private - */ - - Transport.prototype.send = function(packets){ - if ('open' == this.readyState) { - this.write(packets); - } else { - throw new Error('Transport not open'); - } - }; - - /** - * Called upon open - * - * @api private - */ - - Transport.prototype.onOpen = function () { - this.readyState = 'open'; - this.writable = true; - this.emit('open'); - }; - - /** - * Called with data. - * - * @param {String} data - * @api private - */ - - Transport.prototype.onData = function(data){ - var packet = parser.decodePacket(data, this.socket.binaryType); - this.onPacket(packet); - }; - - /** - * Called with a decoded packet. - */ - - Transport.prototype.onPacket = function (packet) { - this.emit('packet', packet); - }; - - /** - * Called upon close. - * - * @api private - */ - - Transport.prototype.onClose = function () { - this.readyState = 'closed'; - this.emit('close'); - }; - -},{"component-emitter":8,"engine.io-parser":24}],14:[function(require,module,exports){ - (function (global){ - /** - * Module dependencies - */ - - var XMLHttpRequest = require('xmlhttprequest'); - var XHR = require('./polling-xhr'); - var JSONP = require('./polling-jsonp'); - var websocket = require('./websocket'); - - /** - * Export transports. - */ - - exports.polling = polling; - exports.websocket = websocket; - - /** - * Polling transport polymorphic constructor. - * Decides on xhr vs jsonp based on feature detection. - * - * @api private - */ - - function polling(opts){ - var xhr; - var xd = false; - var xs = false; - var jsonp = false !== opts.jsonp; - - if (global.location) { - var isSSL = 'https:' == location.protocol; - var port = location.port; - - // some user agents have empty `location.port` - if (!port) { - port = isSSL ? 443 : 80; - } - - xd = opts.hostname != location.hostname || port != opts.port; - xs = opts.secure != isSSL; - } - - opts.xdomain = xd; - opts.xscheme = xs; - xhr = new XMLHttpRequest(opts); - - if ('open' in xhr && !opts.forceJSONP) { - return new XHR(opts); - } else { - if (!jsonp) throw new Error('JSONP disabled'); - return new JSONP(opts); - } - } - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./polling-jsonp":15,"./polling-xhr":16,"./websocket":18,"xmlhttprequest":19}],15:[function(require,module,exports){ - (function (global){ - - /** - * Module requirements. - */ - - var Polling = require('./polling'); - var inherit = require('component-inherit'); - - /** - * Module exports. - */ - - module.exports = JSONPPolling; - - /** - * Cached regular expressions. - */ - - var rNewline = /\n/g; - var rEscapedNewline = /\\n/g; - - /** - * Global JSONP callbacks. - */ - - var callbacks; - - /** - * Callbacks count. - */ - - var index = 0; - - /** - * Noop. - */ - - function empty () { } - - /** - * JSONP Polling constructor. - * - * @param {Object} opts. - * @api public - */ - - function JSONPPolling (opts) { - Polling.call(this, opts); - - this.query = this.query || {}; - - // define global callbacks array if not present - // we do this here (lazily) to avoid unneeded global pollution - if (!callbacks) { - // we need to consider multiple engines in the same page - if (!global.___eio) global.___eio = []; - callbacks = global.___eio; - } - - // callback identifier - this.index = callbacks.length; - - // add callback to jsonp global - var self = this; - callbacks.push(function (msg) { - self.onData(msg); - }); - - // append to query string - this.query.j = this.index; - - // prevent spurious errors from being emitted when the window is unloaded - if (global.document && global.addEventListener) { - global.addEventListener('beforeunload', function () { - if (self.script) self.script.onerror = empty; - }, false); - } - } - - /** - * Inherits from Polling. - */ - - inherit(JSONPPolling, Polling); - - /* - * JSONP only supports binary as base64 encoded strings - */ - - JSONPPolling.prototype.supportsBinary = false; - - /** - * Closes the socket. - * - * @api private - */ - - JSONPPolling.prototype.doClose = function () { - if (this.script) { - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - if (this.form) { - this.form.parentNode.removeChild(this.form); - this.form = null; - this.iframe = null; - } - - Polling.prototype.doClose.call(this); - }; - - /** - * Starts a poll cycle. - * - * @api private - */ - - JSONPPolling.prototype.doPoll = function () { - var self = this; - var script = document.createElement('script'); - - if (this.script) { - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - script.async = true; - script.src = this.uri(); - script.onerror = function(e){ - self.onError('jsonp poll error',e); - }; - - var insertAt = document.getElementsByTagName('script')[0]; - insertAt.parentNode.insertBefore(script, insertAt); - this.script = script; - - var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent); - - if (isUAgecko) { - setTimeout(function () { - var iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - document.body.removeChild(iframe); - }, 100); - } - }; - - /** - * Writes with a hidden iframe. - * - * @param {String} data to send - * @param {Function} called upon flush. - * @api private - */ - - JSONPPolling.prototype.doWrite = function (data, fn) { - var self = this; - - if (!this.form) { - var form = document.createElement('form'); - var area = document.createElement('textarea'); - var id = this.iframeId = 'eio_iframe_' + this.index; - var iframe; - - form.className = 'socketio'; - form.style.position = 'absolute'; - form.style.top = '-1000px'; - form.style.left = '-1000px'; - form.target = id; - form.method = 'POST'; - form.setAttribute('accept-charset', 'utf-8'); - area.name = 'd'; - form.appendChild(area); - document.body.appendChild(form); - - this.form = form; - this.area = area; - } - - this.form.action = this.uri(); - - function complete () { - initIframe(); - fn(); - } - - function initIframe () { - if (self.iframe) { - try { - self.form.removeChild(self.iframe); - } catch (e) { - self.onError('jsonp polling iframe removal error', e); - } - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">'; - iframe = document.createElement(html); - } catch (e) { - iframe = document.createElement('iframe'); - iframe.name = self.iframeId; - iframe.src = 'javascript:0'; - } - - iframe.id = self.iframeId; - - self.form.appendChild(iframe); - self.iframe = iframe; - } - - initIframe(); - - // escape \n to prevent it from being converted into \r\n by some UAs - // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side - data = data.replace(rEscapedNewline, '\\\n'); - this.area.value = data.replace(rNewline, '\\n'); - - try { - this.form.submit(); - } catch(e) {} - - if (this.iframe.attachEvent) { - this.iframe.onreadystatechange = function(){ - if (self.iframe.readyState == 'complete') { - complete(); - } - }; - } else { - this.iframe.onload = complete; - } - }; - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./polling":17,"component-inherit":9}],16:[function(require,module,exports){ - (function (global){ - /** - * Module requirements. - */ - - var XMLHttpRequest = require('xmlhttprequest'); - var Polling = require('./polling'); - var Emitter = require('component-emitter'); - var inherit = require('component-inherit'); - var debug = require('debug')('engine.io-client:polling-xhr'); - - /** - * Module exports. - */ - - module.exports = XHR; - module.exports.Request = Request; - - /** - * Empty function - */ - - function empty(){} - - /** - * XHR Polling constructor. - * - * @param {Object} opts - * @api public - */ - - function XHR(opts){ - Polling.call(this, opts); - - if (global.location) { - var isSSL = 'https:' == location.protocol; - var port = location.port; - - // some user agents have empty `location.port` - if (!port) { - port = isSSL ? 443 : 80; - } - - this.xd = opts.hostname != global.location.hostname || - port != opts.port; - this.xs = opts.secure != isSSL; - } - } - - /** - * Inherits from Polling. - */ - - inherit(XHR, Polling); - - /** - * XHR supports binary - */ - - XHR.prototype.supportsBinary = true; - - /** - * Creates a request. - * - * @param {String} method - * @api private - */ - - XHR.prototype.request = function(opts){ - opts = opts || {}; - opts.uri = this.uri(); - opts.xd = this.xd; - opts.xs = this.xs; - opts.agent = this.agent || false; - opts.supportsBinary = this.supportsBinary; - opts.enablesXDR = this.enablesXDR; - - // SSL options for Node.js client - opts.pfx = this.pfx; - opts.key = this.key; - opts.passphrase = this.passphrase; - opts.cert = this.cert; - opts.ca = this.ca; - opts.ciphers = this.ciphers; - opts.rejectUnauthorized = this.rejectUnauthorized; - - return new Request(opts); - }; - - /** - * Sends data. - * - * @param {String} data to send. - * @param {Function} called upon flush. - * @api private - */ - - XHR.prototype.doWrite = function(data, fn){ - var isBinary = typeof data !== 'string' && data !== undefined; - var req = this.request({ method: 'POST', data: data, isBinary: isBinary }); - var self = this; - req.on('success', fn); - req.on('error', function(err){ - self.onError('xhr post error', err); - }); - this.sendXhr = req; - }; - - /** - * Starts a poll cycle. - * - * @api private - */ - - XHR.prototype.doPoll = function(){ - debug('xhr poll'); - var req = this.request(); - var self = this; - req.on('data', function(data){ - self.onData(data); - }); - req.on('error', function(err){ - self.onError('xhr poll error', err); - }); - this.pollXhr = req; - }; - - /** - * Request constructor - * - * @param {Object} options - * @api public - */ - - function Request(opts){ - this.method = opts.method || 'GET'; - this.uri = opts.uri; - this.xd = !!opts.xd; - this.xs = !!opts.xs; - this.async = false !== opts.async; - this.data = undefined != opts.data ? opts.data : null; - this.agent = opts.agent; - this.isBinary = opts.isBinary; - this.supportsBinary = opts.supportsBinary; - this.enablesXDR = opts.enablesXDR; - - // SSL options for Node.js client - this.pfx = opts.pfx; - this.key = opts.key; - this.passphrase = opts.passphrase; - this.cert = opts.cert; - this.ca = opts.ca; - this.ciphers = opts.ciphers; - this.rejectUnauthorized = opts.rejectUnauthorized; - - this.create(); - } - - /** - * Mix in `Emitter`. - */ - - Emitter(Request.prototype); - - /** - * Creates the XHR object and sends the request. - * - * @api private - */ - - Request.prototype.create = function(){ - var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; - - // SSL options for Node.js client - opts.pfx = this.pfx; - opts.key = this.key; - opts.passphrase = this.passphrase; - opts.cert = this.cert; - opts.ca = this.ca; - opts.ciphers = this.ciphers; - opts.rejectUnauthorized = this.rejectUnauthorized; - - var xhr = this.xhr = new XMLHttpRequest(opts); - var self = this; - - try { - debug('xhr open %s: %s', this.method, this.uri); - xhr.open(this.method, this.uri, this.async); - if (this.supportsBinary) { - // This has to be done after open because Firefox is stupid - // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension - xhr.responseType = 'arraybuffer'; - } - - if ('POST' == this.method) { - try { - if (this.isBinary) { - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - } else { - xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); - } - } catch (e) {} - } - - // ie6 check - if ('withCredentials' in xhr) { - xhr.withCredentials = true; - } - - if (this.hasXDR()) { - xhr.onload = function(){ - self.onLoad(); - }; - xhr.onerror = function(){ - self.onError(xhr.responseText); - }; - } else { - xhr.onreadystatechange = function(){ - if (4 != xhr.readyState) return; - if (200 == xhr.status || 1223 == xhr.status) { - self.onLoad(); - } else { - // make sure the `error` event handler that's user-set - // does not throw in the same tick and gets caught here - setTimeout(function(){ - self.onError(xhr.status); - }, 0); - } - }; - } - - debug('xhr data %s', this.data); - xhr.send(this.data); - } catch (e) { - // Need to defer since .create() is called directly fhrom the constructor - // and thus the 'error' event can only be only bound *after* this exception - // occurs. Therefore, also, we cannot throw here at all. - setTimeout(function() { - self.onError(e); - }, 0); - return; - } - - if (global.document) { - this.index = Request.requestsCount++; - Request.requests[this.index] = this; - } - }; - - /** - * Called upon successful response. - * - * @api private - */ - - Request.prototype.onSuccess = function(){ - this.emit('success'); - this.cleanup(); - }; - - /** - * Called if we have data. - * - * @api private - */ - - Request.prototype.onData = function(data){ - this.emit('data', data); - this.onSuccess(); - }; - - /** - * Called upon error. - * - * @api private - */ - - Request.prototype.onError = function(err){ - this.emit('error', err); - this.cleanup(true); - }; - - /** - * Cleans up house. - * - * @api private - */ - - Request.prototype.cleanup = function(fromError){ - if ('undefined' == typeof this.xhr || null === this.xhr) { - return; - } - // xmlhttprequest - if (this.hasXDR()) { - this.xhr.onload = this.xhr.onerror = empty; - } else { - this.xhr.onreadystatechange = empty; - } - - if (fromError) { - try { - this.xhr.abort(); - } catch(e) {} - } - - if (global.document) { - delete Request.requests[this.index]; - } - - this.xhr = null; - }; - - /** - * Called upon load. - * - * @api private - */ - - Request.prototype.onLoad = function(){ - var data; - try { - var contentType; - try { - contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0]; - } catch (e) {} - if (contentType === 'application/octet-stream') { - data = this.xhr.response; - } else { - if (!this.supportsBinary) { - data = this.xhr.responseText; - } else { - data = 'ok'; - } - } - } catch (e) { - this.onError(e); - } - if (null != data) { - this.onData(data); - } - }; - - /** - * Check if it has XDomainRequest. - * - * @api private - */ - - Request.prototype.hasXDR = function(){ - return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; - }; - - /** - * Aborts the request. - * - * @api public - */ - - Request.prototype.abort = function(){ - this.cleanup(); - }; - - /** - * Aborts pending requests when unloading the window. This is needed to prevent - * memory leaks (e.g. when using IE) and to ensure that no spurious error is - * emitted. - */ - - if (global.document) { - Request.requestsCount = 0; - Request.requests = {}; - if (global.attachEvent) { - global.attachEvent('onunload', unloadHandler); - } else if (global.addEventListener) { - global.addEventListener('beforeunload', unloadHandler, false); - } - } - - function unloadHandler() { - for (var i in Request.requests) { - if (Request.requests.hasOwnProperty(i)) { - Request.requests[i].abort(); - } - } - } - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./polling":17,"component-emitter":8,"component-inherit":9,"debug":20,"xmlhttprequest":19}],17:[function(require,module,exports){ - /** - * Module dependencies. - */ - - var Transport = require('../transport'); - var parseqs = require('parseqs'); - var parser = require('engine.io-parser'); - var inherit = require('component-inherit'); - var debug = require('debug')('engine.io-client:polling'); - - /** - * Module exports. - */ - - module.exports = Polling; - - /** - * Is XHR2 supported? - */ - - var hasXHR2 = (function() { - var XMLHttpRequest = require('xmlhttprequest'); - var xhr = new XMLHttpRequest({ xdomain: false }); - return null != xhr.responseType; - })(); - - /** - * Polling interface. - * - * @param {Object} opts - * @api private - */ - - function Polling(opts){ - var forceBase64 = (opts && opts.forceBase64); - if (!hasXHR2 || forceBase64) { - this.supportsBinary = false; - } - Transport.call(this, opts); - } - - /** - * Inherits from Transport. - */ - - inherit(Polling, Transport); - - /** - * Transport name. - */ - - Polling.prototype.name = 'polling'; - - /** - * Opens the socket (triggers polling). We write a PING message to determine - * when the transport is open. - * - * @api private - */ - - Polling.prototype.doOpen = function(){ - this.poll(); - }; - - /** - * Pauses polling. - * - * @param {Function} callback upon buffers are flushed and transport is paused - * @api private - */ - - Polling.prototype.pause = function(onPause){ - var pending = 0; - var self = this; - - this.readyState = 'pausing'; - - function pause(){ - debug('paused'); - self.readyState = 'paused'; - onPause(); - } - - if (this.polling || !this.writable) { - var total = 0; - - if (this.polling) { - debug('we are currently polling - waiting to pause'); - total++; - this.once('pollComplete', function(){ - debug('pre-pause polling complete'); - --total || pause(); - }); - } - - if (!this.writable) { - debug('we are currently writing - waiting to pause'); - total++; - this.once('drain', function(){ - debug('pre-pause writing complete'); - --total || pause(); - }); - } - } else { - pause(); - } - }; - - /** - * Starts polling cycle. - * - * @api public - */ - - Polling.prototype.poll = function(){ - debug('polling'); - this.polling = true; - this.doPoll(); - this.emit('poll'); - }; - - /** - * Overloads onData to detect payloads. - * - * @api private - */ - - Polling.prototype.onData = function(data){ - var self = this; - debug('polling got data %s', data); - var callback = function(packet, index, total) { - // if its the first message we consider the transport open - if ('opening' == self.readyState) { - self.onOpen(); - } - - // if its a close packet, we close the ongoing requests - if ('close' == packet.type) { - self.onClose(); - return false; - } - - // otherwise bypass onData and handle the message - self.onPacket(packet); - }; - - // decode payload - parser.decodePayload(data, this.socket.binaryType, callback); - - // if an event did not trigger closing - if ('closed' != this.readyState) { - // if we got data we're not polling - this.polling = false; - this.emit('pollComplete'); - - if ('open' == this.readyState) { - this.poll(); - } else { - debug('ignoring poll - transport state "%s"', this.readyState); - } - } - }; - - /** - * For polling, send a close packet. - * - * @api private - */ - - Polling.prototype.doClose = function(){ - var self = this; - - function close(){ - debug('writing close packet'); - self.write([{ type: 'close' }]); - } - - if ('open' == this.readyState) { - debug('transport open - closing'); - close(); - } else { - // in case we're trying to close while - // handshaking is in progress (GH-164) - debug('transport not open - deferring close'); - this.once('open', close); - } - }; - - /** - * Writes a packets payload. - * - * @param {Array} data packets - * @param {Function} drain callback - * @api private - */ - - Polling.prototype.write = function(packets){ - var self = this; - this.writable = false; - var callbackfn = function() { - self.writable = true; - self.emit('drain'); - }; - - var self = this; - parser.encodePayload(packets, this.supportsBinary, function(data) { - self.doWrite(data, callbackfn); - }); - }; - - /** - * Generates uri for connection. - * - * @api private - */ - - Polling.prototype.uri = function(){ - var query = this.query || {}; - var schema = this.secure ? 'https' : 'http'; - var port = ''; - - // cache busting is forced - if (false !== this.timestampRequests) { - query[this.timestampParam] = +new Date + '-' + Transport.timestamps++; - } - - if (!this.supportsBinary && !query.sid) { - query.b64 = 1; - } - - query = parseqs.encode(query); - - // avoid port if default for schema - if (this.port && (('https' == schema && this.port != 443) || - ('http' == schema && this.port != 80))) { - port = ':' + this.port; - } - - // prepend ? to query - if (query.length) { - query = '?' + query; - } - - return schema + '://' + this.hostname + port + this.path + query; - }; - -},{"../transport":13,"component-inherit":9,"debug":20,"engine.io-parser":24,"parseqs":53,"xmlhttprequest":19}],18:[function(require,module,exports){ - /** - * Module dependencies. - */ - - var Transport = require('../transport'); - var parser = require('engine.io-parser'); - var parseqs = require('parseqs'); - var inherit = require('component-inherit'); - var debug = require('debug')('engine.io-client:websocket'); - - /** - * `ws` exposes a WebSocket-compatible interface in - * Node, or the `WebSocket` or `MozWebSocket` globals - * in the browser. - */ - - var WebSocket = require('ws'); - - /** - * Module exports. - */ - - module.exports = WS; - - /** - * WebSocket transport constructor. - * - * @api {Object} connection options - * @api public - */ - - function WS(opts){ - var forceBase64 = (opts && opts.forceBase64); - if (forceBase64) { - this.supportsBinary = false; - } - Transport.call(this, opts); - } - - /** - * Inherits from Transport. - */ - - inherit(WS, Transport); - - /** - * Transport name. - * - * @api public - */ - - WS.prototype.name = 'websocket'; - - /* - * WebSockets support binary - */ - - WS.prototype.supportsBinary = true; - - /** - * Opens socket. - * - * @api private - */ - - WS.prototype.doOpen = function(){ - if (!this.check()) { - // let probe timeout - return; - } - - var self = this; - var uri = this.uri(); - var protocols = void(0); - var opts = { agent: this.agent }; - - // SSL options for Node.js client - opts.pfx = this.pfx; - opts.key = this.key; - opts.passphrase = this.passphrase; - opts.cert = this.cert; - opts.ca = this.ca; - opts.ciphers = this.ciphers; - opts.rejectUnauthorized = this.rejectUnauthorized; - - this.ws = new WebSocket(uri, protocols, opts); - - if (this.ws.binaryType === undefined) { - this.supportsBinary = false; - } - - this.ws.binaryType = 'arraybuffer'; - this.addEventListeners(); - }; - - /** - * Adds event listeners to the socket - * - * @api private - */ - - WS.prototype.addEventListeners = function(){ - var self = this; - - this.ws.onopen = function(){ - self.onOpen(); - }; - this.ws.onclose = function(){ - self.onClose(); - }; - this.ws.onmessage = function(ev){ - self.onData(ev.data); - }; - this.ws.onerror = function(e){ - self.onError('websocket error', e); - }; - }; - - /** - * Override `onData` to use a timer on iOS. - * See: https://gist.github.com/mloughran/2052006 - * - * @api private - */ - - if ('undefined' != typeof navigator - && /iPad|iPhone|iPod/i.test(navigator.userAgent)) { - WS.prototype.onData = function(data){ - var self = this; - setTimeout(function(){ - Transport.prototype.onData.call(self, data); - }, 0); - }; - } - - /** - * Writes data to socket. - * - * @param {Array} array of packets. - * @api private - */ - - WS.prototype.write = function(packets){ - var self = this; - this.writable = false; - // encodePacket efficient as it uses WS framing - // no need for encodePayload - for (var i = 0, l = packets.length; i < l; i++) { - parser.encodePacket(packets[i], this.supportsBinary, function(data) { - //Sometimes the websocket has already been closed but the browser didn't - //have a chance of informing us about it yet, in that case send will - //throw an error - try { - self.ws.send(data); - } catch (e){ - debug('websocket closed before onclose event'); - } - }); - } - - function ondrain() { - self.writable = true; - self.emit('drain'); - } - // fake drain - // defer to next tick to allow Socket to clear writeBuffer - setTimeout(ondrain, 0); - }; - - /** - * Called upon close - * - * @api private - */ - - WS.prototype.onClose = function(){ - Transport.prototype.onClose.call(this); - }; - - /** - * Closes socket. - * - * @api private - */ - - WS.prototype.doClose = function(){ - if (typeof this.ws !== 'undefined') { - this.ws.close(); - } - }; - - /** - * Generates uri for connection. - * - * @api private - */ - - WS.prototype.uri = function(){ - var query = this.query || {}; - var schema = this.secure ? 'wss' : 'ws'; - var port = ''; - - // avoid port if default for schema - if (this.port && (('wss' == schema && this.port != 443) - || ('ws' == schema && this.port != 80))) { - port = ':' + this.port; - } - - // append timestamp to URI - if (this.timestampRequests) { - query[this.timestampParam] = +new Date; - } - - // communicate binary support capabilities - if (!this.supportsBinary) { - query.b64 = 1; - } - - query = parseqs.encode(query); - - // prepend ? to query - if (query.length) { - query = '?' + query; - } - - return schema + '://' + this.hostname + port + this.path + query; - }; - - /** - * Feature detection for WebSocket. - * - * @return {Boolean} whether this transport is available. - * @api public - */ - - WS.prototype.check = function(){ - return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name); - }; - -},{"../transport":13,"component-inherit":9,"debug":20,"engine.io-parser":24,"parseqs":53,"ws":101}],19:[function(require,module,exports){ -// browser shim for xmlhttprequest module - var hasCORS = require('has-cors'); - - module.exports = function(opts) { - var xdomain = opts.xdomain; - - // scheme must be same when usign XDomainRequest - // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx - var xscheme = opts.xscheme; - - // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. - // https://github.com/Automattic/engine.io-client/pull/217 - var enablesXDR = opts.enablesXDR; - - // XMLHttpRequest can be disabled on IE - try { - if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) { - return new XMLHttpRequest(); - } - } catch (e) { } - - // Use XDomainRequest for IE8 if enablesXDR is true - // because loading bar keeps flashing when using jsonp-polling - // https://github.com/yujiosaka/socke.io-ie8-loading-example - try { - if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) { - return new XDomainRequest(); - } - } catch (e) { } - - if (!xdomain) { - try { - return new ActiveXObject('Microsoft.XMLHTTP'); - } catch(e) { } - } - } - -},{"has-cors":43}],20:[function(require,module,exports){ - - /** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - - exports = module.exports = require('./debug'); - exports.log = log; - exports.formatArgs = formatArgs; - exports.save = save; - exports.load = load; - exports.useColors = useColors; - - /** - * Colors. - */ - - exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' - ]; - - /** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - - function useColors() { - // is webkit? http://stackoverflow.com/a/16459606/376773 - return ('WebkitAppearance' in document.documentElement.style) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (window.console && (console.firebug || (console.exception && console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); - } - - /** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - - exports.formatters.j = function(v) { - return JSON.stringify(v); - }; - - - /** - * Colorize log arguments if enabled. - * - * @api public - */ - - function formatArgs() { - var args = arguments; - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return args; - - var c = 'color: ' + this.color; - args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); - return args; - } - - /** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - - function log() { - // This hackery is required for IE8, - // where the `console.log` function doesn't have 'apply' - return 'object' == typeof console - && 'function' == typeof console.log - && Function.prototype.apply.call(console.log, console, arguments); - } - - /** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - - function save(namespaces) { - try { - if (null == namespaces) { - localStorage.removeItem('debug'); - } else { - localStorage.debug = namespaces; - } - } catch(e) {} - } - - /** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - - function load() { - var r; - try { - r = localStorage.debug; - } catch(e) {} - return r; - } - - /** - * Enable namespaces listed in `localStorage.debug` initially. - */ - - exports.enable(load()); - -},{"./debug":21}],21:[function(require,module,exports){ - - /** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - - exports = module.exports = debug; - exports.coerce = coerce; - exports.disable = disable; - exports.enable = enable; - exports.enabled = enabled; - exports.humanize = require('ms'); - - /** - * The currently active debug mode names, and names to skip. - */ - - exports.names = []; - exports.skips = []; - - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lowercased letter, i.e. "n". - */ - - exports.formatters = {}; - - /** - * Previously assigned color. - */ - - var prevColor = 0; - - /** - * Previous log timestamp. - */ - - var prevTime; - - /** - * Select a color. - * - * @return {Number} - * @api private - */ - - function selectColor() { - return exports.colors[prevColor++ % exports.colors.length]; - } - - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - - function debug(namespace) { - - // define the `disabled` version - function disabled() { - } - disabled.enabled = false; - - // define the `enabled` version - function enabled() { - - var self = enabled; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // add the `color` if not set - if (null == self.useColors) self.useColors = exports.useColors(); - if (null == self.color && self.useColors) self.color = selectColor(); - - var args = Array.prototype.slice.call(arguments); - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %o - args = ['%o'].concat(args); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - if ('function' === typeof exports.formatArgs) { - args = exports.formatArgs.apply(self, args); - } - var logFn = enabled.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - enabled.enabled = true; - - var fn = exports.enabled(namespace) ? enabled : disabled; - - fn.namespace = namespace; - - return fn; - } - - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - - function enable(namespaces) { - exports.save(namespaces); - - var split = (namespaces || '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } - } - - /** - * Disable debug output. - * - * @api public - */ - - function disable() { - exports.enable(''); - } - - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - - function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; - } - - /** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - - function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; - } - -},{"ms":22}],22:[function(require,module,exports){ - /** - * Helpers. - */ - - var s = 1000; - var m = s * 60; - var h = m * 60; - var d = h * 24; - var y = d * 365.25; - - /** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} options - * @return {String|Number} - * @api public - */ - - module.exports = function(val, options){ - options = options || {}; - if ('string' == typeof val) return parse(val); - return options.long - ? long(val) - : short(val); - }; - - /** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - - function parse(str) { - var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); - if (!match) return; - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 's': - return n * s; - case 'ms': - return n; - } - } - - /** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - - function short(ms) { - if (ms >= d) return Math.round(ms / d) + 'd'; - if (ms >= h) return Math.round(ms / h) + 'h'; - if (ms >= m) return Math.round(ms / m) + 'm'; - if (ms >= s) return Math.round(ms / s) + 's'; - return ms + 'ms'; - } - - /** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - - function long(ms) { - return plural(ms, d, 'day') - || plural(ms, h, 'hour') - || plural(ms, m, 'minute') - || plural(ms, s, 'second') - || ms + ' ms'; - } - - /** - * Pluralization helper. - */ - - function plural(ms, n, name) { - if (ms < n) return; - if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; - return Math.ceil(ms / n) + ' ' + name + 's'; - } - -},{}],23:[function(require,module,exports){ - /** - * Parses an URI - * - * @author Steven Levithan <stevenlevithan.com> (MIT license) - * @api private - */ - - var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - var parts = [ - 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' - ]; - - module.exports = function parseuri(str) { - var src = str, - b = str.indexOf('['), - e = str.indexOf(']'); - - if (b != -1 && e != -1) { - str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); - } - - var m = re.exec(str || ''), - uri = {}, - i = 14; - - while (i--) { - uri[parts[i]] = m[i] || ''; - } - - if (b != -1 && e != -1) { - uri.source = src; - uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); - uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); - uri.ipv6uri = true; - } - - return uri; - }; - -},{}],24:[function(require,module,exports){ - (function (global){ - /** - * Module dependencies. - */ - - var keys = require('./keys'); - var hasBinary = require('has-binary'); - var sliceBuffer = require('arraybuffer.slice'); - var base64encoder = require('base64-arraybuffer'); - var after = require('after'); - var utf8 = require('utf8'); - - /** - * Check if we are running an android browser. That requires us to use - * ArrayBuffer with polling transports... - * - * http://ghinda.net/jpeg-blob-ajax-android/ - */ - - var isAndroid = navigator.userAgent.match(/Android/i); - - /** - * Check if we are running in PhantomJS. - * Uploading a Blob with PhantomJS does not work correctly, as reported here: - * https://github.com/ariya/phantomjs/issues/11395 - * @type boolean - */ - var isPhantomJS = /PhantomJS/i.test(navigator.userAgent); - - /** - * When true, avoids using Blobs to encode payloads. - * @type boolean - */ - var dontSendBlobs = isAndroid || isPhantomJS; - - /** - * Current protocol version. - */ - - exports.protocol = 3; - - /** - * Packet types. - */ - - var packets = exports.packets = { - open: 0 // non-ws - , close: 1 // non-ws - , ping: 2 - , pong: 3 - , message: 4 - , upgrade: 5 - , noop: 6 - }; - - var packetslist = keys(packets); - - /** - * Premade error packet. - */ - - var err = { type: 'error', data: 'parser error' }; - - /** - * Create a blob api even for blob builder when vendor prefixes exist - */ - - var Blob = require('blob'); - - /** - * Encodes a packet. - * - * <packet type id> [ <data> ] - * - * Example: - * - * 5hello world - * 3 - * 4 - * - * Binary is encoded in an identical principle - * - * @api private - */ - - exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) { - if ('function' == typeof supportsBinary) { - callback = supportsBinary; - supportsBinary = false; - } - - if ('function' == typeof utf8encode) { - callback = utf8encode; - utf8encode = null; - } - - var data = (packet.data === undefined) - ? undefined - : packet.data.buffer || packet.data; - - if (global.ArrayBuffer && data instanceof ArrayBuffer) { - return encodeArrayBuffer(packet, supportsBinary, callback); - } else if (Blob && data instanceof global.Blob) { - return encodeBlob(packet, supportsBinary, callback); - } - - // might be an object with { base64: true, data: dataAsBase64String } - if (data && data.base64) { - return encodeBase64Object(packet, callback); - } - - // Sending data as a utf-8 string - var encoded = packets[packet.type]; - - // data fragment is optional - if (undefined !== packet.data) { - encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data); - } - - return callback('' + encoded); - - }; - - function encodeBase64Object(packet, callback) { - // packet data is an object { base64: true, data: dataAsBase64String } - var message = 'b' + exports.packets[packet.type] + packet.data.data; - return callback(message); - } - - /** - * Encode packet helpers for binary types - */ - - function encodeArrayBuffer(packet, supportsBinary, callback) { - if (!supportsBinary) { - return exports.encodeBase64Packet(packet, callback); - } - - var data = packet.data; - var contentArray = new Uint8Array(data); - var resultBuffer = new Uint8Array(1 + data.byteLength); - - resultBuffer[0] = packets[packet.type]; - for (var i = 0; i < contentArray.length; i++) { - resultBuffer[i+1] = contentArray[i]; - } - - return callback(resultBuffer.buffer); - } - - function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) { - if (!supportsBinary) { - return exports.encodeBase64Packet(packet, callback); - } - - var fr = new FileReader(); - fr.onload = function() { - packet.data = fr.result; - exports.encodePacket(packet, supportsBinary, true, callback); - }; - return fr.readAsArrayBuffer(packet.data); - } - - function encodeBlob(packet, supportsBinary, callback) { - if (!supportsBinary) { - return exports.encodeBase64Packet(packet, callback); - } - - if (dontSendBlobs) { - return encodeBlobAsArrayBuffer(packet, supportsBinary, callback); - } - - var length = new Uint8Array(1); - length[0] = packets[packet.type]; - var blob = new Blob([length.buffer, packet.data]); - - return callback(blob); - } - - /** - * Encodes a packet with binary data in a base64 string - * - * @param {Object} packet, has `type` and `data` - * @return {String} base64 encoded message - */ - - exports.encodeBase64Packet = function(packet, callback) { - var message = 'b' + exports.packets[packet.type]; - if (Blob && packet.data instanceof Blob) { - var fr = new FileReader(); - fr.onload = function() { - var b64 = fr.result.split(',')[1]; - callback(message + b64); - }; - return fr.readAsDataURL(packet.data); - } - - var b64data; - try { - b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data)); - } catch (e) { - // iPhone Safari doesn't let you apply with typed arrays - var typed = new Uint8Array(packet.data); - var basic = new Array(typed.length); - for (var i = 0; i < typed.length; i++) { - basic[i] = typed[i]; - } - b64data = String.fromCharCode.apply(null, basic); - } - message += global.btoa(b64data); - return callback(message); - }; - - /** - * Decodes a packet. Changes format to Blob if requested. - * - * @return {Object} with `type` and `data` (if any) - * @api private - */ - - exports.decodePacket = function (data, binaryType, utf8decode) { - // String data - if (typeof data == 'string' || data === undefined) { - if (data.charAt(0) == 'b') { - return exports.decodeBase64Packet(data.substr(1), binaryType); - } - - if (utf8decode) { - try { - data = utf8.decode(data); - } catch (e) { - return err; - } - } - var type = data.charAt(0); - - if (Number(type) != type || !packetslist[type]) { - return err; - } - - if (data.length > 1) { - return { type: packetslist[type], data: data.substring(1) }; - } else { - return { type: packetslist[type] }; - } - } - - var asArray = new Uint8Array(data); - var type = asArray[0]; - var rest = sliceBuffer(data, 1); - if (Blob && binaryType === 'blob') { - rest = new Blob([rest]); - } - return { type: packetslist[type], data: rest }; - }; - - /** - * Decodes a packet encoded in a base64 string - * - * @param {String} base64 encoded message - * @return {Object} with `type` and `data` (if any) - */ - - exports.decodeBase64Packet = function(msg, binaryType) { - var type = packetslist[msg.charAt(0)]; - if (!global.ArrayBuffer) { - return { type: type, data: { base64: true, data: msg.substr(1) } }; - } - - var data = base64encoder.decode(msg.substr(1)); - - if (binaryType === 'blob' && Blob) { - data = new Blob([data]); - } - - return { type: type, data: data }; - }; - - /** - * Encodes multiple messages (payload). - * - * <length>:data - * - * Example: - * - * 11:hello world2:hi - * - * If any contents are binary, they will be encoded as base64 strings. Base64 - * encoded strings are marked with a b before the length specifier - * - * @param {Array} packets - * @api private - */ - - exports.encodePayload = function (packets, supportsBinary, callback) { - if (typeof supportsBinary == 'function') { - callback = supportsBinary; - supportsBinary = null; - } - - var isBinary = hasBinary(packets); - - if (supportsBinary && isBinary) { - if (Blob && !dontSendBlobs) { - return exports.encodePayloadAsBlob(packets, callback); - } - - return exports.encodePayloadAsArrayBuffer(packets, callback); - } - - if (!packets.length) { - return callback('0:'); - } - - function setLengthHeader(message) { - return message.length + ':' + message; - } - - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) { - doneCallback(null, setLengthHeader(message)); - }); - } - - map(packets, encodeOne, function(err, results) { - return callback(results.join('')); - }); - }; - - /** - * Async array map using after - */ - - function map(ary, each, done) { - var result = new Array(ary.length); - var next = after(ary.length, done); - - var eachWithIndex = function(i, el, cb) { - each(el, function(error, msg) { - result[i] = msg; - cb(error, result); - }); - }; - - for (var i = 0; i < ary.length; i++) { - eachWithIndex(i, ary[i], next); - } - } - - /* - * Decodes data when a payload is maybe expected. Possible binary contents are - * decoded from their base64 representation - * - * @param {String} data, callback method - * @api public - */ - - exports.decodePayload = function (data, binaryType, callback) { - if (typeof data != 'string') { - return exports.decodePayloadAsBinary(data, binaryType, callback); - } - - if (typeof binaryType === 'function') { - callback = binaryType; - binaryType = null; - } - - var packet; - if (data == '') { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - var length = '' - , n, msg; - - for (var i = 0, l = data.length; i < l; i++) { - var chr = data.charAt(i); - - if (':' != chr) { - length += chr; - } else { - if ('' == length || (length != (n = Number(length)))) { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - msg = data.substr(i + 1, n); - - if (length != msg.length) { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - if (msg.length) { - packet = exports.decodePacket(msg, binaryType, true); - - if (err.type == packet.type && err.data == packet.data) { - // parser error in individual packet - ignoring payload - return callback(err, 0, 1); - } - - var ret = callback(packet, i + n, l); - if (false === ret) return; - } - - // advance cursor - i += n; - length = ''; - } - } - - if (length != '') { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - }; - - /** - * Encodes multiple messages (payload) as binary. - * - * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number - * 255><data> - * - * Example: - * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers - * - * @param {Array} packets - * @return {ArrayBuffer} encoded payload - * @api private - */ - - exports.encodePayloadAsArrayBuffer = function(packets, callback) { - if (!packets.length) { - return callback(new ArrayBuffer(0)); - } - - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, true, true, function(data) { - return doneCallback(null, data); - }); - } - - map(packets, encodeOne, function(err, encodedPackets) { - var totalLength = encodedPackets.reduce(function(acc, p) { - var len; - if (typeof p === 'string'){ - len = p.length; - } else { - len = p.byteLength; - } - return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 - }, 0); - - var resultArray = new Uint8Array(totalLength); - - var bufferIndex = 0; - encodedPackets.forEach(function(p) { - var isString = typeof p === 'string'; - var ab = p; - if (isString) { - var view = new Uint8Array(p.length); - for (var i = 0; i < p.length; i++) { - view[i] = p.charCodeAt(i); - } - ab = view.buffer; - } - - if (isString) { // not true binary - resultArray[bufferIndex++] = 0; - } else { // true binary - resultArray[bufferIndex++] = 1; - } - - var lenStr = ab.byteLength.toString(); - for (var i = 0; i < lenStr.length; i++) { - resultArray[bufferIndex++] = parseInt(lenStr[i]); - } - resultArray[bufferIndex++] = 255; - - var view = new Uint8Array(ab); - for (var i = 0; i < view.length; i++) { - resultArray[bufferIndex++] = view[i]; - } - }); - - return callback(resultArray.buffer); - }); - }; - - /** - * Encode as Blob - */ - - exports.encodePayloadAsBlob = function(packets, callback) { - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, true, true, function(encoded) { - var binaryIdentifier = new Uint8Array(1); - binaryIdentifier[0] = 1; - if (typeof encoded === 'string') { - var view = new Uint8Array(encoded.length); - for (var i = 0; i < encoded.length; i++) { - view[i] = encoded.charCodeAt(i); - } - encoded = view.buffer; - binaryIdentifier[0] = 0; - } - - var len = (encoded instanceof ArrayBuffer) - ? encoded.byteLength - : encoded.size; - - var lenStr = len.toString(); - var lengthAry = new Uint8Array(lenStr.length + 1); - for (var i = 0; i < lenStr.length; i++) { - lengthAry[i] = parseInt(lenStr[i]); - } - lengthAry[lenStr.length] = 255; - - if (Blob) { - var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); - doneCallback(null, blob); - } - }); - } - - map(packets, encodeOne, function(err, results) { - return callback(new Blob(results)); - }); - }; - - /* - * Decodes data when a payload is maybe expected. Strings are decoded by - * interpreting each byte as a key code for entries marked to start with 0. See - * description of encodePayloadAsBinary - * - * @param {ArrayBuffer} data, callback method - * @api public - */ - - exports.decodePayloadAsBinary = function (data, binaryType, callback) { - if (typeof binaryType === 'function') { - callback = binaryType; - binaryType = null; - } - - var bufferTail = data; - var buffers = []; - - var numberTooLong = false; - while (bufferTail.byteLength > 0) { - var tailArray = new Uint8Array(bufferTail); - var isString = tailArray[0] === 0; - var msgLength = ''; - - for (var i = 1; ; i++) { - if (tailArray[i] == 255) break; - - if (msgLength.length > 310) { - numberTooLong = true; - break; - } - - msgLength += tailArray[i]; - } - - if(numberTooLong) return callback(err, 0, 1); - - bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); - msgLength = parseInt(msgLength); - - var msg = sliceBuffer(bufferTail, 0, msgLength); - if (isString) { - try { - msg = String.fromCharCode.apply(null, new Uint8Array(msg)); - } catch (e) { - // iPhone Safari doesn't let you apply to typed arrays - var typed = new Uint8Array(msg); - msg = ''; - for (var i = 0; i < typed.length; i++) { - msg += String.fromCharCode(typed[i]); - } - } - } - - buffers.push(msg); - bufferTail = sliceBuffer(bufferTail, msgLength); - } - - var total = buffers.length; - buffers.forEach(function(buffer, i) { - callback(exports.decodePacket(buffer, binaryType, true), i, total); - }); - }; - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./keys":25,"after":1,"arraybuffer.slice":2,"base64-arraybuffer":5,"blob":6,"has-binary":41,"utf8":86}],25:[function(require,module,exports){ - - /** - * Gets the keys for an object. - * - * @return {Array} keys - * @api private - */ - - module.exports = Object.keys || function keys (obj){ - var arr = []; - var has = Object.prototype.hasOwnProperty; - - for (var i in obj) { - if (has.call(obj, i)) { - arr.push(i); - } - } - return arr; - }; - -},{}],26:[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 usePoll = typeof channel.bufferedAmountLowThreshold !== 'number'; - var offset = 0; - var sliceFile = function() { - 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) { - if (usePoll) { - window.setTimeout(sliceFile, self.config.pacing); - } else if (channel.bufferedAmount <= channel.bufferedAmountLowThreshold) { - window.setTimeout(sliceFile, 0); - } else { - // wait for bufferedAmountLow to fire - } - } else { - self.emit('progress', file.size, file.size, null); - self.emit('sentFile'); - } - offset = offset + self.config.chunksize; - }; - })(file); - var slice = file.slice(offset, offset + self.config.chunksize); - reader.readAsArrayBuffer(slice); - }; - if (!usePoll) { - channel.bufferedAmountLowThreshold = 8 * this.config.chunksize; - channel.addEventListener('bufferedamountlow', sliceFile); - } - window.setTimeout(sliceFile, 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 = typeof window !== 'undefined' && window && window.File && window.FileReader && window.Blob; - module.exports.Sender = Sender; - module.exports.Receiver = Receiver; - -},{"util":89,"wildemitter":100}],27:[function(require,module,exports){ -// getScreenMedia helper by @HenrikJoreteg - var getUserMedia = require('getusermedia'); - -// cache for constraints and callback - var cache = {}; - - module.exports = function (mode, constraints, cb) { - var hasConstraints = arguments.length === 3; - 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\/(\d+)\./)[1], 10); - var maxver = 33; - // Chrome 71 dropped support for "window.chrome.webstore;". - var isCef = (chromever < 71) && !window.chrome.webstore; - // "known" crash in chrome 34 and 35 on linux - if (window.navigator.userAgent.match('Linux')) maxver = 35; - - // check that the extension is installed by looking for a - // sessionStorage variable that contains the extension id - // this has to be set after installation unless the contest - // script does that - if (sessionStorage.getScreenMediaJSExtensionId) { - chrome.runtime.sendMessage(sessionStorage.getScreenMediaJSExtensionId, - {type:'getScreen', id: 1}, null, - function (data) { - if (!data || data.sourceId === '') { // user canceled - var error = new Error('NavigatorUserMediaError'); - error.name = 'PERMISSION_DENIED'; - callback(error); - } else { - constraints = (hasConstraints && constraints) || {audio: false, video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - } - }}; - constraints.video.mandatory.chromeMediaSourceId = data.sourceId; - getUserMedia(constraints, callback); - } - } - ); - } else if (window.cefGetScreenMedia) { - //window.cefGetScreenMedia is experimental - may be removed without notice - window.cefGetScreenMedia(function(sourceId) { - if (!sourceId) { - var error = new Error('cefGetScreenMediaError'); - error.name = 'CEF_GETSCREENMEDIA_CANCELED'; - callback(error); - } else { - constraints = (hasConstraints && 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 = sourceId; - getUserMedia(constraints, callback); - } - }); - } else 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 ? constraints : 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 >= 52) { - mode = mode || 'window'; - constraints = (hasConstraints && constraints) || { - video: { - mozMediaSource: mode, - mediaSource: mode - } - }; - getUserMedia(constraints, function (err, stream) { - callback(err, stream); - if (err) { - return; - } - // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 - 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 = 'FF52_REQUIRED'; - return callback(error); - } - } - }; - - typeof window !== 'undefined' && window.addEventListener('message', function (event) { - if (event.origin != window.location.origin && !event.isTrusted) { - 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":28}],28:[function(require,module,exports){ -// getUserMedia helper by @HenrikJoreteg used for navigator.getUserMedia shim - var adapter = require('webrtc-adapter'); - - module.exports = function (constraints, cb) { - var error; - var haveOpts = arguments.length === 2; - var defaultOpts = {video: true, audio: true}; - - var denied = 'PermissionDeniedError'; - var altDenied = 'PERMISSION_DENIED'; - var notSatisfied = 'ConstraintNotSatisfiedError'; - - // make constraints optional - if (!haveOpts) { - cb = constraints; - constraints = defaultOpts; - } - - // treat lack of browser support like an error - if (typeof navigator === 'undefined' || !navigator.getUserMedia) { - // throw proper error per spec - error = new Error('MediaStreamError'); - error.name = 'NotSupportedError'; - - // keep all callbacks async - return 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 setTimeout(function () { - cb(error); - }, 0); - } - - navigator.mediaDevices.getUserMedia(constraints) - .then(function (stream) { - cb(null, stream); - }).catch(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 || err === altDenied) { - 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); - }); - }; - -},{"webrtc-adapter":90}],29:[function(require,module,exports){ -// getUserMedia helper by @HenrikJoreteg used for navigator.getUserMedia shim - var adapter = require('webrtc-adapter'); - - module.exports = function (constraints, cb) { - var error; - var haveOpts = arguments.length === 2; - var defaultOpts = {video: true, audio: true}; - - var denied = 'PermissionDeniedError'; - var altDenied = 'PERMISSION_DENIED'; - var notSatisfied = 'ConstraintNotSatisfiedError'; - - // make constraints optional - if (!haveOpts) { - cb = constraints; - constraints = defaultOpts; - } - - // treat lack of browser support like an error - if (typeof navigator === 'undefined' || !navigator.getUserMedia) { - // throw proper error per spec - error = new Error('MediaStreamError'); - error.name = 'NotSupportedError'; - - // keep all callbacks async - return 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 setTimeout(function () { - cb(error); - }, 0); - } - - // testing support -- note: using the about:config pref is better - // for Firefox 39+, this might get removed in the future - if (localStorage && localStorage.useFirefoxFakeDevice === 'true') { - constraints.fake = true; - } - - navigator.mediaDevices.getUserMedia(constraints) - .then(function (stream) { - cb(null, stream); - }).catch(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 || err === altDenied) { - 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); - }); - }; - -},{"webrtc-adapter":30}],30:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - - 'use strict'; - -// Shimming starts here. - (function() { - // Utils. - var logging = require('./utils').log; - var browserDetails = require('./utils').browserDetails; - // Export to the adapter global object visible in the browser. - module.exports.browserDetails = browserDetails; - module.exports.extractVersion = require('./utils').extractVersion; - module.exports.disableLog = require('./utils').disableLog; - - // Uncomment the line below if you want logging to occur, including logging - // for the switch statement below. Can also be turned on in the browser via - // adapter.disableLog(false), but then logging from the switch statement below - // will not appear. - // require('./utils').disableLog(false); - - // Browser shims. - var chromeShim = require('./chrome/chrome_shim') || null; - var edgeShim = require('./edge/edge_shim') || null; - var firefoxShim = require('./firefox/firefox_shim') || null; - var safariShim = require('./safari/safari_shim') || null; - - // Shim browser if found. - switch (browserDetails.browser) { - case 'opera': // fallthrough as it uses chrome shims - case 'chrome': - if (!chromeShim || !chromeShim.shimPeerConnection) { - logging('Chrome shim is not included in this adapter release.'); - return; - } - logging('adapter.js shimming chrome.'); - // Export to the adapter global object visible in the browser. - module.exports.browserShim = chromeShim; - - chromeShim.shimGetUserMedia(); - chromeShim.shimMediaStream(); - chromeShim.shimSourceObject(); - chromeShim.shimPeerConnection(); - chromeShim.shimOnTrack(); - break; - case 'firefox': - if (!firefoxShim || !firefoxShim.shimPeerConnection) { - logging('Firefox shim is not included in this adapter release.'); - return; - } - logging('adapter.js shimming firefox.'); - // Export to the adapter global object visible in the browser. - module.exports.browserShim = firefoxShim; - - firefoxShim.shimGetUserMedia(); - firefoxShim.shimSourceObject(); - firefoxShim.shimPeerConnection(); - firefoxShim.shimOnTrack(); - break; - case 'edge': - if (!edgeShim || !edgeShim.shimPeerConnection) { - logging('MS edge shim is not included in this adapter release.'); - return; - } - logging('adapter.js shimming edge.'); - // Export to the adapter global object visible in the browser. - module.exports.browserShim = edgeShim; - - edgeShim.shimGetUserMedia(); - edgeShim.shimPeerConnection(); - break; - case 'safari': - if (!safariShim) { - logging('Safari shim is not included in this adapter release.'); - return; - } - logging('adapter.js shimming safari.'); - // Export to the adapter global object visible in the browser. - module.exports.browserShim = safariShim; - - safariShim.shimGetUserMedia(); - break; - default: - logging('Unsupported browser!'); - } - })(); - -},{"./chrome/chrome_shim":31,"./edge/edge_shim":33,"./firefox/firefox_shim":35,"./safari/safari_shim":37,"./utils":38}],31:[function(require,module,exports){ - - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - var logging = require('../utils.js').log; - var browserDetails = require('../utils.js').browserDetails; - - var chromeShim = { - shimMediaStream: function() { - window.MediaStream = window.MediaStream || window.webkitMediaStream; - }, - - shimOnTrack: function() { - if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in - window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { - get: function() { - return this._ontrack; - }, - set: function(f) { - var self = this; - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - this.removeEventListener('addstream', this._ontrackpoly); - } - this.addEventListener('track', this._ontrack = f); - this.addEventListener('addstream', this._ontrackpoly = function(e) { - // onaddstream does not fire when a track is added to an existing - // stream. But stream.onaddtrack is implemented so we use that. - e.stream.addEventListener('addtrack', function(te) { - var event = new Event('track'); - event.track = te.track; - event.receiver = {track: te.track}; - event.streams = [e.stream]; - self.dispatchEvent(event); - }); - e.stream.getTracks().forEach(function(track) { - var event = new Event('track'); - event.track = track; - event.receiver = {track: track}; - event.streams = [e.stream]; - this.dispatchEvent(event); - }.bind(this)); - }.bind(this)); - } - }); - } - }, - - shimSourceObject: function() { - if (typeof window === 'object') { - if (window.HTMLMediaElement && - !('srcObject' in window.HTMLMediaElement.prototype)) { - // Shim the srcObject property, once, when HTMLMediaElement is found. - Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { - get: function() { - return this._srcObject; - }, - set: function(stream) { - var self = this; - // Use _srcObject as a private property for this shim - this._srcObject = stream; - if (this.src) { - URL.revokeObjectURL(this.src); - } - - if (!stream) { - this.src = ''; - return; - } - this.src = URL.createObjectURL(stream); - // We need to recreate the blob url when a track is added or - // removed. Doing it manually since we want to avoid a recursion. - stream.addEventListener('addtrack', function() { - if (self.src) { - URL.revokeObjectURL(self.src); - } - self.src = URL.createObjectURL(stream); - }); - stream.addEventListener('removetrack', function() { - if (self.src) { - URL.revokeObjectURL(self.src); - } - self.src = URL.createObjectURL(stream); - }); - } - }); - } - } - }, - - shimPeerConnection: function() { - // The RTCPeerConnection object. - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - // Translate iceTransportPolicy to iceTransports, - // see https://code.google.com/p/webrtc/issues/detail?id=4869 - logging('PeerConnection'); - if (pcConfig && pcConfig.iceTransportPolicy) { - pcConfig.iceTransports = pcConfig.iceTransportPolicy; - } - - var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); - var origGetStats = pc.getStats.bind(pc); - pc.getStats = function(selector, successCallback, errorCallback) { - var self = this; - var args = arguments; - - // If selector is a function then we are in the old style stats so just - // pass back the original getStats format to avoid breaking old users. - if (arguments.length > 0 && typeof selector === 'function') { - return origGetStats(selector, successCallback); - } - - var fixChromeStats_ = function(response) { - var standardReport = {}; - var reports = response.result(); - reports.forEach(function(report) { - var standardStats = { - id: report.id, - timestamp: report.timestamp, - type: report.type - }; - report.names().forEach(function(name) { - standardStats[name] = report.stat(name); - }); - standardReport[standardStats.id] = standardStats; - }); - - return standardReport; - }; - - // shim getStats with maplike support - var makeMapStats = function(stats, legacyStats) { - var map = new Map(Object.keys(stats).map(function(key) { - return[key, stats[key]]; - })); - legacyStats = legacyStats || stats; - Object.keys(legacyStats).forEach(function(key) { - map[key] = legacyStats[key]; - }); - return map; - }; - - if (arguments.length >= 2) { - var successCallbackWrapper_ = function(response) { - args[1](makeMapStats(fixChromeStats_(response))); - }; - - return origGetStats.apply(this, [successCallbackWrapper_, - arguments[0]]); - } - - // promise-support - return new Promise(function(resolve, reject) { - if (args.length === 1 && typeof selector === 'object') { - origGetStats.apply(self, [ - function(response) { - resolve(makeMapStats(fixChromeStats_(response))); - }, reject]); - } else { - // Preserve legacy chrome stats only on legacy access of stats obj - origGetStats.apply(self, [ - function(response) { - resolve(makeMapStats(fixChromeStats_(response), - response.result())); - }, reject]); - } - }).then(successCallback, errorCallback); - }; - - return pc; - }; - window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (webkitRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - return webkitRTCPeerConnection.generateCertificate; - } - }); - } - - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var self = this; - if (arguments.length < 1 || (arguments.length === 1 && - typeof arguments[0] === 'object')) { - var opts = arguments.length === 1 ? arguments[0] : undefined; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [resolve, reject, opts]); - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - - // add promise support -- natively available in Chrome 51 - if (browserDetails.version < 51) { - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - var promise = new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], resolve, reject]); - }); - if (args.length < 2) { - return promise; - } - return promise.then(function() { - args[1].apply(null, []); - }, - function(err) { - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }); - }; - }); - } - - // support for addIceCandidate(null) - var nativeAddIceCandidate = - RTCPeerConnection.prototype.addIceCandidate; - RTCPeerConnection.prototype.addIceCandidate = function() { - return arguments[0] === null ? Promise.resolve() - : nativeAddIceCandidate.apply(this, arguments); - }; - - // shim implicit creation of RTCSessionDescription/RTCIceCandidate - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate') ? - RTCIceCandidate : RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }; - }); - }, - - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - if (browserDetails.version >= 43) { - element.srcObject = stream; - } else if (typeof element.src !== 'undefined') { - element.src = URL.createObjectURL(stream); - } else { - logging('Error attaching stream to element.'); - } - }, - - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - if (browserDetails.version >= 43) { - to.srcObject = from.srcObject; - } else { - to.src = from.src; - } - } - }; - - -// Expose public methods. - module.exports = { - shimMediaStream: chromeShim.shimMediaStream, - shimOnTrack: chromeShim.shimOnTrack, - shimSourceObject: chromeShim.shimSourceObject, - shimPeerConnection: chromeShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia'), - attachMediaStream: chromeShim.attachMediaStream, - reattachMediaStream: chromeShim.reattachMediaStream - }; - -},{"../utils.js":38,"./getusermedia":32}],32:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - var logging = require('../utils.js').log; - -// Expose public methods. - module.exports = function() { - var constraintsToChrome_ = function(c) { - if (typeof c !== 'object' || c.mandatory || c.optional) { - return c; - } - var cc = {}; - Object.keys(c).forEach(function(key) { - if (key === 'require' || key === 'advanced' || key === 'mediaSource') { - return; - } - var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; - if (r.exact !== undefined && typeof r.exact === 'number') { - r.min = r.max = r.exact; - } - var oldname_ = function(prefix, name) { - if (prefix) { - return prefix + name.charAt(0).toUpperCase() + name.slice(1); - } - return (name === 'deviceId') ? 'sourceId' : name; - }; - if (r.ideal !== undefined) { - cc.optional = cc.optional || []; - var oc = {}; - if (typeof r.ideal === 'number') { - oc[oldname_('min', key)] = r.ideal; - cc.optional.push(oc); - oc = {}; - oc[oldname_('max', key)] = r.ideal; - cc.optional.push(oc); - } else { - oc[oldname_('', key)] = r.ideal; - cc.optional.push(oc); - } - } - if (r.exact !== undefined && typeof r.exact !== 'number') { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_('', key)] = r.exact; - } else { - ['min', 'max'].forEach(function(mix) { - if (r[mix] !== undefined) { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_(mix, key)] = r[mix]; - } - }); - } - }); - if (c.advanced) { - cc.optional = (cc.optional || []).concat(c.advanced); - } - return cc; - }; - - var shimConstraints_ = function(constraints, func) { - constraints = JSON.parse(JSON.stringify(constraints)); - if (constraints && constraints.audio) { - constraints.audio = constraintsToChrome_(constraints.audio); - } - if (constraints && typeof constraints.video === 'object') { - // Shim facingMode for mobile, where it defaults to "user". - var face = constraints.video.facingMode; - face = face && ((typeof face === 'object') ? face : {ideal: face}); - - if ((face && (face.exact === 'user' || face.exact === 'environment' || - face.ideal === 'user' || face.ideal === 'environment')) && - !(navigator.mediaDevices.getSupportedConstraints && - navigator.mediaDevices.getSupportedConstraints().facingMode)) { - delete constraints.video.facingMode; - if (face.exact === 'environment' || face.ideal === 'environment') { - // Look for "back" in label, or use last cam (typically back cam). - return navigator.mediaDevices.enumerateDevices() - .then(function(devices) { - devices = devices.filter(function(d) { - return d.kind === 'videoinput'; - }); - var back = devices.find(function(d) { - return d.label.toLowerCase().indexOf('back') !== -1; - }) || (devices.length && devices[devices.length - 1]); - if (back) { - constraints.video.deviceId = face.exact ? {exact: back.deviceId} : - {ideal: back.deviceId}; - } - constraints.video = constraintsToChrome_(constraints.video); - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }); - } - } - constraints.video = constraintsToChrome_(constraints.video); - } - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }; - - var shimError_ = function(e) { - return { - name: { - PermissionDeniedError: 'NotAllowedError', - ConstraintNotSatisfiedError: 'OverconstrainedError' - }[e.name] || e.name, - message: e.message, - constraint: e.constraintName, - toString: function() { - return this.name + (this.message && ': ') + this.message; - } - }; - }; - - var getUserMedia_ = function(constraints, onSuccess, onError) { - shimConstraints_(constraints, function(c) { - navigator.webkitGetUserMedia(c, onSuccess, function(e) { - onError(shimError_(e)); - }); - }); - }; - - navigator.getUserMedia = getUserMedia_; - - // Returns the result of getUserMedia as a Promise. - var getUserMediaPromise_ = function(constraints) { - return new Promise(function(resolve, reject) { - navigator.getUserMedia(constraints, resolve, reject); - }); - }; - - if (!navigator.mediaDevices) { - navigator.mediaDevices = { - getUserMedia: getUserMediaPromise_, - enumerateDevices: function() { - return new Promise(function(resolve) { - var kinds = {audio: 'audioinput', video: 'videoinput'}; - return MediaStreamTrack.getSources(function(devices) { - resolve(devices.map(function(device) { - return {label: device.label, - kind: kinds[device.kind], - deviceId: device.id, - groupId: ''}; - })); - }); - }); - } - }; - } - - // A shim for getUserMedia method on the mediaDevices object. - // TODO(KaptenJansson) remove once implemented in Chrome stable. - if (!navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia = function(constraints) { - return getUserMediaPromise_(constraints); - }; - } else { - // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia - // function which returns a Promise, it does not accept spec-style - // constraints. - var origGetUserMedia = navigator.mediaDevices.getUserMedia. - bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(cs) { - return shimConstraints_(cs, function(c) { - return origGetUserMedia(c).catch(function(e) { - return Promise.reject(shimError_(e)); - }); - }); - }; - } - - // Dummy devicechange event methods. - // TODO(KaptenJansson) remove once implemented in Chrome stable. - if (typeof navigator.mediaDevices.addEventListener === 'undefined') { - navigator.mediaDevices.addEventListener = function() { - logging('Dummy mediaDevices.addEventListener called.'); - }; - } - if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { - navigator.mediaDevices.removeEventListener = function() { - logging('Dummy mediaDevices.removeEventListener called.'); - }; - } - }; - -},{"../utils.js":38}],33:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var SDPUtils = require('sdp'); - var logging = require('../utils').log; - - var edgeShim = { - shimPeerConnection: function() { - if (window.RTCIceGatherer) { - // ORTC defines an RTCIceCandidate object but no constructor. - // Not implemented in Edge. - if (!window.RTCIceCandidate) { - window.RTCIceCandidate = function(args) { - return args; - }; - } - // ORTC does not have a session description object but - // other browsers (i.e. Chrome) that will support both PC and ORTC - // in the future might have this defined already. - if (!window.RTCSessionDescription) { - window.RTCSessionDescription = function(args) { - return args; - }; - } - } - - window.RTCPeerConnection = function(config) { - var self = this; - - var _eventTarget = document.createDocumentFragment(); - ['addEventListener', 'removeEventListener', 'dispatchEvent'] - .forEach(function(method) { - self[method] = _eventTarget[method].bind(_eventTarget); - }); - - this.onicecandidate = null; - this.onaddstream = null; - this.ontrack = null; - this.onremovestream = null; - this.onsignalingstatechange = null; - this.oniceconnectionstatechange = null; - this.onnegotiationneeded = null; - this.ondatachannel = null; - - this.localStreams = []; - this.remoteStreams = []; - this.getLocalStreams = function() { - return self.localStreams; - }; - this.getRemoteStreams = function() { - return self.remoteStreams; - }; - - this.localDescription = new RTCSessionDescription({ - type: '', - sdp: '' - }); - this.remoteDescription = new RTCSessionDescription({ - type: '', - sdp: '' - }); - this.signalingState = 'stable'; - this.iceConnectionState = 'new'; - this.iceGatheringState = 'new'; - - this.iceOptions = { - gatherPolicy: 'all', - iceServers: [] - }; - if (config && config.iceTransportPolicy) { - switch (config.iceTransportPolicy) { - case 'all': - case 'relay': - this.iceOptions.gatherPolicy = config.iceTransportPolicy; - break; - case 'none': - // FIXME: remove once implementation and spec have added this. - throw new TypeError('iceTransportPolicy "none" not supported'); - default: - // don't set iceTransportPolicy. - break; - } - } - this.usingBundle = config && config.bundlePolicy === 'max-bundle'; - - if (config && config.iceServers) { - // Edge does not like - // 1) stun: - // 2) turn: that does not have all of turn:host:port?transport=udp - var iceServers = JSON.parse(JSON.stringify(config.iceServers)); - this.iceOptions.iceServers = iceServers.filter(function(server) { - if (server && server.urls) { - var urls = server.urls; - if (typeof urls === 'string') { - urls = [urls]; - } - urls = urls.filter(function(url) { - return url.indexOf('turn:') === 0 && - url.indexOf('transport=udp') !== -1; - })[0]; - return !!urls; - } - return false; - }); - } - - // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... - // everything that is needed to describe a SDP m-line. - this.transceivers = []; - - // since the iceGatherer is currently created in createOffer but we - // must not emit candidates until after setLocalDescription we buffer - // them in this array. - this._localIceCandidatesBuffer = []; - }; - - window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { - var self = this; - var sections = SDPUtils.splitSections(self.localDescription.sdp); - // FIXME: need to apply ice candidates in a way which is async but - // in-order - this._localIceCandidatesBuffer.forEach(function(event) { - var end = !event.candidate || Object.keys(event.candidate).length === 0; - if (end) { - for (var j = 1; j < sections.length; j++) { - if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) { - sections[j] += 'a=end-of-candidates\r\n'; - } - } - } else if (event.candidate.candidate.indexOf('typ endOfCandidates') - === -1) { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=' + event.candidate.candidate + '\r\n'; - } - self.localDescription.sdp = sections.join(''); - self.dispatchEvent(event); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - if (!event.candidate && self.iceGatheringState !== 'complete') { - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - if (complete) { - self.iceGatheringState = 'complete'; - } - } - }); - this._localIceCandidatesBuffer = []; - }; - - window.RTCPeerConnection.prototype.addStream = function(stream) { - // Clone is necessary for local demos mostly, attaching directly - // to two different senders does not work (build 10547). - this.localStreams.push(stream.clone()); - this._maybeFireNegotiationNeeded(); - }; - - window.RTCPeerConnection.prototype.removeStream = function(stream) { - var idx = this.localStreams.indexOf(stream); - if (idx > -1) { - this.localStreams.splice(idx, 1); - this._maybeFireNegotiationNeeded(); - } - }; - - window.RTCPeerConnection.prototype.getSenders = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpSender; - }) - .map(function(transceiver) { - return transceiver.rtpSender; - }); - }; - - window.RTCPeerConnection.prototype.getReceivers = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpReceiver; - }) - .map(function(transceiver) { - return transceiver.rtpReceiver; - }); - }; - - // Determines the intersection of local and remote capabilities. - window.RTCPeerConnection.prototype._getCommonCapabilities = - function(localCapabilities, remoteCapabilities) { - var commonCapabilities = { - codecs: [], - headerExtensions: [], - fecMechanisms: [] - }; - localCapabilities.codecs.forEach(function(lCodec) { - for (var i = 0; i < remoteCapabilities.codecs.length; i++) { - var rCodec = remoteCapabilities.codecs[i]; - if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && - lCodec.clockRate === rCodec.clockRate && - lCodec.numChannels === rCodec.numChannels) { - // push rCodec so we reply with offerer payload type - commonCapabilities.codecs.push(rCodec); - - // FIXME: also need to determine intersection between - // .rtcpFeedback and .parameters - break; - } - } - }); - - localCapabilities.headerExtensions - .forEach(function(lHeaderExtension) { - for (var i = 0; i < remoteCapabilities.headerExtensions.length; - i++) { - var rHeaderExtension = remoteCapabilities.headerExtensions[i]; - if (lHeaderExtension.uri === rHeaderExtension.uri) { - commonCapabilities.headerExtensions.push(rHeaderExtension); - break; - } - } - }); - - // FIXME: fecMechanisms - return commonCapabilities; - }; - - // Create ICE gatherer, ICE transport and DTLS transport. - window.RTCPeerConnection.prototype._createIceAndDtlsTransports = - function(mid, sdpMLineIndex) { - var self = this; - var iceGatherer = new RTCIceGatherer(self.iceOptions); - var iceTransport = new RTCIceTransport(iceGatherer); - iceGatherer.onlocalcandidate = function(evt) { - var event = new Event('icecandidate'); - event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; - - var cand = evt.candidate; - var end = !cand || Object.keys(cand).length === 0; - // Edge emits an empty object for RTCIceCandidateComplete‥ - if (end) { - // polyfill since RTCIceGatherer.state is not implemented in - // Edge 10547 yet. - if (iceGatherer.state === undefined) { - iceGatherer.state = 'completed'; - } - - // Emit a candidate with type endOfCandidates to make the samples - // work. Edge requires addIceCandidate with this empty candidate - // to start checking. The real solution is to signal - // end-of-candidates to the other side when getting the null - // candidate but some apps (like the samples) don't do that. - event.candidate.candidate = - 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; - } else { - // RTCIceCandidate doesn't have a component, needs to be added - cand.component = iceTransport.component === 'RTCP' ? 2 : 1; - event.candidate.candidate = SDPUtils.writeCandidate(cand); - } - - // update local description. - var sections = SDPUtils.splitSections(self.localDescription.sdp); - if (event.candidate.candidate.indexOf('typ endOfCandidates') - === -1) { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=' + event.candidate.candidate + '\r\n'; - } else { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=end-of-candidates\r\n'; - } - self.localDescription.sdp = sections.join(''); - - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - - // Emit candidate if localDescription is set. - // Also emits null candidate when all gatherers are complete. - switch (self.iceGatheringState) { - case 'new': - self._localIceCandidatesBuffer.push(event); - if (end && complete) { - self._localIceCandidatesBuffer.push( - new Event('icecandidate')); - } - break; - case 'gathering': - self._emitBufferedCandidates(); - self.dispatchEvent(event); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - if (complete) { - self.dispatchEvent(new Event('icecandidate')); - if (self.onicecandidate !== null) { - self.onicecandidate(new Event('icecandidate')); - } - self.iceGatheringState = 'complete'; - } - break; - case 'complete': - // should not happen... currently! - break; - default: // no-op. - break; - } - }; - iceTransport.onicestatechange = function() { - self._updateConnectionState(); - }; - - var dtlsTransport = new RTCDtlsTransport(iceTransport); - dtlsTransport.ondtlsstatechange = function() { - self._updateConnectionState(); - }; - dtlsTransport.onerror = function() { - // onerror does not set state to failed by itself. - dtlsTransport.state = 'failed'; - self._updateConnectionState(); - }; - - return { - iceGatherer: iceGatherer, - iceTransport: iceTransport, - dtlsTransport: dtlsTransport - }; - }; - - // Start the RTP Sender and Receiver for a transceiver. - window.RTCPeerConnection.prototype._transceive = function(transceiver, - send, recv) { - var params = this._getCommonCapabilities(transceiver.localCapabilities, - transceiver.remoteCapabilities); - if (send && transceiver.rtpSender) { - params.encodings = transceiver.sendEncodingParameters; - params.rtcp = { - cname: SDPUtils.localCName - }; - if (transceiver.recvEncodingParameters.length) { - params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; - } - transceiver.rtpSender.send(params); - } - if (recv && transceiver.rtpReceiver) { - params.encodings = transceiver.recvEncodingParameters; - params.rtcp = { - cname: transceiver.cname - }; - if (transceiver.sendEncodingParameters.length) { - params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; - } - transceiver.rtpReceiver.receive(params); - } - }; - - window.RTCPeerConnection.prototype.setLocalDescription = - function(description) { - var self = this; - var sections; - var sessionpart; - if (description.type === 'offer') { - // FIXME: What was the purpose of this empty if statement? - // if (!this._pendingOffer) { - // } else { - if (this._pendingOffer) { - // VERY limited support for SDP munging. Limited to: - // * changing the order of codecs - sections = SDPUtils.splitSections(description.sdp); - sessionpart = sections.shift(); - sections.forEach(function(mediaSection, sdpMLineIndex) { - var caps = SDPUtils.parseRtpParameters(mediaSection); - self._pendingOffer[sdpMLineIndex].localCapabilities = caps; - }); - this.transceivers = this._pendingOffer; - delete this._pendingOffer; - } - } else if (description.type === 'answer') { - sections = SDPUtils.splitSections(self.remoteDescription.sdp); - sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var transceiver = self.transceivers[sdpMLineIndex]; - var iceGatherer = transceiver.iceGatherer; - var iceTransport = transceiver.iceTransport; - var dtlsTransport = transceiver.dtlsTransport; - var localCapabilities = transceiver.localCapabilities; - var remoteCapabilities = transceiver.remoteCapabilities; - var rejected = mediaSection.split('\n', 1)[0] - .split(' ', 2)[1] === '0'; - - if (!rejected) { - var remoteIceParameters = SDPUtils.getIceParameters( - mediaSection, sessionpart); - if (isIceLite) { - var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') - .map(function(cand) { - return SDPUtils.parseCandidate(cand); - }) - .filter(function(cand) { - return cand.component === '1'; - }); - // ice-lite only includes host candidates in the SDP so we can - // use setRemoteCandidates (which implies an - // RTCIceCandidateComplete) - if (cands.length) { - iceTransport.setRemoteCandidates(cands); - } - } - var remoteDtlsParameters = SDPUtils.getDtlsParameters( - mediaSection, sessionpart); - if (isIceLite) { - remoteDtlsParameters.role = 'server'; - } - - if (!self.usingBundle || sdpMLineIndex === 0) { - iceTransport.start(iceGatherer, remoteIceParameters, - isIceLite ? 'controlling' : 'controlled'); - dtlsTransport.start(remoteDtlsParameters); - } - - // Calculate intersection of capabilities. - var params = self._getCommonCapabilities(localCapabilities, - remoteCapabilities); - - // Start the RTCRtpSender. The RTCRtpReceiver for this - // transceiver has already been started in setRemoteDescription. - self._transceive(transceiver, - params.codecs.length > 0, - false); - } - }); - } - - this.localDescription = { - type: description.type, - sdp: description.sdp - }; - switch (description.type) { - case 'offer': - this._updateSignalingState('have-local-offer'); - break; - case 'answer': - this._updateSignalingState('stable'); - break; - default: - throw new TypeError('unsupported type "' + description.type + - '"'); - } - - // If a success callback was provided, emit ICE candidates after it - // has been executed. Otherwise, emit callback after the Promise is - // resolved. - var hasCallback = arguments.length > 1 && - typeof arguments[1] === 'function'; - if (hasCallback) { - var cb = arguments[1]; - window.setTimeout(function() { - cb(); - if (self.iceGatheringState === 'new') { - self.iceGatheringState = 'gathering'; - } - self._emitBufferedCandidates(); - }, 0); - } - var p = Promise.resolve(); - p.then(function() { - if (!hasCallback) { - if (self.iceGatheringState === 'new') { - self.iceGatheringState = 'gathering'; - } - // Usually candidates will be emitted earlier. - window.setTimeout(self._emitBufferedCandidates.bind(self), 500); - } - }); - return p; - }; - - window.RTCPeerConnection.prototype.setRemoteDescription = - function(description) { - var self = this; - var stream = new MediaStream(); - var receiverList = []; - var sections = SDPUtils.splitSections(description.sdp); - var sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - this.usingBundle = SDPUtils.matchPrefix(sessionpart, - 'a=group:BUNDLE ').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].substr(2).split(' '); - var kind = mline[0]; - var rejected = mline[1] === '0'; - var direction = SDPUtils.getDirection(mediaSection, sessionpart); - - var transceiver; - var iceGatherer; - var iceTransport; - var dtlsTransport; - var rtpSender; - var rtpReceiver; - var sendEncodingParameters; - var recvEncodingParameters; - var localCapabilities; - - var track; - // FIXME: ensure the mediaSection has rtcp-mux set. - var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); - var remoteIceParameters; - var remoteDtlsParameters; - if (!rejected) { - remoteIceParameters = SDPUtils.getIceParameters(mediaSection, - sessionpart); - remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, - sessionpart); - remoteDtlsParameters.role = 'client'; - } - recvEncodingParameters = - SDPUtils.parseRtpEncodingParameters(mediaSection); - - var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); - if (mid.length) { - mid = mid[0].substr(6); - } else { - mid = SDPUtils.generateIdentifier(); - } - - var cname; - // Gets the first SSRC. Note that with RTX there might be multiple - // SSRCs. - var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') - .map(function(line) { - return SDPUtils.parseSsrcMedia(line); - }) - .filter(function(obj) { - return obj.attribute === 'cname'; - })[0]; - if (remoteSsrc) { - cname = remoteSsrc.value; - } - - var isComplete = SDPUtils.matchPrefix(mediaSection, - 'a=end-of-candidates').length > 0; - var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') - .map(function(cand) { - return SDPUtils.parseCandidate(cand); - }) - .filter(function(cand) { - return cand.component === '1'; - }); - if (description.type === 'offer' && !rejected) { - var transports = self.usingBundle && sdpMLineIndex > 0 ? { - iceGatherer: self.transceivers[0].iceGatherer, - iceTransport: self.transceivers[0].iceTransport, - dtlsTransport: self.transceivers[0].dtlsTransport - } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); - - if (isComplete) { - transports.iceTransport.setRemoteCandidates(cands); - } - - localCapabilities = RTCRtpReceiver.getCapabilities(kind); - sendEncodingParameters = [{ - ssrc: (2 * sdpMLineIndex + 2) * 1001 - }]; - - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); - - track = rtpReceiver.track; - receiverList.push([track, rtpReceiver]); - // FIXME: not correct when there are multiple streams but that is - // not currently supported in this shim. - stream.addTrack(track); - - // FIXME: look at direction. - if (self.localStreams.length > 0 && - self.localStreams[0].getTracks().length >= sdpMLineIndex) { - // FIXME: actually more complicated, needs to match types etc - var localtrack = self.localStreams[0] - .getTracks()[sdpMLineIndex]; - rtpSender = new RTCRtpSender(localtrack, - transports.dtlsTransport); - } - - self.transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: remoteCapabilities, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - cname: cname, - sendEncodingParameters: sendEncodingParameters, - recvEncodingParameters: recvEncodingParameters - }; - // Start the RTCRtpReceiver now. The RTPSender is started in - // setLocalDescription. - self._transceive(self.transceivers[sdpMLineIndex], - false, - direction === 'sendrecv' || direction === 'sendonly'); - } else if (description.type === 'answer' && !rejected) { - transceiver = self.transceivers[sdpMLineIndex]; - iceGatherer = transceiver.iceGatherer; - iceTransport = transceiver.iceTransport; - dtlsTransport = transceiver.dtlsTransport; - rtpSender = transceiver.rtpSender; - rtpReceiver = transceiver.rtpReceiver; - sendEncodingParameters = transceiver.sendEncodingParameters; - localCapabilities = transceiver.localCapabilities; - - self.transceivers[sdpMLineIndex].recvEncodingParameters = - recvEncodingParameters; - self.transceivers[sdpMLineIndex].remoteCapabilities = - remoteCapabilities; - self.transceivers[sdpMLineIndex].cname = cname; - - if ((isIceLite || isComplete) && cands.length) { - iceTransport.setRemoteCandidates(cands); - } - if (!self.usingBundle || sdpMLineIndex === 0) { - iceTransport.start(iceGatherer, remoteIceParameters, - 'controlling'); - dtlsTransport.start(remoteDtlsParameters); - } - - self._transceive(transceiver, - direction === 'sendrecv' || direction === 'recvonly', - direction === 'sendrecv' || direction === 'sendonly'); - - if (rtpReceiver && - (direction === 'sendrecv' || direction === 'sendonly')) { - track = rtpReceiver.track; - receiverList.push([track, rtpReceiver]); - stream.addTrack(track); - } else { - // FIXME: actually the receiver should be created later. - delete transceiver.rtpReceiver; - } - } - }); - - this.remoteDescription = { - type: description.type, - sdp: description.sdp - }; - switch (description.type) { - case 'offer': - this._updateSignalingState('have-remote-offer'); - break; - case 'answer': - this._updateSignalingState('stable'); - break; - default: - throw new TypeError('unsupported type "' + description.type + - '"'); - } - if (stream.getTracks().length) { - self.remoteStreams.push(stream); - window.setTimeout(function() { - var event = new Event('addstream'); - event.stream = stream; - self.dispatchEvent(event); - if (self.onaddstream !== null) { - window.setTimeout(function() { - self.onaddstream(event); - }, 0); - } - - receiverList.forEach(function(item) { - var track = item[0]; - var receiver = item[1]; - var trackEvent = new Event('track'); - trackEvent.track = track; - trackEvent.receiver = receiver; - trackEvent.streams = [stream]; - self.dispatchEvent(event); - if (self.ontrack !== null) { - window.setTimeout(function() { - self.ontrack(trackEvent); - }, 0); - } - }); - }, 0); - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; - - window.RTCPeerConnection.prototype.close = function() { - this.transceivers.forEach(function(transceiver) { - /* not yet - if (transceiver.iceGatherer) { - transceiver.iceGatherer.close(); - } - */ - if (transceiver.iceTransport) { - transceiver.iceTransport.stop(); - } - if (transceiver.dtlsTransport) { - transceiver.dtlsTransport.stop(); - } - if (transceiver.rtpSender) { - transceiver.rtpSender.stop(); - } - if (transceiver.rtpReceiver) { - transceiver.rtpReceiver.stop(); - } - }); - // FIXME: clean up tracks, local streams, remote streams, etc - this._updateSignalingState('closed'); - }; - - // Update the signaling state. - window.RTCPeerConnection.prototype._updateSignalingState = - function(newState) { - this.signalingState = newState; - var event = new Event('signalingstatechange'); - this.dispatchEvent(event); - if (this.onsignalingstatechange !== null) { - this.onsignalingstatechange(event); - } - }; - - // Determine whether to fire the negotiationneeded event. - window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = - function() { - // Fire away (for now). - var event = new Event('negotiationneeded'); - this.dispatchEvent(event); - if (this.onnegotiationneeded !== null) { - this.onnegotiationneeded(event); - } - }; - - // Update the connection state. - window.RTCPeerConnection.prototype._updateConnectionState = function() { - var self = this; - var newState; - var states = { - 'new': 0, - closed: 0, - connecting: 0, - checking: 0, - connected: 0, - completed: 0, - failed: 0 - }; - this.transceivers.forEach(function(transceiver) { - states[transceiver.iceTransport.state]++; - states[transceiver.dtlsTransport.state]++; - }); - // ICETransport.completed and connected are the same for this purpose. - states.connected += states.completed; - - newState = 'new'; - if (states.failed > 0) { - newState = 'failed'; - } else if (states.connecting > 0 || states.checking > 0) { - newState = 'connecting'; - } else if (states.disconnected > 0) { - newState = 'disconnected'; - } else if (states.new > 0) { - newState = 'new'; - } else if (states.connected > 0 || states.completed > 0) { - newState = 'connected'; - } - - if (newState !== self.iceConnectionState) { - self.iceConnectionState = newState; - var event = new Event('iceconnectionstatechange'); - this.dispatchEvent(event); - if (this.oniceconnectionstatechange !== null) { - this.oniceconnectionstatechange(event); - } - } - }; - - window.RTCPeerConnection.prototype.createOffer = function() { - var self = this; - if (this._pendingOffer) { - throw new Error('createOffer called while there is a pending offer.'); - } - var offerOptions; - if (arguments.length === 1 && typeof arguments[0] !== 'function') { - offerOptions = arguments[0]; - } else if (arguments.length === 3) { - offerOptions = arguments[2]; - } - - var tracks = []; - var numAudioTracks = 0; - var numVideoTracks = 0; - // Default to sendrecv. - if (this.localStreams.length) { - numAudioTracks = this.localStreams[0].getAudioTracks().length; - numVideoTracks = this.localStreams[0].getVideoTracks().length; - } - // Determine number of audio and video tracks we need to send/recv. - if (offerOptions) { - // Reject Chrome legacy constraints. - if (offerOptions.mandatory || offerOptions.optional) { - throw new TypeError( - 'Legacy mandatory/optional constraints not supported.'); - } - if (offerOptions.offerToReceiveAudio !== undefined) { - numAudioTracks = offerOptions.offerToReceiveAudio; - } - if (offerOptions.offerToReceiveVideo !== undefined) { - numVideoTracks = offerOptions.offerToReceiveVideo; - } - } - if (this.localStreams.length) { - // Push local streams. - this.localStreams[0].getTracks().forEach(function(track) { - tracks.push({ - kind: track.kind, - track: track, - wantReceive: track.kind === 'audio' ? - numAudioTracks > 0 : numVideoTracks > 0 - }); - if (track.kind === 'audio') { - numAudioTracks--; - } else if (track.kind === 'video') { - numVideoTracks--; - } - }); - } - // Create M-lines for recvonly streams. - while (numAudioTracks > 0 || numVideoTracks > 0) { - if (numAudioTracks > 0) { - tracks.push({ - kind: 'audio', - wantReceive: true - }); - numAudioTracks--; - } - if (numVideoTracks > 0) { - tracks.push({ - kind: 'video', - wantReceive: true - }); - numVideoTracks--; - } - } - - var sdp = SDPUtils.writeSessionBoilerplate(); - var transceivers = []; - tracks.forEach(function(mline, sdpMLineIndex) { - // For each track, create an ice gatherer, ice transport, - // dtls transport, potentially rtpsender and rtpreceiver. - var track = mline.track; - var kind = mline.kind; - var mid = SDPUtils.generateIdentifier(); - - var transports = self.usingBundle && sdpMLineIndex > 0 ? { - iceGatherer: transceivers[0].iceGatherer, - iceTransport: transceivers[0].iceTransport, - dtlsTransport: transceivers[0].dtlsTransport - } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); - - var localCapabilities = RTCRtpSender.getCapabilities(kind); - var rtpSender; - var rtpReceiver; - - // generate an ssrc now, to be used later in rtpSender.send - var sendEncodingParameters = [{ - ssrc: (2 * sdpMLineIndex + 1) * 1001 - }]; - if (track) { - rtpSender = new RTCRtpSender(track, transports.dtlsTransport); - } - - if (mline.wantReceive) { - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); - } - - transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: null, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - sendEncodingParameters: sendEncodingParameters, - recvEncodingParameters: null - }; - }); - if (this.usingBundle) { - sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; - } - tracks.forEach(function(mline, sdpMLineIndex) { - var transceiver = transceivers[sdpMLineIndex]; - sdp += SDPUtils.writeMediaSection(transceiver, - transceiver.localCapabilities, 'offer', self.localStreams[0]); - }); - - this._pendingOffer = transceivers; - var desc = new RTCSessionDescription({ - type: 'offer', - sdp: sdp - }); - if (arguments.length && typeof arguments[0] === 'function') { - window.setTimeout(arguments[0], 0, desc); - } - return Promise.resolve(desc); - }; - - window.RTCPeerConnection.prototype.createAnswer = function() { - var self = this; - - var sdp = SDPUtils.writeSessionBoilerplate(); - if (this.usingBundle) { - sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; - } - this.transceivers.forEach(function(transceiver) { - // Calculate intersection of capabilities. - var commonCapabilities = self._getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); - - sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, - 'answer', self.localStreams[0]); - }); - - var desc = new RTCSessionDescription({ - type: 'answer', - sdp: sdp - }); - if (arguments.length && typeof arguments[0] === 'function') { - window.setTimeout(arguments[0], 0, desc); - } - return Promise.resolve(desc); - }; - - window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - if (candidate === null) { - this.transceivers.forEach(function(transceiver) { - transceiver.iceTransport.addRemoteCandidate({}); - }); - } else { - var mLineIndex = candidate.sdpMLineIndex; - if (candidate.sdpMid) { - for (var i = 0; i < this.transceivers.length; i++) { - if (this.transceivers[i].mid === candidate.sdpMid) { - mLineIndex = i; - break; - } - } - } - var transceiver = this.transceivers[mLineIndex]; - if (transceiver) { - var cand = Object.keys(candidate.candidate).length > 0 ? - SDPUtils.parseCandidate(candidate.candidate) : {}; - // Ignore Chrome's invalid candidates since Edge does not like them. - if (cand.protocol === 'tcp' && cand.port === 0) { - return; - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component !== '1') { - return; - } - // A dirty hack to make samples work. - if (cand.type === 'endOfCandidates') { - cand = {}; - } - transceiver.iceTransport.addRemoteCandidate(cand); - - // update the remoteDescription. - var sections = SDPUtils.splitSections(this.remoteDescription.sdp); - sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() - : 'a=end-of-candidates') + '\r\n'; - this.remoteDescription.sdp = sections.join(''); - } - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; - - window.RTCPeerConnection.prototype.getStats = function() { - var promises = []; - this.transceivers.forEach(function(transceiver) { - ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', - 'dtlsTransport'].forEach(function(method) { - if (transceiver[method]) { - promises.push(transceiver[method].getStats()); - } - }); - }); - var cb = arguments.length > 1 && typeof arguments[1] === 'function' && - arguments[1]; - return new Promise(function(resolve) { - // shim getStats with maplike support - var results = new Map(); - Promise.all(promises).then(function(res) { - res.forEach(function(result) { - Object.keys(result).forEach(function(id) { - results.set(id, result[id]); - results[id] = result[id]; - }); - }); - if (cb) { - window.setTimeout(cb, 0, results); - } - resolve(results); - }); - }); - }; - }, - - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - element.srcObject = stream; - }, - - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - to.srcObject = from.srcObject; - } - }; - -// Expose public methods. - module.exports = { - shimPeerConnection: edgeShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia'), - attachMediaStream: edgeShim.attachMediaStream, - reattachMediaStream: edgeShim.reattachMediaStream - }; - -},{"../utils":38,"./getusermedia":34,"sdp":62}],34:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - -// Expose public methods. - module.exports = function() { - var shimError_ = function(e) { - return { - name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, - message: e.message, - constraint: e.constraint, - toString: function() { - return this.name; - } - }; - }; - - // getUserMedia error shim. - var origGetUserMedia = navigator.mediaDevices.getUserMedia. - bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(c) { - return origGetUserMedia(c).catch(function(e) { - return Promise.reject(shimError_(e)); - }); - }; - }; - -},{}],35:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var logging = require('../utils').log; - var browserDetails = require('../utils').browserDetails; - - var firefoxShim = { - shimOnTrack: function() { - if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in - window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { - get: function() { - return this._ontrack; - }, - set: function(f) { - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - this.removeEventListener('addstream', this._ontrackpoly); - } - this.addEventListener('track', this._ontrack = f); - this.addEventListener('addstream', this._ontrackpoly = function(e) { - e.stream.getTracks().forEach(function(track) { - var event = new Event('track'); - event.track = track; - event.receiver = {track: track}; - event.streams = [e.stream]; - this.dispatchEvent(event); - }.bind(this)); - }.bind(this)); - } - }); - } - }, - - shimSourceObject: function() { - // Firefox has supported mozSrcObject since FF22, unprefixed in 42. - if (typeof window === 'object') { - if (window.HTMLMediaElement && - !('srcObject' in window.HTMLMediaElement.prototype)) { - // Shim the srcObject property, once, when HTMLMediaElement is found. - Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { - get: function() { - return this.mozSrcObject; - }, - set: function(stream) { - this.mozSrcObject = stream; - } - }); - } - } - }, - - shimPeerConnection: function() { - if (typeof window !== 'object' || !(window.RTCPeerConnection || - window.mozRTCPeerConnection)) { - return; // probably media.peerconnection.enabled=false in about:config - } - // The RTCPeerConnection object. - if (!window.RTCPeerConnection) { - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - if (browserDetails.version < 38) { - // .urls is not supported in FF < 38. - // create RTCIceServers with a single url. - if (pcConfig && pcConfig.iceServers) { - var newIceServers = []; - for (var i = 0; i < pcConfig.iceServers.length; i++) { - var server = pcConfig.iceServers[i]; - if (server.hasOwnProperty('urls')) { - for (var j = 0; j < server.urls.length; j++) { - var newServer = { - url: server.urls[j] - }; - if (server.urls[j].indexOf('turn') === 0) { - newServer.username = server.username; - newServer.credential = server.credential; - } - newIceServers.push(newServer); - } - } else { - newIceServers.push(pcConfig.iceServers[i]); - } - } - pcConfig.iceServers = newIceServers; - } - } - return new mozRTCPeerConnection(pcConfig, pcConstraints); - }; - window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (mozRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - return mozRTCPeerConnection.generateCertificate; - } - }); - } - - window.RTCSessionDescription = mozRTCSessionDescription; - window.RTCIceCandidate = mozRTCIceCandidate; - } - - // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate') ? - RTCIceCandidate : RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }; - }); - - // support for addIceCandidate(null) - var nativeAddIceCandidate = - RTCPeerConnection.prototype.addIceCandidate; - RTCPeerConnection.prototype.addIceCandidate = function() { - return arguments[0] === null ? Promise.resolve() - : nativeAddIceCandidate.apply(this, arguments); - }; - - // shim getStats with maplike support - var makeMapStats = function(stats) { - var map = new Map(); - Object.keys(stats).forEach(function(key) { - map.set(key, stats[key]); - map[key] = stats[key]; - }); - return map; - }; - - var nativeGetStats = RTCPeerConnection.prototype.getStats; - RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { - return nativeGetStats.apply(this, [selector || null]) - .then(function(stats) { - return makeMapStats(stats); - }) - .then(onSucc, onErr); - }; - }, - - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - element.srcObject = stream; - }, - - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - to.srcObject = from.srcObject; - } - }; - -// Expose public methods. - module.exports = { - shimOnTrack: firefoxShim.shimOnTrack, - shimSourceObject: firefoxShim.shimSourceObject, - shimPeerConnection: firefoxShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia'), - attachMediaStream: firefoxShim.attachMediaStream, - reattachMediaStream: firefoxShim.reattachMediaStream - }; - -},{"../utils":38,"./getusermedia":36}],36:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var logging = require('../utils').log; - var browserDetails = require('../utils').browserDetails; - -// Expose public methods. - module.exports = function() { - var shimError_ = function(e) { - return { - name: { - SecurityError: 'NotAllowedError', - PermissionDeniedError: 'NotAllowedError' - }[e.name] || e.name, - message: { - 'The operation is insecure.': 'The request is not allowed by the ' + - 'user agent or the platform in the current context.' - }[e.message] || e.message, - constraint: e.constraint, - toString: function() { - return this.name + (this.message && ': ') + this.message; - } - }; - }; - - // getUserMedia constraints shim. - var getUserMedia_ = function(constraints, onSuccess, onError) { - var constraintsToFF37_ = function(c) { - if (typeof c !== 'object' || c.require) { - return c; - } - var require = []; - Object.keys(c).forEach(function(key) { - if (key === 'require' || key === 'advanced' || key === 'mediaSource') { - return; - } - var r = c[key] = (typeof c[key] === 'object') ? - c[key] : {ideal: c[key]}; - if (r.min !== undefined || - r.max !== undefined || r.exact !== undefined) { - require.push(key); - } - if (r.exact !== undefined) { - if (typeof r.exact === 'number') { - r. min = r.max = r.exact; - } else { - c[key] = r.exact; - } - delete r.exact; - } - if (r.ideal !== undefined) { - c.advanced = c.advanced || []; - var oc = {}; - if (typeof r.ideal === 'number') { - oc[key] = {min: r.ideal, max: r.ideal}; - } else { - oc[key] = r.ideal; - } - c.advanced.push(oc); - delete r.ideal; - if (!Object.keys(r).length) { - delete c[key]; - } - } - }); - if (require.length) { - c.require = require; - } - return c; - }; - constraints = JSON.parse(JSON.stringify(constraints)); - if (browserDetails.version < 38) { - logging('spec: ' + JSON.stringify(constraints)); - if (constraints.audio) { - constraints.audio = constraintsToFF37_(constraints.audio); - } - if (constraints.video) { - constraints.video = constraintsToFF37_(constraints.video); - } - logging('ff37: ' + JSON.stringify(constraints)); - } - return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { - onError(shimError_(e)); - }); - }; - - // Returns the result of getUserMedia as a Promise. - var getUserMediaPromise_ = function(constraints) { - return new Promise(function(resolve, reject) { - getUserMedia_(constraints, resolve, reject); - }); - }; - - // Shim for mediaDevices on older versions. - if (!navigator.mediaDevices) { - navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, - addEventListener: function() { }, - removeEventListener: function() { } - }; - } - navigator.mediaDevices.enumerateDevices = - navigator.mediaDevices.enumerateDevices || function() { - return new Promise(function(resolve) { - var infos = [ - {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, - {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} - ]; - resolve(infos); - }); - }; - - if (browserDetails.version < 41) { - // Work around http://bugzil.la/1169665 - var orgEnumerateDevices = - navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); - navigator.mediaDevices.enumerateDevices = function() { - return orgEnumerateDevices().then(undefined, function(e) { - if (e.name === 'NotFoundError') { - return []; - } - throw e; - }); - }; - } - if (browserDetails.version < 49) { - var origGetUserMedia = navigator.mediaDevices.getUserMedia. - bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(c) { - return origGetUserMedia(c).catch(function(e) { - return Promise.reject(shimError_(e)); - }); - }; - } - navigator.getUserMedia = function(constraints, onSuccess, onError) { - if (browserDetails.version < 44) { - return getUserMedia_(constraints, onSuccess, onError); - } - // Replace Firefox 44+'s deprecation warning with unprefixed version. - console.warn('navigator.getUserMedia has been replaced by ' + - 'navigator.mediaDevices.getUserMedia'); - navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); - }; - }; - -},{"../utils":38}],37:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - 'use strict'; - var safariShim = { - // TODO: DrAlex, should be here, double check against LayoutTests - // shimOnTrack: function() { }, - - // TODO: DrAlex - // attachMediaStream: function(element, stream) { }, - // reattachMediaStream: function(to, from) { }, - - // TODO: once the back-end for the mac port is done, add. - // TODO: check for webkitGTK+ - // shimPeerConnection: function() { }, - - shimGetUserMedia: function() { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - }; - -// Expose public methods. - module.exports = { - shimGetUserMedia: safariShim.shimGetUserMedia - // TODO - // shimOnTrack: safariShim.shimOnTrack, - // shimPeerConnection: safariShim.shimPeerConnection, - // attachMediaStream: safariShim.attachMediaStream, - // reattachMediaStream: safariShim.reattachMediaStream - }; - -},{}],38:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var logDisabled_ = true; - -// Utility methods. - var utils = { - disableLog: function(bool) { - if (typeof bool !== 'boolean') { - return new Error('Argument type: ' + typeof bool + - '. Please use a boolean.'); - } - logDisabled_ = bool; - return (bool) ? 'adapter.js logging disabled' : - 'adapter.js logging enabled'; - }, - - log: function() { - if (typeof window === 'object') { - if (logDisabled_) { - return; - } - if (typeof console !== 'undefined' && typeof console.log === 'function') { - console.log.apply(console, arguments); - } - } - }, - - /** - * Extract browser version out of the provided user agent string. - * - * @param {!string} uastring userAgent string. - * @param {!string} expr Regular expression used as match criteria. - * @param {!number} pos position in the version string to be returned. - * @return {!number} browser version. - */ - extractVersion: function(uastring, expr, pos) { - var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); - }, - - /** - * Browser detector. - * - * @return {object} result containing browser, version and minVersion - * properties. - */ - detectBrowser: function() { - // Returned result object. - var result = {}; - result.browser = null; - result.version = null; - result.minVersion = null; - - // Fail early if it's not a browser - if (typeof window === 'undefined' || !window.navigator) { - result.browser = 'Not a browser.'; - return result; - } - - // Firefox. - if (navigator.mozGetUserMedia) { - result.browser = 'firefox'; - result.version = this.extractVersion(navigator.userAgent, - /Firefox\/([0-9]+)\./, 1); - result.minVersion = 31; - - // all webkit-based browsers - } else if (navigator.webkitGetUserMedia) { - // Chrome, Chromium, Webview, Opera, all use the chrome shim for now - if (window.webkitRTCPeerConnection) { - result.browser = 'chrome'; - result.version = this.extractVersion(navigator.userAgent, - /Chrom(e|ium)\/([0-9]+)\./, 2); - result.minVersion = 38; - - // Safari or unknown webkit-based - // for the time being Safari has support for MediaStreams but not webRTC - } else { - // Safari UA substrings of interest for reference: - // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr) - // - safari UI version: Version/9.0.3 (unique to Safari) - // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr) - // - // if the webkit version and safari UI webkit versions are equals, - // ... this is a stable version. - // - // only the internal webkit version is important today to know if - // media streams are supported - // - if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { - result.browser = 'safari'; - result.version = this.extractVersion(navigator.userAgent, - /AppleWebKit\/([0-9]+)\./, 1); - result.minVersion = 602; - - // unknown webkit-based browser - } else { - result.browser = 'Unsupported webkit-based browser ' + - 'with GUM support but no WebRTC support.'; - return result; - } - } - - // Edge. - } else if (navigator.mediaDevices && - navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { - result.browser = 'edge'; - result.version = this.extractVersion(navigator.userAgent, - /Edge\/(\d+).(\d+)$/, 2); - result.minVersion = 10547; - - // Default fallthrough: not supported. - } else { - result.browser = 'Not a supported browser.'; - return result; - } - - // Warn if version is less than minVersion. - if (result.version < result.minVersion) { - utils.log('Browser: ' + result.browser + ' Version: ' + result.version + - ' < minimum supported version: ' + result.minVersion + - '\n some things might not work!'); - } - - return result; - } - }; - -// Export. - module.exports = { - log: utils.log, - disableLog: utils.disableLog, - browserDetails: utils.detectBrowser(), - extractVersion: utils.extractVersion - }; - -},{}],39:[function(require,module,exports){ - - /** - * Returns `this`. Execute this without a "context" (i.e. without it being - * attached to an object of the left-hand side), and `this` points to the - * "global" scope of the current JS execution. - */ - - module.exports = (function () { return this; })(); - -},{}],40:[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; - if (typeof window !== 'undefined') { - 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.frequencyBinCount); - - 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'); - } - analyser.disconnect(); - sourceNode.disconnect(); - }; - 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":100}],41:[function(require,module,exports){ - (function (global){ - - /* - * Module requirements. - */ - - var isArray = require('isarray'); - - /** - * Module exports. - */ - - module.exports = hasBinary; - - /** - * Checks for binary data. - * - * Right now only Buffer and ArrayBuffer are supported.. - * - * @param {Object} anything - * @api public - */ - - function hasBinary(data) { - - function _hasBinary(obj) { - if (!obj) return false; - - if ( (global.Buffer && global.Buffer.isBuffer(obj)) || - (global.ArrayBuffer && obj instanceof ArrayBuffer) || - (global.Blob && obj instanceof Blob) || - (global.File && obj instanceof File) - ) { - return true; - } - - if (isArray(obj)) { - for (var i = 0; i < obj.length; i++) { - if (_hasBinary(obj[i])) { - return true; - } - } - } else if (obj && 'object' == typeof obj) { - if (obj.toJSON) { - obj = obj.toJSON(); - } - - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) { - return true; - } - } - } - - return false; - } - - return _hasBinary(data); - } - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"isarray":42}],42:[function(require,module,exports){ - module.exports = Array.isArray || function (arr) { - return Object.prototype.toString.call(arr) == '[object Array]'; - }; - -},{}],43:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var global = require('global'); - - /** - * Module exports. - * - * Logic borrowed from Modernizr: - * - * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js - */ - - try { - module.exports = 'XMLHttpRequest' in global && - 'withCredentials' in new global.XMLHttpRequest(); - } catch (err) { - // if XMLHttp support is disabled in IE then it will throw - // when trying to create - module.exports = false; - } - -},{"global":39}],44:[function(require,module,exports){ - - var indexOf = [].indexOf; - - module.exports = function(arr, obj){ - if (indexOf) return arr.indexOf(obj); - for (var i = 0; i < arr.length; ++i) { - if (arr[i] === obj) return i; - } - return -1; - }; -},{}],45:[function(require,module,exports){ - /*! JSON v3.2.6 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */ - ;(function (window) { - // Convenience aliases. - var getClass = {}.toString, isProperty, forEach, undef; - - // Detect the `define` function exposed by asynchronous module loaders. The - // strict `define` check is necessary for compatibility with `r.js`. - var isLoader = typeof define === "function" && define.amd; - - // Detect native implementations. - var nativeJSON = typeof JSON == "object" && JSON; - - // Set up the JSON 3 namespace, preferring the CommonJS `exports` object if - // available. - var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports; - - if (JSON3 && nativeJSON) { - // Explicitly delegate to the native `stringify` and `parse` - // implementations in CommonJS environments. - JSON3.stringify = nativeJSON.stringify; - JSON3.parse = nativeJSON.parse; - } else { - // Export for web browsers, JavaScript engines, and asynchronous module - // loaders, using the global `JSON` object if available. - JSON3 = window.JSON = nativeJSON || {}; - } - - // Test the `Date#getUTC*` methods. Based on work by @Yaffle. - var isExtended = new Date(-3509827334573292); - try { - // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical - // results for certain dates in Opera >= 10.53. - isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 && - // Safari < 2.0.2 stores the internal millisecond time value correctly, - // but clips the values returned by the date methods to the range of - // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]). - isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708; - } catch (exception) {} - - // Internal: Determines whether the native `JSON.stringify` and `parse` - // implementations are spec-compliant. Based on work by Ken Snyder. - function has(name) { - if (has[name] !== undef) { - // Return cached feature test result. - return has[name]; - } - - var isSupported; - if (name == "bug-string-char-index") { - // IE <= 7 doesn't support accessing string characters using square - // bracket notation. IE 8 only supports this for primitives. - isSupported = "a"[0] != "a"; - } else if (name == "json") { - // Indicates whether both `JSON.stringify` and `JSON.parse` are - // supported. - isSupported = has("json-stringify") && has("json-parse"); - } else { - var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'; - // Test `JSON.stringify`. - if (name == "json-stringify") { - var stringify = JSON3.stringify, stringifySupported = typeof stringify == "function" && isExtended; - if (stringifySupported) { - // A test function object with a custom `toJSON` method. - (value = function () { - return 1; - }).toJSON = value; - try { - stringifySupported = - // Firefox 3.1b1 and b2 serialize string, number, and boolean - // primitives as object literals. - stringify(0) === "0" && - // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object - // literals. - stringify(new Number()) === "0" && - stringify(new String()) == '""' && - // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or - // does not define a canonical JSON representation (this applies to - // objects with `toJSON` properties as well, *unless* they are nested - // within an object or array). - stringify(getClass) === undef && - // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and - // FF 3.1b3 pass this test. - stringify(undef) === undef && - // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s, - // respectively, if the value is omitted entirely. - stringify() === undef && - // FF 3.1b1, 2 throw an error if the given value is not a number, - // string, array, object, Boolean, or `null` literal. This applies to - // objects with custom `toJSON` methods as well, unless they are nested - // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON` - // methods entirely. - stringify(value) === "1" && - stringify([value]) == "[1]" && - // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of - // `"[null]"`. - stringify([undef]) == "[null]" && - // YUI 3.0.0b1 fails to serialize `null` literals. - stringify(null) == "null" && - // FF 3.1b1, 2 halts serialization if an array contains a function: - // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3 - // elides non-JSON values from objects and arrays, unless they - // define custom `toJSON` methods. - stringify([undef, getClass, null]) == "[null,null,null]" && - // Simple serialization test. FF 3.1b1 uses Unicode escape sequences - // where character escape codes are expected (e.g., `\b` => `\u0008`). - stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized && - // FF 3.1b1 and b2 ignore the `filter` and `width` arguments. - stringify(null, value) === "1" && - stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" && - // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly - // serialize extended years. - stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' && - // The milliseconds are optional in ES 5, but required in 5.1. - stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' && - // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative - // four-digit years instead of six-digit years. Credits: @Yaffle. - stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' && - // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond - // values less than 1000. Credits: @Yaffle. - stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"'; - } catch (exception) { - stringifySupported = false; - } - } - isSupported = stringifySupported; - } - // Test `JSON.parse`. - if (name == "json-parse") { - var parse = JSON3.parse; - if (typeof parse == "function") { - try { - // FF 3.1b1, b2 will throw an exception if a bare literal is provided. - // Conforming implementations should also coerce the initial argument to - // a string prior to parsing. - if (parse("0") === 0 && !parse(false)) { - // Simple parsing test. - value = parse(serialized); - var parseSupported = value["a"].length == 5 && value["a"][0] === 1; - if (parseSupported) { - try { - // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings. - parseSupported = !parse('"\t"'); - } catch (exception) {} - if (parseSupported) { - try { - // FF 4.0 and 4.0.1 allow leading `+` signs and leading - // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow - // certain octal literals. - parseSupported = parse("01") !== 1; - } catch (exception) {} - } - if (parseSupported) { - try { - // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal - // points. These environments, along with FF 3.1b1 and 2, - // also allow trailing commas in JSON objects and arrays. - parseSupported = parse("1.") !== 1; - } catch (exception) {} - } - } - } - } catch (exception) { - parseSupported = false; - } - } - isSupported = parseSupported; - } - } - return has[name] = !!isSupported; - } - - if (!has("json")) { - // Common `[[Class]]` name aliases. - var functionClass = "[object Function]"; - var dateClass = "[object Date]"; - var numberClass = "[object Number]"; - var stringClass = "[object String]"; - var arrayClass = "[object Array]"; - var booleanClass = "[object Boolean]"; - - // Detect incomplete support for accessing string characters by index. - var charIndexBuggy = has("bug-string-char-index"); - - // Define additional utility methods if the `Date` methods are buggy. - if (!isExtended) { - var floor = Math.floor; - // A mapping between the months of the year and the number of days between - // January 1st and the first of the respective month. - var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - // Internal: Calculates the number of days between the Unix epoch and the - // first day of the given month. - var getDay = function (year, month) { - return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400); - }; - } - - // Internal: Determines if a property is a direct property of the given - // object. Delegates to the native `Object#hasOwnProperty` method. - if (!(isProperty = {}.hasOwnProperty)) { - isProperty = function (property) { - var members = {}, constructor; - if ((members.__proto__ = null, members.__proto__ = { - // The *proto* property cannot be set multiple times in recent - // versions of Firefox and SeaMonkey. - "toString": 1 - }, members).toString != getClass) { - // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but - // supports the mutable *proto* property. - isProperty = function (property) { - // Capture and break the object's prototype chain (see section 8.6.2 - // of the ES 5.1 spec). The parenthesized expression prevents an - // unsafe transformation by the Closure Compiler. - var original = this.__proto__, result = property in (this.__proto__ = null, this); - // Restore the original prototype chain. - this.__proto__ = original; - return result; - }; - } else { - // Capture a reference to the top-level `Object` constructor. - constructor = members.constructor; - // Use the `constructor` property to simulate `Object#hasOwnProperty` in - // other environments. - isProperty = function (property) { - var parent = (this.constructor || constructor).prototype; - return property in this && !(property in parent && this[property] === parent[property]); - }; - } - members = null; - return isProperty.call(this, property); - }; - } - - // Internal: A set of primitive types used by `isHostType`. - var PrimitiveTypes = { - 'boolean': 1, - 'number': 1, - 'string': 1, - 'undefined': 1 - }; - - // Internal: Determines if the given object `property` value is a - // non-primitive. - var isHostType = function (object, property) { - var type = typeof object[property]; - return type == 'object' ? !!object[property] : !PrimitiveTypes[type]; - }; - - // Internal: Normalizes the `for...in` iteration algorithm across - // environments. Each enumerated key is yielded to a `callback` function. - forEach = function (object, callback) { - var size = 0, Properties, members, property; - - // Tests for bugs in the current environment's `for...in` algorithm. The - // `valueOf` property inherits the non-enumerable flag from - // `Object.prototype` in older versions of IE, Netscape, and Mozilla. - (Properties = function () { - this.valueOf = 0; - }).prototype.valueOf = 0; - - // Iterate over a new instance of the `Properties` class. - members = new Properties(); - for (property in members) { - // Ignore all properties inherited from `Object.prototype`. - if (isProperty.call(members, property)) { - size++; - } - } - Properties = members = null; - - // Normalize the iteration algorithm. - if (!size) { - // A list of non-enumerable properties inherited from `Object.prototype`. - members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"]; - // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable - // properties. - forEach = function (object, callback) { - var isFunction = getClass.call(object) == functionClass, property, length; - var hasProperty = !isFunction && typeof object.constructor != 'function' && isHostType(object, 'hasOwnProperty') ? object.hasOwnProperty : isProperty; - for (property in object) { - // Gecko <= 1.0 enumerates the `prototype` property of functions under - // certain conditions; IE does not. - if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) { - callback(property); - } - } - // Manually invoke the callback for each non-enumerable property. - for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property)); - }; - } else if (size == 2) { - // Safari <= 2.0.4 enumerates shadowed properties twice. - forEach = function (object, callback) { - // Create a set of iterated properties. - var members = {}, isFunction = getClass.call(object) == functionClass, property; - for (property in object) { - // Store each property name to prevent double enumeration. The - // `prototype` property of functions is not enumerated due to cross- - // environment inconsistencies. - if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) { - callback(property); - } - } - }; - } else { - // No bugs detected; use the standard `for...in` algorithm. - forEach = function (object, callback) { - var isFunction = getClass.call(object) == functionClass, property, isConstructor; - for (property in object) { - if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) { - callback(property); - } - } - // Manually invoke the callback for the `constructor` property due to - // cross-environment inconsistencies. - if (isConstructor || isProperty.call(object, (property = "constructor"))) { - callback(property); - } - }; - } - return forEach(object, callback); - }; - - // Public: Serializes a JavaScript `value` as a JSON string. The optional - // `filter` argument may specify either a function that alters how object and - // array members are serialized, or an array of strings and numbers that - // indicates which properties should be serialized. The optional `width` - // argument may be either a string or number that specifies the indentation - // level of the output. - if (!has("json-stringify")) { - // Internal: A map of control characters and their escaped equivalents. - var Escapes = { - 92: "\\\\", - 34: '\\"', - 8: "\\b", - 12: "\\f", - 10: "\\n", - 13: "\\r", - 9: "\\t" - }; - - // Internal: Converts `value` into a zero-padded string such that its - // length is at least equal to `width`. The `width` must be <= 6. - var leadingZeroes = "000000"; - var toPaddedString = function (width, value) { - // The `|| 0` expression is necessary to work around a bug in - // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`. - return (leadingZeroes + (value || 0)).slice(-width); - }; - - // Internal: Double-quotes a string `value`, replacing all ASCII control - // characters (characters with code unit values between 0 and 31) with - // their escaped equivalents. This is an implementation of the - // `Quote(value)` operation defined in ES 5.1 section 15.12.3. - var unicodePrefix = "\\u00"; - var quote = function (value) { - var result = '"', index = 0, length = value.length, isLarge = length > 10 && charIndexBuggy, symbols; - if (isLarge) { - symbols = value.split(""); - } - for (; index < length; index++) { - var charCode = value.charCodeAt(index); - // If the character is a control character, append its Unicode or - // shorthand escape sequence; otherwise, append the character as-is. - switch (charCode) { - case 8: case 9: case 10: case 12: case 13: case 34: case 92: - result += Escapes[charCode]; - break; - default: - if (charCode < 32) { - result += unicodePrefix + toPaddedString(2, charCode.toString(16)); - break; - } - result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index]; - } - } - return result + '"'; - }; - - // Internal: Recursively serializes an object. Implements the - // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations. - var serialize = function (property, object, callback, properties, whitespace, indentation, stack) { - var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result; - try { - // Necessary for host object support. - value = object[property]; - } catch (exception) {} - if (typeof value == "object" && value) { - className = getClass.call(value); - if (className == dateClass && !isProperty.call(value, "toJSON")) { - if (value > -1 / 0 && value < 1 / 0) { - // Dates are serialized according to the `Date#toJSON` method - // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 - // for the ISO 8601 date time string format. - if (getDay) { - // Manually compute the year, month, date, hours, minutes, - // seconds, and milliseconds if the `getUTC*` methods are - // buggy. Adapted from @Yaffle's `date-shim` project. - date = floor(value / 864e5); - for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++); - for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++); - date = 1 + date - getDay(year, month); - // The `time` value specifies the time within the day (see ES - // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used - // to compute `A modulo B`, as the `%` operator does not - // correspond to the `modulo` operation for negative numbers. - time = (value % 864e5 + 864e5) % 864e5; - // The hours, minutes, seconds, and milliseconds are obtained by - // decomposing the time within the day. See section 15.9.1.10. - hours = floor(time / 36e5) % 24; - minutes = floor(time / 6e4) % 60; - seconds = floor(time / 1e3) % 60; - milliseconds = time % 1e3; - } else { - year = value.getUTCFullYear(); - month = value.getUTCMonth(); - date = value.getUTCDate(); - hours = value.getUTCHours(); - minutes = value.getUTCMinutes(); - seconds = value.getUTCSeconds(); - milliseconds = value.getUTCMilliseconds(); - } - // Serialize extended years correctly. - value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) + - "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) + - // Months, dates, hours, minutes, and seconds should have two - // digits; milliseconds should have three. - "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) + - // Milliseconds are optional in ES 5.0, but required in 5.1. - "." + toPaddedString(3, milliseconds) + "Z"; - } else { - value = null; - } - } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) { - // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the - // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3 - // ignores all `toJSON` methods on these objects unless they are - // defined directly on an instance. - value = value.toJSON(property); - } - } - if (callback) { - // If a replacement function was provided, call it to obtain the value - // for serialization. - value = callback.call(object, property, value); - } - if (value === null) { - return "null"; - } - className = getClass.call(value); - if (className == booleanClass) { - // Booleans are represented literally. - return "" + value; - } else if (className == numberClass) { - // JSON numbers must be finite. `Infinity` and `NaN` are serialized as - // `"null"`. - return value > -1 / 0 && value < 1 / 0 ? "" + value : "null"; - } else if (className == stringClass) { - // Strings are double-quoted and escaped. - return quote("" + value); - } - // Recursively serialize objects and arrays. - if (typeof value == "object") { - // Check for cyclic structures. This is a linear search; performance - // is inversely proportional to the number of unique nested objects. - for (length = stack.length; length--;) { - if (stack[length] === value) { - // Cyclic structures cannot be serialized by `JSON.stringify`. - throw TypeError(); - } - } - // Add the object to the stack of traversed objects. - stack.push(value); - results = []; - // Save the current indentation level and indent one additional level. - prefix = indentation; - indentation += whitespace; - if (className == arrayClass) { - // Recursively serialize array elements. - for (index = 0, length = value.length; index < length; index++) { - element = serialize(index, value, callback, properties, whitespace, indentation, stack); - results.push(element === undef ? "null" : element); - } - result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]"; - } else { - // Recursively serialize object members. Members are selected from - // either a user-specified list of property names, or the object - // itself. - forEach(properties || value, function (property) { - var element = serialize(property, value, callback, properties, whitespace, indentation, stack); - if (element !== undef) { - // According to ES 5.1 section 15.12.3: "If `gap` {whitespace} - // is not the empty string, let `member` {quote(property) + ":"} - // be the concatenation of `member` and the `space` character." - // The "`space` character" refers to the literal space - // character, not the `space` {width} argument provided to - // `JSON.stringify`. - results.push(quote(property) + ":" + (whitespace ? " " : "") + element); - } - }); - result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}"; - } - // Remove the object from the traversed object stack. - stack.pop(); - return result; - } - }; - - // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. - JSON3.stringify = function (source, filter, width) { - var whitespace, callback, properties, className; - if (typeof filter == "function" || typeof filter == "object" && filter) { - if ((className = getClass.call(filter)) == functionClass) { - callback = filter; - } else if (className == arrayClass) { - // Convert the property names array into a makeshift set. - properties = {}; - for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1)); - } - } - if (width) { - if ((className = getClass.call(width)) == numberClass) { - // Convert the `width` to an integer and create a string containing - // `width` number of space characters. - if ((width -= width % 1) > 0) { - for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " "); - } - } else if (className == stringClass) { - whitespace = width.length <= 10 ? width : width.slice(0, 10); - } - } - // Opera <= 7.54u2 discards the values associated with empty string keys - // (`""`) only if they are used directly within an object member list - // (e.g., `!("" in { "": 1})`). - return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []); - }; - } - - // Public: Parses a JSON source string. - if (!has("json-parse")) { - var fromCharCode = String.fromCharCode; - - // Internal: A map of escaped control characters and their unescaped - // equivalents. - var Unescapes = { - 92: "\\", - 34: '"', - 47: "/", - 98: "\b", - 116: "\t", - 110: "\n", - 102: "\f", - 114: "\r" - }; - - // Internal: Stores the parser state. - var Index, Source; - - // Internal: Resets the parser state and throws a `SyntaxError`. - var abort = function() { - Index = Source = null; - throw SyntaxError(); - }; - - // Internal: Returns the next token, or `"$"` if the parser has reached - // the end of the source string. A token may be a string, number, `null` - // literal, or Boolean literal. - var lex = function () { - var source = Source, length = source.length, value, begin, position, isSigned, charCode; - while (Index < length) { - charCode = source.charCodeAt(Index); - switch (charCode) { - case 9: case 10: case 13: case 32: - // Skip whitespace tokens, including tabs, carriage returns, line - // feeds, and space characters. - Index++; - break; - case 123: case 125: case 91: case 93: case 58: case 44: - // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at - // the current position. - value = charIndexBuggy ? source.charAt(Index) : source[Index]; - Index++; - return value; - case 34: - // `"` delimits a JSON string; advance to the next character and - // begin parsing the string. String tokens are prefixed with the - // sentinel `@` character to distinguish them from punctuators and - // end-of-string tokens. - for (value = "@", Index++; Index < length;) { - charCode = source.charCodeAt(Index); - if (charCode < 32) { - // Unescaped ASCII control characters (those with a code unit - // less than the space character) are not permitted. - abort(); - } else if (charCode == 92) { - // A reverse solidus (`\`) marks the beginning of an escaped - // control character (including `"`, `\`, and `/`) or Unicode - // escape sequence. - charCode = source.charCodeAt(++Index); - switch (charCode) { - case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114: - // Revive escaped control characters. - value += Unescapes[charCode]; - Index++; - break; - case 117: - // `\u` marks the beginning of a Unicode escape sequence. - // Advance to the first character and validate the - // four-digit code point. - begin = ++Index; - for (position = Index + 4; Index < position; Index++) { - charCode = source.charCodeAt(Index); - // A valid sequence comprises four hexdigits (case- - // insensitive) that form a single hexadecimal value. - if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) { - // Invalid Unicode escape sequence. - abort(); - } - } - // Revive the escaped character. - value += fromCharCode("0x" + source.slice(begin, Index)); - break; - default: - // Invalid escape sequence. - abort(); - } - } else { - if (charCode == 34) { - // An unescaped double-quote character marks the end of the - // string. - break; - } - charCode = source.charCodeAt(Index); - begin = Index; - // Optimize for the common case where a string is valid. - while (charCode >= 32 && charCode != 92 && charCode != 34) { - charCode = source.charCodeAt(++Index); - } - // Append the string as-is. - value += source.slice(begin, Index); - } - } - if (source.charCodeAt(Index) == 34) { - // Advance to the next character and return the revived string. - Index++; - return value; - } - // Unterminated string. - abort(); - default: - // Parse numbers and literals. - begin = Index; - // Advance past the negative sign, if one is specified. - if (charCode == 45) { - isSigned = true; - charCode = source.charCodeAt(++Index); - } - // Parse an integer or floating-point value. - if (charCode >= 48 && charCode <= 57) { - // Leading zeroes are interpreted as octal literals. - if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) { - // Illegal octal literal. - abort(); - } - isSigned = false; - // Parse the integer component. - for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++); - // Floats cannot contain a leading decimal point; however, this - // case is already accounted for by the parser. - if (source.charCodeAt(Index) == 46) { - position = ++Index; - // Parse the decimal component. - for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); - if (position == Index) { - // Illegal trailing decimal. - abort(); - } - Index = position; - } - // Parse exponents. The `e` denoting the exponent is - // case-insensitive. - charCode = source.charCodeAt(Index); - if (charCode == 101 || charCode == 69) { - charCode = source.charCodeAt(++Index); - // Skip past the sign following the exponent, if one is - // specified. - if (charCode == 43 || charCode == 45) { - Index++; - } - // Parse the exponential component. - for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); - if (position == Index) { - // Illegal empty exponent. - abort(); - } - Index = position; - } - // Coerce the parsed value to a JavaScript number. - return +source.slice(begin, Index); - } - // A negative sign may only precede numbers. - if (isSigned) { - abort(); - } - // `true`, `false`, and `null` literals. - if (source.slice(Index, Index + 4) == "true") { - Index += 4; - return true; - } else if (source.slice(Index, Index + 5) == "false") { - Index += 5; - return false; - } else if (source.slice(Index, Index + 4) == "null") { - Index += 4; - return null; - } - // Unrecognized token. - abort(); - } - } - // Return the sentinel `$` character if the parser has reached the end - // of the source string. - return "$"; - }; - - // Internal: Parses a JSON `value` token. - var get = function (value) { - var results, hasMembers; - if (value == "$") { - // Unexpected end of input. - abort(); - } - if (typeof value == "string") { - if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") { - // Remove the sentinel `@` character. - return value.slice(1); - } - // Parse object and array literals. - if (value == "[") { - // Parses a JSON array, returning a new JavaScript array. - results = []; - for (;; hasMembers || (hasMembers = true)) { - value = lex(); - // A closing square bracket marks the end of the array literal. - if (value == "]") { - break; - } - // If the array literal contains elements, the current token - // should be a comma separating the previous element from the - // next. - if (hasMembers) { - if (value == ",") { - value = lex(); - if (value == "]") { - // Unexpected trailing `,` in array literal. - abort(); - } - } else { - // A `,` must separate each array element. - abort(); - } - } - // Elisions and leading commas are not permitted. - if (value == ",") { - abort(); - } - results.push(get(value)); - } - return results; - } else if (value == "{") { - // Parses a JSON object, returning a new JavaScript object. - results = {}; - for (;; hasMembers || (hasMembers = true)) { - value = lex(); - // A closing curly brace marks the end of the object literal. - if (value == "}") { - break; - } - // If the object literal contains members, the current token - // should be a comma separator. - if (hasMembers) { - if (value == ",") { - value = lex(); - if (value == "}") { - // Unexpected trailing `,` in object literal. - abort(); - } - } else { - // A `,` must separate each object member. - abort(); - } - } - // Leading commas are not permitted, object property names must be - // double-quoted strings, and a `:` must separate each property - // name and value. - if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") { - abort(); - } - results[value.slice(1)] = get(lex()); - } - return results; - } - // Unexpected token encountered. - abort(); - } - return value; - }; - - // Internal: Updates a traversed object member. - var update = function(source, property, callback) { - var element = walk(source, property, callback); - if (element === undef) { - delete source[property]; - } else { - source[property] = element; - } - }; - - // Internal: Recursively traverses a parsed JSON object, invoking the - // `callback` function for each value. This is an implementation of the - // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2. - var walk = function (source, property, callback) { - var value = source[property], length; - if (typeof value == "object" && value) { - // `forEach` can't be used to traverse an array in Opera <= 8.54 - // because its `Object#hasOwnProperty` implementation returns `false` - // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`). - if (getClass.call(value) == arrayClass) { - for (length = value.length; length--;) { - update(value, length, callback); - } - } else { - forEach(value, function (property) { - update(value, property, callback); - }); - } - } - return callback.call(source, property, value); - }; - - // Public: `JSON.parse`. See ES 5.1 section 15.12.2. - JSON3.parse = function (source, callback) { - var result, value; - Index = 0; - Source = "" + source; - result = get(lex()); - // If a JSON string contains multiple tokens, it is invalid. - if (lex() != "$") { - abort(); - } - // Reset the parser state. - Index = Source = null; - return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; - }; - } - } - - // Export for asynchronous module loaders. - if (isLoader) { - define(function () { - return JSON3; - }); - } - }(this)); - -},{}],46:[function(require,module,exports){ - var util = require('util'); - var hark = require('hark'); - var webrtcSupport = require('webrtcsupport'); - var getUserMedia = require('getusermedia'); - var getScreenMedia = require('getscreenmedia'); - var WildEmitter = require('wildemitter'); - var GainController = require('mediastream-gain'); - var mockconsole = require('mockconsole'); - - - function isAllTracksEnded(stream) { - var isAllTracksEnded = true; - stream.getTracks().forEach(function (t) { - isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; - }); - return isAllTracksEnded; - } - - function LocalMedia(opts) { - WildEmitter.call(this); - - var config = this.config = { - autoAdjustMic: false, - detectSpeakingEvents: false, - audioFallback: false, - media: { - audio: true, - video: true - }, - logger: mockconsole - }; - - var item; - for (item in opts) { - if (opts.hasOwnProperty(item)) { - 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 = webrtcSupport.supportScreenSharing; - - this.localStreams = []; - this.localScreens = []; - - if (!webrtcSupport.supportGetUserMedia) { - 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); - } - - stream.getTracks().forEach(function (track) { - track.addEventListener('ended', function () { - if (isAllTracksEnded(stream)) { - self.emit('localStreamStopped', stream); - } - }); - }); - - self.emit('localStream', stream); - } else { - // Fallback for users without a camera - if (self.config.audioFallback && (err.name === 'DevicesNotFoundError' || err.name === 'NotFoundError') && constraints.video !== false) { - constraints.video = false; - self.start(constraints, cb); - return; - } - } - if (cb) { - return cb(err, stream); - } - }); - }; - - LocalMedia.prototype.stop = function (stream) { - // FIXME: duplicates cleanup code until fixed in FF - if (stream) { - stream.getTracks().forEach(function (track) { track.stop(); }); - var idx = this.localStreams.indexOf(stream); - if (idx > -1) { - this.localStreams.splice(idx, 1); - if (webrtcSupport.prefix === 'moz') { - this.emit('localStreamStopped', stream); - } - } else { - idx = this.localScreens.indexOf(stream); - if (idx > -1) { - this.localScreens.splice(idx, 1); - if (webrtcSupport.prefix === 'moz') { - this.emit('localScreenStopped', stream); - } - } - } - } else { - this.stopStreams(); - this.stopScreenShare(); - } - }; - - LocalMedia.prototype.stopStreams = function () { - var self = this; - if (this.audioMonitor) { - this.audioMonitor.stop(); - delete this.audioMonitor; - } - var oldLocalStream = this.localStreams; - this.localStreams = []; - - oldLocalStream.forEach(function (stream) { - stream.getTracks().forEach(function (track) { track.stop(); }); - if (webrtcSupport.prefix === 'moz') { - self.emit('localStreamStopped', stream); - } - }); - }; - - LocalMedia.prototype.startScreenShare = function (mode, cb) { - var self = this; - getScreenMedia(mode, function (err, stream) { - if (!err) { - self.localScreens.push(stream); - - stream.getTracks().forEach(function (track) { - track.addEventListener('ended', function () { - if (isAllTracksEnded(stream)) { - self.emit('localScreenStopped', stream); - } - }); - }); - - self.emit('localScreen', stream); - } - - // enable the callback - if (cb) { - return cb(err, stream); - } - }); - }; - - LocalMedia.prototype.stopScreenShare = function (stream) { - var self = this; - if (stream) { - stream.getTracks().forEach(function (track) { track.stop(); }); - var idx = this.localScreens.indexOf(stream); - if (idx > -1) { - this.localScreens.splice(idx, 1); - if (webrtcSupport.prefix === 'moz') { - this.emit('localScreenStopped', stream); - } - } - } else { - var oldLocalScreens = this.localScreens; - this.localScreens = []; - - oldLocalScreens.forEach(function (stream) { - stream.getTracks().forEach(function (track) { track.stop(); }); - if (webrtcSupport.prefix === 'moz') { - self.emit('localScreenStopped', stream); - } - }); - } - }; - -// 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; - var hasAudioTracks = false; - this.localStreams.forEach(function (stream) { - var audioTracks = stream.getAudioTracks(); - if (audioTracks.length > 0) { - hasAudioTracks = true; - audioTracks.forEach(function (track) { - enabled = enabled && track.enabled; - }); - } - }); - - // If no audioTracks were found, that means there is no microphone device. - // In that case, isAudioEnabled should return false. - if (!hasAudioTracks) { - return false; - } - - return enabled; - }; - -// check if all video streams are enabled - LocalMedia.prototype.isVideoEnabled = function () { - var enabled = true; - var hasVideoTracks = false; - this.localStreams.forEach(function (stream) { - var videoTracks = stream.getVideoTracks(); - if (videoTracks.length > 0) { - hasVideoTracks = true; - videoTracks.forEach(function (track) { - enabled = enabled && track.enabled; - }); - } - }); - - // If no videoTracks were found, that means there is no camera device. - // In that case, isVideoEnabled should return false. - if (!hasVideoTracks) { - return false; - } - - 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":27,"getusermedia":29,"hark":40,"mediastream-gain":48,"mockconsole":50,"util":89,"webrtcsupport":99,"wildemitter":100}],47:[function(require,module,exports){ - (function (global){ - /** - * lodash (Custom Build) <https://lodash.com/> - * Build: `lodash modularize exports="npm" -o ./` - * Copyright jQuery Foundation and other contributors <https://jquery.org/> - * Released under MIT license <https://lodash.com/license> - * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */ - - /** Used as the size to enable large array optimizations. */ - var LARGE_ARRAY_SIZE = 200; - - /** Used to stand-in for `undefined` hash values. */ - var HASH_UNDEFINED = '__lodash_hash_undefined__'; - - /** Used as references for various `Number` constants. */ - var MAX_SAFE_INTEGER = 9007199254740991; - - /** `Object#toString` result references. */ - var argsTag = '[object Arguments]', - arrayTag = '[object Array]', - boolTag = '[object Boolean]', - dateTag = '[object Date]', - errorTag = '[object Error]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]', - mapTag = '[object Map]', - numberTag = '[object Number]', - objectTag = '[object Object]', - promiseTag = '[object Promise]', - regexpTag = '[object RegExp]', - setTag = '[object Set]', - stringTag = '[object String]', - symbolTag = '[object Symbol]', - weakMapTag = '[object WeakMap]'; - - var arrayBufferTag = '[object ArrayBuffer]', - dataViewTag = '[object DataView]', - float32Tag = '[object Float32Array]', - float64Tag = '[object Float64Array]', - int8Tag = '[object Int8Array]', - int16Tag = '[object Int16Array]', - int32Tag = '[object Int32Array]', - uint8Tag = '[object Uint8Array]', - uint8ClampedTag = '[object Uint8ClampedArray]', - uint16Tag = '[object Uint16Array]', - uint32Tag = '[object Uint32Array]'; - - /** - * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). - */ - var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; - - /** Used to match `RegExp` flags from their coerced string values. */ - var reFlags = /\w*$/; - - /** Used to detect host constructors (Safari). */ - var reIsHostCtor = /^\[object .+?Constructor\]$/; - - /** Used to detect unsigned integer values. */ - var reIsUint = /^(?:0|[1-9]\d*)$/; - - /** Used to identify `toStringTag` values supported by `_.clone`. */ - var cloneableTags = {}; - cloneableTags[argsTag] = cloneableTags[arrayTag] = - cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = - cloneableTags[boolTag] = cloneableTags[dateTag] = - cloneableTags[float32Tag] = cloneableTags[float64Tag] = - cloneableTags[int8Tag] = cloneableTags[int16Tag] = - cloneableTags[int32Tag] = cloneableTags[mapTag] = - cloneableTags[numberTag] = cloneableTags[objectTag] = - cloneableTags[regexpTag] = cloneableTags[setTag] = - cloneableTags[stringTag] = cloneableTags[symbolTag] = - cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = - cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; - cloneableTags[errorTag] = cloneableTags[funcTag] = - cloneableTags[weakMapTag] = false; - - /** Detect free variable `global` from Node.js. */ - var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; - - /** Detect free variable `self`. */ - var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - - /** Used as a reference to the global object. */ - var root = freeGlobal || freeSelf || Function('return this')(); - - /** Detect free variable `exports`. */ - var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; - - /** Detect free variable `module`. */ - var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; - - /** Detect the popular CommonJS extension `module.exports`. */ - var moduleExports = freeModule && freeModule.exports === freeExports; - - /** - * Adds the key-value `pair` to `map`. - * - * @private - * @param {Object} map The map to modify. - * @param {Array} pair The key-value pair to add. - * @returns {Object} Returns `map`. - */ - function addMapEntry(map, pair) { - // Don't return `map.set` because it's not chainable in IE 11. - map.set(pair[0], pair[1]); - return map; - } - - /** - * Adds `value` to `set`. - * - * @private - * @param {Object} set The set to modify. - * @param {*} value The value to add. - * @returns {Object} Returns `set`. - */ - function addSetEntry(set, value) { - // Don't return `set.add` because it's not chainable in IE 11. - set.add(value); - return set; - } - - /** - * A specialized version of `_.forEach` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ - function arrayEach(array, iteratee) { - var index = -1, - length = array ? array.length : 0; - - while (++index < length) { - if (iteratee(array[index], index, array) === false) { - break; - } - } - return array; - } - - /** - * Appends the elements of `values` to `array`. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to append. - * @returns {Array} Returns `array`. - */ - function arrayPush(array, values) { - var index = -1, - length = values.length, - offset = array.length; - - while (++index < length) { - array[offset + index] = values[index]; - } - return array; - } - - /** - * A specialized version of `_.reduce` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the first element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduce(array, iteratee, accumulator, initAccum) { - var index = -1, - length = array ? array.length : 0; - - if (initAccum && length) { - accumulator = array[++index]; - } - while (++index < length) { - accumulator = iteratee(accumulator, array[index], index, array); - } - return accumulator; - } - - /** - * The base implementation of `_.times` without support for iteratee shorthands - * or max array length checks. - * - * @private - * @param {number} n The number of times to invoke `iteratee`. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the array of results. - */ - function baseTimes(n, iteratee) { - var index = -1, - result = Array(n); - - while (++index < n) { - result[index] = iteratee(index); - } - return result; - } - - /** - * Gets the value at `key` of `object`. - * - * @private - * @param {Object} [object] The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function getValue(object, key) { - return object == null ? undefined : object[key]; - } - - /** - * Checks if `value` is a host object in IE < 9. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a host object, else `false`. - */ - function isHostObject(value) { - // Many host objects are `Object` objects that can coerce to strings - // despite having improperly defined `toString` methods. - var result = false; - if (value != null && typeof value.toString != 'function') { - try { - result = !!(value + ''); - } catch (e) {} - } - return result; - } - - /** - * Converts `map` to its key-value pairs. - * - * @private - * @param {Object} map The map to convert. - * @returns {Array} Returns the key-value pairs. - */ - function mapToArray(map) { - var index = -1, - result = Array(map.size); - - map.forEach(function(value, key) { - result[++index] = [key, value]; - }); - return result; - } - - /** - * Creates a unary function that invokes `func` with its argument transformed. - * - * @private - * @param {Function} func The function to wrap. - * @param {Function} transform The argument transform. - * @returns {Function} Returns the new function. - */ - function overArg(func, transform) { - return function(arg) { - return func(transform(arg)); - }; - } - - /** - * Converts `set` to an array of its values. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the values. - */ - function setToArray(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = value; - }); - return result; - } - - /** Used for built-in method references. */ - var arrayProto = Array.prototype, - funcProto = Function.prototype, - objectProto = Object.prototype; - - /** Used to detect overreaching core-js shims. */ - var coreJsData = root['__core-js_shared__']; - - /** Used to detect methods masquerading as native. */ - var maskSrcKey = (function() { - var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); - return uid ? ('Symbol(src)_1.' + uid) : ''; - }()); - - /** Used to resolve the decompiled source of functions. */ - var funcToString = funcProto.toString; - - /** Used to check objects for own properties. */ - var hasOwnProperty = objectProto.hasOwnProperty; - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var objectToString = objectProto.toString; - - /** Used to detect if a method is native. */ - var reIsNative = RegExp('^' + - funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' - ); - - /** Built-in value references. */ - var Buffer = moduleExports ? root.Buffer : undefined, - Symbol = root.Symbol, - Uint8Array = root.Uint8Array, - getPrototype = overArg(Object.getPrototypeOf, Object), - objectCreate = Object.create, - propertyIsEnumerable = objectProto.propertyIsEnumerable, - splice = arrayProto.splice; - - /* Built-in method references for those with the same name as other `lodash` methods. */ - var nativeGetSymbols = Object.getOwnPropertySymbols, - nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, - nativeKeys = overArg(Object.keys, Object); - - /* Built-in method references that are verified to be native. */ - var DataView = getNative(root, 'DataView'), - Map = getNative(root, 'Map'), - Promise = getNative(root, 'Promise'), - Set = getNative(root, 'Set'), - WeakMap = getNative(root, 'WeakMap'), - nativeCreate = getNative(Object, 'create'); - - /** Used to detect maps, sets, and weakmaps. */ - var dataViewCtorString = toSource(DataView), - mapCtorString = toSource(Map), - promiseCtorString = toSource(Promise), - setCtorString = toSource(Set), - weakMapCtorString = toSource(WeakMap); - - /** Used to convert symbols to primitives and strings. */ - var symbolProto = Symbol ? Symbol.prototype : undefined, - symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; - - /** - * Creates a hash object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Hash(entries) { - var index = -1, - length = entries ? entries.length : 0; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ - function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - } - - /** - * Removes `key` and its value from the hash. - * - * @private - * @name delete - * @memberOf Hash - * @param {Object} hash The hash to modify. - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function hashDelete(key) { - return this.has(key) && delete this.__data__[key]; - } - - /** - * Gets the hash value for `key`. - * - * @private - * @name get - * @memberOf Hash - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function hashGet(key) { - var data = this.__data__; - if (nativeCreate) { - var result = data[key]; - return result === HASH_UNDEFINED ? undefined : result; - } - return hasOwnProperty.call(data, key) ? data[key] : undefined; - } - - /** - * Checks if a hash value for `key` exists. - * - * @private - * @name has - * @memberOf Hash - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function hashHas(key) { - var data = this.__data__; - return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); - } - - /** - * Sets the hash `key` to `value`. - * - * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. - */ - function hashSet(key, value) { - var data = this.__data__; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; - } - -// Add methods to `Hash`. - Hash.prototype.clear = hashClear; - Hash.prototype['delete'] = hashDelete; - Hash.prototype.get = hashGet; - Hash.prototype.has = hashHas; - Hash.prototype.set = hashSet; - - /** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function ListCache(entries) { - var index = -1, - length = entries ? entries.length : 0; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ - function listCacheClear() { - this.__data__ = []; - } - - /** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - return true; - } - - /** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; - } - - /** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; - } - - /** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. - */ - function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; - } - -// Add methods to `ListCache`. - ListCache.prototype.clear = listCacheClear; - ListCache.prototype['delete'] = listCacheDelete; - ListCache.prototype.get = listCacheGet; - ListCache.prototype.has = listCacheHas; - ListCache.prototype.set = listCacheSet; - - /** - * Creates a map cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function MapCache(entries) { - var index = -1, - length = entries ? entries.length : 0; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the map. - * - * @private - * @name clear - * @memberOf MapCache - */ - function mapCacheClear() { - this.__data__ = { - 'hash': new Hash, - 'map': new (Map || ListCache), - 'string': new Hash - }; - } - - /** - * Removes `key` and its value from the map. - * - * @private - * @name delete - * @memberOf MapCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function mapCacheDelete(key) { - return getMapData(this, key)['delete'](key); - } - - /** - * Gets the map value for `key`. - * - * @private - * @name get - * @memberOf MapCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function mapCacheGet(key) { - return getMapData(this, key).get(key); - } - - /** - * Checks if a map value for `key` exists. - * - * @private - * @name has - * @memberOf MapCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function mapCacheHas(key) { - return getMapData(this, key).has(key); - } - - /** - * Sets the map `key` to `value`. - * - * @private - * @name set - * @memberOf MapCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the map cache instance. - */ - function mapCacheSet(key, value) { - getMapData(this, key).set(key, value); - return this; - } - -// Add methods to `MapCache`. - MapCache.prototype.clear = mapCacheClear; - MapCache.prototype['delete'] = mapCacheDelete; - MapCache.prototype.get = mapCacheGet; - MapCache.prototype.has = mapCacheHas; - MapCache.prototype.set = mapCacheSet; - - /** - * Creates a stack cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Stack(entries) { - this.__data__ = new ListCache(entries); - } - - /** - * Removes all key-value entries from the stack. - * - * @private - * @name clear - * @memberOf Stack - */ - function stackClear() { - this.__data__ = new ListCache; - } - - /** - * Removes `key` and its value from the stack. - * - * @private - * @name delete - * @memberOf Stack - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function stackDelete(key) { - return this.__data__['delete'](key); - } - - /** - * Gets the stack value for `key`. - * - * @private - * @name get - * @memberOf Stack - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function stackGet(key) { - return this.__data__.get(key); - } - - /** - * Checks if a stack value for `key` exists. - * - * @private - * @name has - * @memberOf Stack - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function stackHas(key) { - return this.__data__.has(key); - } - - /** - * Sets the stack `key` to `value`. - * - * @private - * @name set - * @memberOf Stack - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the stack cache instance. - */ - function stackSet(key, value) { - var cache = this.__data__; - if (cache instanceof ListCache) { - var pairs = cache.__data__; - if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { - pairs.push([key, value]); - return this; - } - cache = this.__data__ = new MapCache(pairs); - } - cache.set(key, value); - return this; - } - -// Add methods to `Stack`. - Stack.prototype.clear = stackClear; - Stack.prototype['delete'] = stackDelete; - Stack.prototype.get = stackGet; - Stack.prototype.has = stackHas; - Stack.prototype.set = stackSet; - - /** - * Creates an array of the enumerable property names of the array-like `value`. - * - * @private - * @param {*} value The value to query. - * @param {boolean} inherited Specify returning inherited property names. - * @returns {Array} Returns the array of property names. - */ - function arrayLikeKeys(value, inherited) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - // Safari 9 makes `arguments.length` enumerable in strict mode. - var result = (isArray(value) || isArguments(value)) - ? baseTimes(value.length, String) - : []; - - var length = result.length, - skipIndexes = !!length; - - for (var key in value) { - if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && (key == 'length' || isIndex(key, length)))) { - result.push(key); - } - } - return result; - } - - /** - * Assigns `value` to `key` of `object` if the existing value is not equivalent - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignValue(object, key, value) { - var objValue = object[key]; - if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || - (value === undefined && !(key in object))) { - object[key] = value; - } - } - - /** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; - } - - /** - * The base implementation of `_.assign` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssign(object, source) { - return object && copyObject(source, keys(source), object); - } - - /** - * The base implementation of `_.clone` and `_.cloneDeep` which tracks - * traversed objects. - * - * @private - * @param {*} value The value to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @param {boolean} [isFull] Specify a clone including symbols. - * @param {Function} [customizer] The function to customize cloning. - * @param {string} [key] The key of `value`. - * @param {Object} [object] The parent object of `value`. - * @param {Object} [stack] Tracks traversed objects and their clone counterparts. - * @returns {*} Returns the cloned value. - */ - function baseClone(value, isDeep, isFull, customizer, key, object, stack) { - var result; - if (customizer) { - result = object ? customizer(value, key, object, stack) : customizer(value); - } - if (result !== undefined) { - return result; - } - if (!isObject(value)) { - return value; - } - var isArr = isArray(value); - if (isArr) { - result = initCloneArray(value); - if (!isDeep) { - return copyArray(value, result); - } - } else { - var tag = getTag(value), - isFunc = tag == funcTag || tag == genTag; - - if (isBuffer(value)) { - return cloneBuffer(value, isDeep); - } - if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - if (isHostObject(value)) { - return object ? value : {}; - } - result = initCloneObject(isFunc ? {} : value); - if (!isDeep) { - return copySymbols(value, baseAssign(result, value)); - } - } else { - if (!cloneableTags[tag]) { - return object ? value : {}; - } - result = initCloneByTag(value, tag, baseClone, isDeep); - } - } - // Check for circular references and return its corresponding clone. - stack || (stack = new Stack); - var stacked = stack.get(value); - if (stacked) { - return stacked; - } - stack.set(value, result); - - if (!isArr) { - var props = isFull ? getAllKeys(value) : keys(value); - } - arrayEach(props || value, function(subValue, key) { - if (props) { - key = subValue; - subValue = value[key]; - } - // Recursively populate clone (susceptible to call stack limits). - assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack)); - }); - return result; - } - - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} prototype The object to inherit from. - * @returns {Object} Returns the new object. - */ - function baseCreate(proto) { - return isObject(proto) ? objectCreate(proto) : {}; - } - - /** - * The base implementation of `getAllKeys` and `getAllKeysIn` which uses - * `keysFunc` and `symbolsFunc` to get the enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Function} keysFunc The function to get the keys of `object`. - * @param {Function} symbolsFunc The function to get the symbols of `object`. - * @returns {Array} Returns the array of property names and symbols. - */ - function baseGetAllKeys(object, keysFunc, symbolsFunc) { - var result = keysFunc(object); - return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); - } - - /** - * The base implementation of `getTag`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - function baseGetTag(value) { - return objectToString.call(value); - } - - /** - * The base implementation of `_.isNative` without bad shim checks. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - */ - function baseIsNative(value) { - if (!isObject(value) || isMasked(value)) { - return false; - } - var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; - return pattern.test(toSource(value)); - } - - /** - * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeys(object) { - if (!isPrototype(object)) { - return nativeKeys(object); - } - var result = []; - for (var key in Object(object)) { - if (hasOwnProperty.call(object, key) && key != 'constructor') { - result.push(key); - } - } - return result; - } - - /** - * Creates a clone of `buffer`. - * - * @private - * @param {Buffer} buffer The buffer to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Buffer} Returns the cloned buffer. - */ - function cloneBuffer(buffer, isDeep) { - if (isDeep) { - return buffer.slice(); - } - var result = new buffer.constructor(buffer.length); - buffer.copy(result); - return result; - } - - /** - * Creates a clone of `arrayBuffer`. - * - * @private - * @param {ArrayBuffer} arrayBuffer The array buffer to clone. - * @returns {ArrayBuffer} Returns the cloned array buffer. - */ - function cloneArrayBuffer(arrayBuffer) { - var result = new arrayBuffer.constructor(arrayBuffer.byteLength); - new Uint8Array(result).set(new Uint8Array(arrayBuffer)); - return result; - } - - /** - * Creates a clone of `dataView`. - * - * @private - * @param {Object} dataView The data view to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned data view. - */ - function cloneDataView(dataView, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; - return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); - } - - /** - * Creates a clone of `map`. - * - * @private - * @param {Object} map The map to clone. - * @param {Function} cloneFunc The function to clone values. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned map. - */ - function cloneMap(map, isDeep, cloneFunc) { - var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map); - return arrayReduce(array, addMapEntry, new map.constructor); - } - - /** - * Creates a clone of `regexp`. - * - * @private - * @param {Object} regexp The regexp to clone. - * @returns {Object} Returns the cloned regexp. - */ - function cloneRegExp(regexp) { - var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); - result.lastIndex = regexp.lastIndex; - return result; - } - - /** - * Creates a clone of `set`. - * - * @private - * @param {Object} set The set to clone. - * @param {Function} cloneFunc The function to clone values. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned set. - */ - function cloneSet(set, isDeep, cloneFunc) { - var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set); - return arrayReduce(array, addSetEntry, new set.constructor); - } - - /** - * Creates a clone of the `symbol` object. - * - * @private - * @param {Object} symbol The symbol object to clone. - * @returns {Object} Returns the cloned symbol object. - */ - function cloneSymbol(symbol) { - return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; - } - - /** - * Creates a clone of `typedArray`. - * - * @private - * @param {Object} typedArray The typed array to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned typed array. - */ - function cloneTypedArray(typedArray, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; - return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); - } - - /** - * Copies the values of `source` to `array`. - * - * @private - * @param {Array} source The array to copy values from. - * @param {Array} [array=[]] The array to copy values to. - * @returns {Array} Returns `array`. - */ - function copyArray(source, array) { - var index = -1, - length = source.length; - - array || (array = Array(length)); - while (++index < length) { - array[index] = source[index]; - } - return array; - } - - /** - * Copies properties of `source` to `object`. - * - * @private - * @param {Object} source The object to copy properties from. - * @param {Array} props The property identifiers to copy. - * @param {Object} [object={}] The object to copy properties to. - * @param {Function} [customizer] The function to customize copied values. - * @returns {Object} Returns `object`. - */ - function copyObject(source, props, object, customizer) { - object || (object = {}); - - var index = -1, - length = props.length; - - while (++index < length) { - var key = props[index]; - - var newValue = customizer - ? customizer(object[key], source[key], key, object, source) - : undefined; - - assignValue(object, key, newValue === undefined ? source[key] : newValue); - } - return object; - } - - /** - * Copies own symbol properties of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbols(source, object) { - return copyObject(source, getSymbols(source), object); - } - - /** - * Creates an array of own enumerable property names and symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeys(object) { - return baseGetAllKeys(object, keys, getSymbols); - } - - /** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ - function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; - } - - /** - * Gets the native function at `key` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. - */ - function getNative(object, key) { - var value = getValue(object, key); - return baseIsNative(value) ? value : undefined; - } - - /** - * Creates an array of the own enumerable symbol properties of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray; - - /** - * Gets the `toStringTag` of `value`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - var getTag = baseGetTag; - -// Fallback for data views, maps, sets, and weak maps in IE 11, -// for data views in Edge < 14, and promises in Node.js. - if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || - (Map && getTag(new Map) != mapTag) || - (Promise && getTag(Promise.resolve()) != promiseTag) || - (Set && getTag(new Set) != setTag) || - (WeakMap && getTag(new WeakMap) != weakMapTag)) { - getTag = function(value) { - var result = objectToString.call(value), - Ctor = result == objectTag ? value.constructor : undefined, - ctorString = Ctor ? toSource(Ctor) : undefined; - - if (ctorString) { - switch (ctorString) { - case dataViewCtorString: return dataViewTag; - case mapCtorString: return mapTag; - case promiseCtorString: return promiseTag; - case setCtorString: return setTag; - case weakMapCtorString: return weakMapTag; - } - } - return result; - }; - } - - /** - * Initializes an array clone. - * - * @private - * @param {Array} array The array to clone. - * @returns {Array} Returns the initialized clone. - */ - function initCloneArray(array) { - var length = array.length, - result = array.constructor(length); - - // Add properties assigned by `RegExp#exec`. - if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { - result.index = array.index; - result.input = array.input; - } - return result; - } - - /** - * Initializes an object clone. - * - * @private - * @param {Object} object The object to clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneObject(object) { - return (typeof object.constructor == 'function' && !isPrototype(object)) - ? baseCreate(getPrototype(object)) - : {}; - } - - /** - * Initializes an object clone based on its `toStringTag`. - * - * **Note:** This function only supports cloning values with tags of - * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. - * - * @private - * @param {Object} object The object to clone. - * @param {string} tag The `toStringTag` of the object to clone. - * @param {Function} cloneFunc The function to clone values. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneByTag(object, tag, cloneFunc, isDeep) { - var Ctor = object.constructor; - switch (tag) { - case arrayBufferTag: - return cloneArrayBuffer(object); - - case boolTag: - case dateTag: - return new Ctor(+object); - - case dataViewTag: - return cloneDataView(object, isDeep); - - case float32Tag: case float64Tag: - case int8Tag: case int16Tag: case int32Tag: - case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: - return cloneTypedArray(object, isDeep); - - case mapTag: - return cloneMap(object, isDeep, cloneFunc); - - case numberTag: - case stringTag: - return new Ctor(object); - - case regexpTag: - return cloneRegExp(object); - - case setTag: - return cloneSet(object, isDeep, cloneFunc); - - case symbolTag: - return cloneSymbol(object); - } - } - - /** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ - function isIndex(value, length) { - length = length == null ? MAX_SAFE_INTEGER : length; - return !!length && - (typeof value == 'number' || reIsUint.test(value)) && - (value > -1 && value % 1 == 0 && value < length); - } - - /** - * Checks if `value` is suitable for use as unique object key. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is suitable, else `false`. - */ - function isKeyable(value) { - var type = typeof value; - return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') - ? (value !== '__proto__') - : (value === null); - } - - /** - * Checks if `func` has its source masked. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is masked, else `false`. - */ - function isMasked(func) { - return !!maskSrcKey && (maskSrcKey in func); - } - - /** - * Checks if `value` is likely a prototype object. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. - */ - function isPrototype(value) { - var Ctor = value && value.constructor, - proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; - - return value === proto; - } - - /** - * Converts `func` to its source code. - * - * @private - * @param {Function} func The function to process. - * @returns {string} Returns the source code. - */ - function toSource(func) { - if (func != null) { - try { - return funcToString.call(func); - } catch (e) {} - try { - return (func + ''); - } catch (e) {} - } - return ''; - } - - /** - * This method is like `_.clone` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @returns {*} Returns the deep cloned value. - * @see _.clone - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var deep = _.cloneDeep(objects); - * console.log(deep[0] === objects[0]); - * // => false - */ - function cloneDeep(value) { - return baseClone(value, true, true); - } - - /** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ - function eq(value, other) { - return value === other || (value !== value && other !== other); - } - - /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - * else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - function isArguments(value) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && - (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); - } - - /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ - var isArray = Array.isArray; - - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - - /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, - * else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false - */ - function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); - } - - /** - * Checks if `value` is a buffer. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. - * @example - * - * _.isBuffer(new Buffer(2)); - * // => true - * - * _.isBuffer(new Uint8Array(2)); - * // => false - */ - var isBuffer = nativeIsBuffer || stubFalse; - - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ - function isFunction(value) { - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 8-9 which returns 'object' for typed array and other constructors. - var tag = isObject(value) ? objectToString.call(value) : ''; - return tag == funcTag || tag == genTag; - } - - /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This method is loosely based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false - * - * _.isLength('3'); - * // => false - */ - function isLength(value) { - return typeof value == 'number' && - value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - function isObject(value) { - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); - } - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - function isObjectLike(value) { - return !!value && typeof value == 'object'; - } - - /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * for more details. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ - function keys(object) { - return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); - } - - /** - * This method returns a new empty array. - * - * @static - * @memberOf _ - * @since 4.13.0 - * @category Util - * @returns {Array} Returns the new empty array. - * @example - * - * var arrays = _.times(2, _.stubArray); - * - * console.log(arrays); - * // => [[], []] - * - * console.log(arrays[0] === arrays[1]); - * // => false - */ - function stubArray() { - return []; - } - - /** - * This method returns `false`. - * - * @static - * @memberOf _ - * @since 4.13.0 - * @category Util - * @returns {boolean} Returns `false`. - * @example - * - * _.times(2, _.stubFalse); - * // => [false, false] - */ - function stubFalse() { - return false; - } - - module.exports = cloneDeep; - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],48:[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":49}],49:[function(require,module,exports){ -// created by @HenrikJoreteg - var prefix; - var version; - - if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { - prefix = 'moz'; - version = navigator.userAgent.match(/Firefox/) && parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); - } else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { - prefix = 'webkit'; - version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); - } - - 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:' && - ((prefix === 'webkit' && version >= 26) || - (prefix === 'moz' && version >= 33)) - var AudioContext = window.AudioContext || window.webkitAudioContext; - var videoEl = document.createElement('video'); - var supportVp8 = videoEl && videoEl.canPlayType && videoEl.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, - browserVersion: version, - 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 - }; - -},{}],50:[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; - -},{}],51:[function(require,module,exports){ - - /** - * HOP ref. - */ - - var has = Object.prototype.hasOwnProperty; - - /** - * Return own keys in `obj`. - * - * @param {Object} obj - * @return {Array} - * @api public - */ - - exports.keys = Object.keys || function(obj){ - var keys = []; - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - return keys; - }; - - /** - * Return own values in `obj`. - * - * @param {Object} obj - * @return {Array} - * @api public - */ - - exports.values = function(obj){ - var vals = []; - for (var key in obj) { - if (has.call(obj, key)) { - vals.push(obj[key]); - } - } - return vals; - }; - - /** - * Merge `b` into `a`. - * - * @param {Object} a - * @param {Object} b - * @return {Object} a - * @api public - */ - - exports.merge = function(a, b){ - for (var key in b) { - if (has.call(b, key)) { - a[key] = b[key]; - } - } - return a; - }; - - /** - * Return length of `obj`. - * - * @param {Object} obj - * @return {Number} - * @api public - */ - - exports.length = function(obj){ - return exports.keys(obj).length; - }; - - /** - * Check if `obj` is empty. - * - * @param {Object} obj - * @return {Boolean} - * @api public - */ - - exports.isEmpty = function(obj){ - return 0 == exports.length(obj); - }; -},{}],52:[function(require,module,exports){ - (function (global){ - /** - * JSON parse. - * - * @see Based on jQuery#parseJSON (MIT) and JSON2 - * @api private - */ - - var rvalidchars = /^[\],:{}\s]*$/; - var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; - var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; - var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g; - var rtrimLeft = /^\s+/; - var rtrimRight = /\s+$/; - - module.exports = function parsejson(data) { - if ('string' != typeof data || !data) { - return null; - } - - data = data.replace(rtrimLeft, '').replace(rtrimRight, ''); - - // Attempt to parse using the native JSON parser first - if (global.JSON && JSON.parse) { - return JSON.parse(data); - } - - if (rvalidchars.test(data.replace(rvalidescape, '@') - .replace(rvalidtokens, ']') - .replace(rvalidbraces, ''))) { - return (new Function('return ' + data))(); - } - }; - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],53:[function(require,module,exports){ - /** - * Compiles a querystring - * Returns string representation of the object - * - * @param {Object} - * @api private - */ - - exports.encode = function (obj) { - var str = ''; - - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - if (str.length) str += '&'; - str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); - } - } - - return str; - }; - - /** - * Parses a simple querystring into an object - * - * @param {String} qs - * @api private - */ - - exports.decode = function(qs){ - var qry = {}; - var pairs = qs.split('&'); - for (var i = 0, l = pairs.length; i < l; i++) { - var pair = pairs[i].split('='); - qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); - } - return qry; - }; - -},{}],54:[function(require,module,exports){ - /** - * Parses an URI - * - * @author Steven Levithan <stevenlevithan.com> (MIT license) - * @api private - */ - - var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - var parts = [ - 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host' - , 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' - ]; - - module.exports = function parseuri(str) { - var m = re.exec(str || '') - , uri = {} - , i = 14; - - while (i--) { - uri[parts[i]] = m[i] || ''; - } - - return uri; - }; - -},{}],55:[function(require,module,exports){ -// shim for using process in browser - var process = module.exports = {}; - -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. - - var cachedSetTimeout; - var cachedClearTimeout; - - function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); - } - function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); - } - (function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } - } ()) - function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } - - - } - function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } - - - - } - var queue = []; - var draining = false; - var currentQueue; - var queueIndex = -1; - - function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } - } - - function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); - } - - process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } - }; - -// v8 likes predictible objects - function Item(fun, array) { - this.fun = fun; - this.array = array; - } - Item.prototype.run = function () { - this.fun.apply(null, this.array); - }; - process.title = 'browser'; - process.browser = true; - process.env = {}; - process.argv = []; - process.version = ''; // empty string to avoid regexp issues - process.versions = {}; - - function noop() {} - - process.on = noop; - process.addListener = noop; - process.once = noop; - process.off = noop; - process.removeListener = noop; - process.removeAllListeners = noop; - process.emit = noop; - - process.binding = function (name) { - throw new Error('process.binding is not supported'); - }; - - process.cwd = function () { return '/' }; - process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); - }; - process.umask = function() { return 0; }; - -},{}],56:[function(require,module,exports){ - var util = require('util'); - var SJJ = require('sdp-jingle-json'); - var WildEmitter = require('wildemitter'); - var Peerconn = require('traceablepeerconnection'); - var adapter = require('webrtc-adapter'); - var cloneDeep = require('lodash.clonedeep'); - - function PeerConnection(config, constraints) { - var self = this; - var item; - WildEmitter.call(this); - - config = config || {}; - config.iceServers = config.iceServers || []; - - var detectedBrowser = adapter.browserDetails.browser; - - // make sure this only gets enabled in Google Chrome - // EXPERIMENTAL FLAG, might get removed without notice - this.enableChromeNativeSimulcast = false; - if (constraints && constraints.optional && - detectedBrowser === 'chrome' && - navigator.appVersion.match(/Chromium\//) === null) { - constraints.optional.forEach(function (constraint) { - if (constraint.enableChromeNativeSimulcast) { - self.enableChromeNativeSimulcast = true; - } - }); - } - - // EXPERIMENTAL FLAG, might get removed without notice - this.enableMultiStreamHacks = false; - if (constraints && constraints.optional && - detectedBrowser === 'chrome') { - constraints.optional.forEach(function (constraint) { - 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) { - 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) { - if (constraint.andyetBatchIce) { - self.batchIceCandidates = constraint.andyetBatchIce; - } - }); - } - this.batchedIceCandidates = []; - - // EXPERIMENTAL FLAG, might get removed without notice - // this attemps to strip out candidates with an already known foundation - // and type -- i.e. those which are gathered via the same TURN server - // but different transports (TURN udp, tcp and tls respectively) - if (constraints && constraints.optional && detectedBrowser === 'chrome') { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetFasterICE) { - self.eliminateDuplicateCandidates = constraint.andyetFasterICE; - } - }); - } - // EXPERIMENTAL FLAG, might get removed without notice - // when using a server such as the jitsi videobridge we don't need to signal - // our candidates - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetDontSignalCandidates) { - self.dontSignalCandidates = constraint.andyetDontSignalCandidates; - } - }); - } - - - // EXPERIMENTAL FLAG, might get removed without notice - this.assumeSetLocalSuccess = false; - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetAssumeSetLocalSuccess) { - self.assumeSetLocalSuccess = constraint.andyetAssumeSetLocalSuccess; - } - }); - } - - // EXPERIMENTAL FLAG, might get removed without notice - // working around https://bugzilla.mozilla.org/show_bug.cgi?id=1087551 - // pass in a timeout for this - if (detectedBrowser === 'firefox') { - if (constraints && constraints.optional) { - this.wtFirefox = 0; - constraints.optional.forEach(function (constraint) { - if (constraint.andyetFirefoxMakesMeSad) { - self.wtFirefox = constraint.andyetFirefoxMakesMeSad; - if (self.wtFirefox > 0) { - self.firefoxcandidatebuffer = []; - } - } - }); - } - } - - - 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, - sid: '', - isInitiator: true, - sdpSessionID: Date.now(), - useJingle: false - }; - - this.iceCredentials = { - local: {}, - remote: {} - }; - - // apply our config - for (item in config) { - this.config[item] = config[item]; - } - - if (this.config.debug) { - this.on('*', function () { - 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 = []; - - this._candidateBuffer = []; - } - - 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 = this.remoteDescription.contents.map(function (c) { return c.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; - var remoteContent = self.remoteDescription.contents.find(function (c) { - return c.name === content.name; - }); - - // process candidates as a callback, in case we need to - // update ufrag and pwd with offer/answer - var processCandidates = function () { - candidates.forEach( - function (candidate) { - var iceCandidate = SJJ.toCandidateSDP(candidate) + '\r\n'; - self.pc.addIceCandidate( - new RTCIceCandidate({ - candidate: iceCandidate, - sdpMLineIndex: mline, - sdpMid: mid - }), function () { - // well, this success callback is pretty meaningless - }, - function (err) { - self.emit('error', err); - } - ); - self._checkRemoteCandidate(iceCandidate); - }); - cb(); - }; - - if (self.iceCredentials.remote[content.name] && transport.ufrag && - self.iceCredentials.remote[content.name].ufrag !== transport.ufrag) { - if (remoteContent) { - remoteContent.transport.ufrag = transport.ufrag; - remoteContent.transport.pwd = transport.pwd; - var offer = { - type: 'offer', - jingle: self.remoteDescription - }; - offer.sdp = SJJ.toSessionSDP(offer.jingle, { - sid: self.config.sdpSessionID, - role: self._role(), - direction: 'incoming' - }); - self.pc.setRemoteDescription(new RTCSessionDescription(offer), - function () { - processCandidates(); - }, - function (err) { - self.emit('error', err); - } - ); - } else { - self.emit('error', 'ice restart failed to find matching content'); - } - } else { - processCandidates(); - } - }); - } 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; - } - - if (this.wtFirefox && this.firefoxcandidatebuffer !== null) { - // we cant add this yet due to https://bugzilla.mozilla.org/show_bug.cgi?id=1087551 - if (this.pc.localDescription && this.pc.localDescription.type === 'offer') { - this.firefoxcandidatebuffer.push(update.candidate); - return cb(); - } - } - - self.pc.addIceCandidate( - new RTCIceCandidate(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 ? constraints : { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 - }; - 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... - var expandedOffer = { - type: 'offer', - sdp: offer.sdp - }; - if (self.assumeSetLocalSuccess) { - self.emit('offer', expandedOffer); - cb(null, expandedOffer); - } - self._candidateBuffer = []; - self.pc.setLocalDescription(offer, - function () { - var jingle; - 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 - jingle.contents.forEach(function (content) { - var transport = content.transport || {}; - if (transport.ufrag) { - self.iceCredentials.local[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.application.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.application.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.application.sources = sources; - } - } - }); - } - if (self.restrictBandwidth > 0) { - if (offer.jingle.contents.length >= 2 && offer.jingle.contents[1].name === 'video') { - var content = offer.jingle.contents[1]; - var hasBw = content.application && content.application.bandwidth && content.application.bandwidth.bandwidth; - if (!hasBw) { - offer.jingle.contents[1].application.bandwidth = { type: 'AS', bandwidth: self.restrictBandwidth.toString() }; - offer.sdp = SJJ.toSessionSDP(offer.jingle, { - sid: self.config.sdpSessionID, - role: self._role(), - direction: 'outgoing' - }); - } - } - } - // Save ICE credentials - offer.jingle.contents.forEach(function (content) { - var transport = content.transport || {}; - if (transport.ufrag) { - self.iceCredentials.remote[content.name] = { - ufrag: transport.ufrag, - pwd: transport.pwd - }; - } - }); - 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 RTCSessionDescription(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 hasConstraints = arguments.length === 2; - var callback = hasConstraints ? cb : constraints; - var mediaConstraints = hasConstraints && constraints ? 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; - - // Save ICE credentials - answer.jingle.contents.forEach(function (content) { - var transport = content.transport || {}; - if (transport.ufrag) { - self.iceCredentials.remote[content.name] = { - ufrag: transport.ufrag, - pwd: transport.pwd - }; - } - }); - } - answer.sdp.split('\r\n').forEach(function (line) { - if (line.indexOf('a=candidate:') === 0) { - self._checkRemoteCandidate(line); - } - }); - self.pc.setRemoteDescription( - new RTCSessionDescription(answer), - function () { - if (self.wtFirefox) { - window.setTimeout(function () { - self.firefoxcandidatebuffer.forEach(function (candidate) { - // add candidates later - self.pc.addIceCandidate( - new RTCIceCandidate(candidate), - function () { }, - function (err) { - self.emit('error', err); - } - ); - self._checkRemoteCandidate(candidate.candidate); - }); - self.firefoxcandidatebuffer = null; - }, self.wtFirefox); - } - 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 = []; - 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 groups = answer.jingle.contents[1].application.sourceGroups || []; - var hasSim = false; - groups.forEach(function (group) { - if (group.semantics == 'SIM') hasSim = true; - }); - if (!hasSim && - answer.jingle.contents[1].application.sources.length) { - var newssrc = JSON.parse(JSON.stringify(answer.jingle.contents[1].application.sources[0])); - newssrc.ssrc = '' + Math.floor(Math.random() * 0xffffffff); // FIXME: look for conflicts - answer.jingle.contents[1].application.sources.push(newssrc); - - sim.push(answer.jingle.contents[1].application.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].application.sources.push(rtxssrc); - groups.push({ - semantics: 'FID', - sources: [newssrc.ssrc, rtxssrc.ssrc] - }); - - answer.jingle.contents[1].application.sourceGroups = groups; - answer.sdp = SJJ.toSessionSDP(answer.jingle, { - sid: self.config.sdpSessionID, - role: self._role(), - direction: 'outgoing' - }); - } - } - } - var expandedAnswer = { - type: 'answer', - sdp: answer.sdp - }; - if (self.assumeSetLocalSuccess) { - // not safe to do when doing simulcast mangling - var copy = cloneDeep(expandedAnswer); - self.emit('answer', copy); - cb(null, copy); - } - self._candidateBuffer = []; - self.pc.setLocalDescription(answer, - function () { - 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' - }); - } - expandedAnswer.jingle.contents[1].application.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) { - var copy = cloneDeep(expandedAnswer); - self.emit('answer', copy); - cb(null, copy); - } - }, - 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) { - if (this.dontSignalCandidates) return; - var ice = event.candidate; - - var expandedCandidate = { - candidate: { - candidate: ice.candidate, - sdpMid: ice.sdpMid, - sdpMLineIndex: ice.sdpMLineIndex - } - }; - this._checkLocalCandidate(ice.candidate); - - var cand = SJJ.toCandidateJSON(ice.candidate); - - var already; - var idx; - if (this.eliminateDuplicateCandidates && cand.type === 'relay') { - // drop candidates with same foundation, component - // take local type pref into account so we don't ignore udp - // ones when we know about a TCP one. unlikely but... - already = this._candidateBuffer.filter( - function (c) { - return c.type === 'relay'; - }).map(function (c) { - return c.foundation + ':' + c.component; - } - ); - idx = already.indexOf(cand.foundation + ':' + cand.component); - // remember: local type pref of udp is 0, tcp 1, tls 2 - if (idx > -1 && ((cand.priority >> 24) >= (already[idx].priority >> 24))) { - // drop it, same foundation with higher (worse) type pref - return; - } - } - if (this.config.bundlePolicy === 'max-bundle') { - // drop candidates which are duplicate for audio/video/data - // duplicate means same host/port but different sdpMid - already = this._candidateBuffer.filter( - function (c) { - return cand.type === c.type; - }).map(function (cand) { - return cand.address + ':' + cand.port; - } - ); - idx = already.indexOf(cand.address + ':' + cand.port); - if (idx > -1) return; - } - // also drop rtcp candidates since we know the peer supports RTCP-MUX - // this is a workaround until browsers implement this natively - if (this.config.rtcpMuxPolicy === 'require' && cand.component === '2') { - return; - } - this._candidateBuffer.push(cand); - - if (self.config.useJingle) { - if (!ice.sdpMid) { // firefox doesn't set this - if (self.pc.remoteDescription && self.pc.remoteDescription.type === 'offer') { - // preserve name from remote - ice.sdpMid = self.remoteDescription.contents[ice.sdpMLineIndex].name; - } else { - ice.sdpMid = self.localDescription.contents[ice.sdpMLineIndex].name; - } - } - if (!self.iceCredentials.local[ice.sdpMid]) { - var jingle = SJJ.toSessionJSON(self.pc.localDescription.sdp, { - role: self._role(), - direction: 'outgoing' - }); - jingle.contents.forEach(function (content) { - var transport = content.transport || {}; - if (transport.ufrag) { - self.iceCredentials.local[content.name] = { - ufrag: transport.ufrag, - pwd: transport.pwd - }; - } - }); - } - expandedCandidate.jingle = { - contents: [{ - name: ice.sdpMid, - creator: self._role(), - transport: { - transportType: 'iceUdp', - ufrag: self.iceCredentials.local[ice.sdpMid].ufrag, - pwd: self.iceCredentials.local[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; - }; - - PeerConnection.prototype.getStats = function (cb) { - this.pc.getStats(null, - function (res) { - cb(null, res); - }, - function (err) { - cb(err); - } - ); - }; - - module.exports = PeerConnection; - -},{"lodash.clonedeep":47,"sdp-jingle-json":57,"traceablepeerconnection":76,"util":89,"webrtc-adapter":90,"wildemitter":100}],57:[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":60,"./lib/tosdp":61}],58:[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; - }; - - exports.msid = function (line) { - var data = line.substr(7); - var parts = data.split(' '); - return { - msid: data, - mslabel: parts[0], - label: parts[1] - }; - }; - -},{}],59:[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' - } - } - }; - -},{}],60:[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, - application: { - applicationType: 'rtp', - media: mline.media, - payloads: [], - encryption: [], - feedback: [], - headerExtensions: [] - }, - transport: { - transportType: '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.application = { - applicationType: 'datachannel' - }; - content.transport.sctp = []; - } - var desc = content.application; - 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.applicationType == '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); - var sources = desc.sources = parsers.sources(ssrcLines || []); - - var msidLine = parsers.findLine('a=msid:', lines); - if (msidLine) { - var msid = parsers.msid(msidLine); - ['msid', 'mslabel', 'label'].forEach(function (key) { - for (var i = 0; i < sources.length; i++) { - var found = false; - for (var j = 0; j < sources[i].parameters.length; j++) { - if (sources[i].parameters[j].key === key) { - found = true; - } - } - if (!found) { - sources[i].parameters.push({ key: key, value: msid[key] }); - } - } - }); - } - - 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.applicationType == '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":58,"./senders":59}],61:[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 contents = session.contents || []; - var hasSources = false; - contents.forEach(function (content) { - if (content.application.sources && - content.application.sources.length) { - hasSources = true; - } - }); - - if (hasSources) { - sdp.push('a=msid-semantic: WMS *'); - } - - var groups = session.groups || []; - groups.forEach(function (group) { - sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' ')); - }); - - - 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.application; - var transport = content.transport; - var payloads = desc.payloads || []; - var fingerprints = (transport && transport.fingerprints) || []; - - var mline = []; - if (desc.applicationType == '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 (fingerprints.length > 0) { - mline.push('UDP/TLS/RTP/SAVPF'); - } else if (desc.encryption && desc.encryption.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.applicationType == '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.applicationType == 'rtp') { - sdp.push('a=' + (SENDERS[role][direction][content.senders] || 'sendrecv')); - } - sdp.push('a=mid:' + content.name); - - if (desc.sources && desc.sources.length) { - (desc.sources[0].parameters || []).forEach(function (param) { - if (param.key === 'msid') { - sdp.push('a=msid:' + param.value); - } - }); - } - - 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":59}],62:[function(require,module,exports){ - /* eslint-env node */ - 'use strict'; - -// SDP helpers. - var SDPUtils = {}; - -// Generate an alphanumeric identifier for cname or mids. -// TODO: use UUIDs instead? https://gist.github.com/jed/982883 - SDPUtils.generateIdentifier = function() { - return Math.random().toString(36).substr(2, 10); - }; - -// The RTCP CNAME used by all peerconnections from the same JS. - SDPUtils.localCName = SDPUtils.generateIdentifier(); - -// Splits SDP into lines, dealing with both CRLF and LF. - SDPUtils.splitLines = function(blob) { - return blob.trim().split('\n').map(function(line) { - return line.trim(); - }); - }; -// Splits SDP into sessionpart and mediasections. Ensures CRLF. - SDPUtils.splitSections = function(blob) { - var parts = blob.split('\nm='); - return parts.map(function(part, index) { - return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; - }); - }; - -// Returns lines that start with a certain prefix. - SDPUtils.matchPrefix = function(blob, prefix) { - return SDPUtils.splitLines(blob).filter(function(line) { - return line.indexOf(prefix) === 0; - }); - }; - -// Parses an ICE candidate line. Sample input: -// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 -// rport 55996" - SDPUtils.parseCandidate = function(line) { - var parts; - // Parse both variants. - if (line.indexOf('a=candidate:') === 0) { - parts = line.substring(12).split(' '); - } else { - parts = line.substring(10).split(' '); - } - - var candidate = { - foundation: parts[0], - component: parts[1], - protocol: parts[2].toLowerCase(), - priority: parseInt(parts[3], 10), - ip: parts[4], - port: parseInt(parts[5], 10), - // skip parts[6] == 'typ' - type: parts[7] - }; - - for (var i = 8; i < parts.length; i += 2) { - switch (parts[i]) { - case 'raddr': - candidate.relatedAddress = parts[i + 1]; - break; - case 'rport': - candidate.relatedPort = parseInt(parts[i + 1], 10); - break; - case 'tcptype': - candidate.tcpType = parts[i + 1]; - break; - default: // Unknown extensions are silently ignored. - break; - } - } - return candidate; - }; - -// Translates a candidate object into SDP candidate attribute. - SDPUtils.writeCandidate = 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 !== 'host' && candidate.relatedAddress && - candidate.relatedPort) { - sdp.push('raddr'); - sdp.push(candidate.relatedAddress); // was: relAddr - sdp.push('rport'); - sdp.push(candidate.relatedPort); // was: relPort - } - if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { - sdp.push('tcptype'); - sdp.push(candidate.tcpType); - } - return 'candidate:' + sdp.join(' '); - }; - -// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: -// a=rtpmap:111 opus/48000/2 - SDPUtils.parseRtpMap = function(line) { - var parts = line.substr(9).split(' '); - var parsed = { - payloadType: parseInt(parts.shift(), 10) // was: id - }; - - parts = parts[0].split('/'); - - parsed.name = parts[0]; - parsed.clockRate = parseInt(parts[1], 10); // was: clockrate - // was: channels - parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; - return parsed; - }; - -// Generate an a=rtpmap line from RTCRtpCodecCapability or -// RTCRtpCodecParameters. - SDPUtils.writeRtpMap = function(codec) { - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + - (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; - }; - -// Parses an a=extmap line (headerextension from RFC 5285). Sample input: -// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset - SDPUtils.parseExtmap = function(line) { - var parts = line.substr(9).split(' '); - return { - id: parseInt(parts[0], 10), - uri: parts[1] - }; - }; - -// Generates a=extmap line from RTCRtpHeaderExtensionParameters or -// RTCRtpHeaderExtension. - SDPUtils.writeExtmap = function(headerExtension) { - return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + - ' ' + headerExtension.uri + '\r\n'; - }; - -// Parses an ftmp line, returns dictionary. Sample input: -// a=fmtp:96 vbr=on;cng=on -// Also deals with vbr=on; cng=on - SDPUtils.parseFmtp = function(line) { - var parsed = {}; - var kv; - var parts = line.substr(line.indexOf(' ') + 1).split(';'); - for (var j = 0; j < parts.length; j++) { - kv = parts[j].trim().split('='); - parsed[kv[0].trim()] = kv[1]; - } - return parsed; - }; - -// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. - SDPUtils.writeFmtp = function(codec) { - var line = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.parameters && Object.keys(codec.parameters).length) { - var params = []; - Object.keys(codec.parameters).forEach(function(param) { - params.push(param + '=' + codec.parameters[param]); - }); - line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; - } - return line; - }; - -// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: -// a=rtcp-fb:98 nack rpsi - SDPUtils.parseRtcpFb = function(line) { - var parts = line.substr(line.indexOf(' ') + 1).split(' '); - return { - type: parts.shift(), - parameter: parts.join(' ') - }; - }; -// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. - SDPUtils.writeRtcpFb = function(codec) { - var lines = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.rtcpFeedback && codec.rtcpFeedback.length) { - // FIXME: special handling for trr-int? - codec.rtcpFeedback.forEach(function(fb) { - lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + - (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + - '\r\n'; - }); - } - return lines; - }; - -// Parses an RFC 5576 ssrc media attribute. Sample input: -// a=ssrc:3735928559 cname:something - SDPUtils.parseSsrcMedia = function(line) { - var sp = line.indexOf(' '); - var parts = { - ssrc: parseInt(line.substr(7, sp - 7), 10) - }; - var colon = line.indexOf(':', sp); - if (colon > -1) { - parts.attribute = line.substr(sp + 1, colon - sp - 1); - parts.value = line.substr(colon + 1); - } else { - parts.attribute = line.substr(sp + 1); - } - return parts; - }; - -// Extracts DTLS parameters from SDP media section or sessionpart. -// FIXME: for consistency with other functions this should only -// get the fingerprint line as input. See also getIceParameters. - SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { - var lines = SDPUtils.splitLines(mediaSection); - // Search in session part, too. - lines = lines.concat(SDPUtils.splitLines(sessionpart)); - var fpLine = lines.filter(function(line) { - return line.indexOf('a=fingerprint:') === 0; - })[0].substr(14); - // Note: a=setup line is ignored since we use the 'auto' role. - var dtlsParameters = { - role: 'auto', - fingerprints: [{ - algorithm: fpLine.split(' ')[0], - value: fpLine.split(' ')[1] - }] - }; - return dtlsParameters; - }; - -// Serializes DTLS parameters to SDP. - SDPUtils.writeDtlsParameters = function(params, setupType) { - var sdp = 'a=setup:' + setupType + '\r\n'; - params.fingerprints.forEach(function(fp) { - sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; - }); - return sdp; - }; -// Parses ICE information from SDP media section or sessionpart. -// FIXME: for consistency with other functions this should only -// get the ice-ufrag and ice-pwd lines as input. - SDPUtils.getIceParameters = function(mediaSection, sessionpart) { - var lines = SDPUtils.splitLines(mediaSection); - // Search in session part, too. - lines = lines.concat(SDPUtils.splitLines(sessionpart)); - var iceParameters = { - usernameFragment: lines.filter(function(line) { - return line.indexOf('a=ice-ufrag:') === 0; - })[0].substr(12), - password: lines.filter(function(line) { - return line.indexOf('a=ice-pwd:') === 0; - })[0].substr(10) - }; - return iceParameters; - }; - -// Serializes ICE parameters to SDP. - SDPUtils.writeIceParameters = function(params) { - return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + - 'a=ice-pwd:' + params.password + '\r\n'; - }; - -// Parses the SDP media section and returns RTCRtpParameters. - SDPUtils.parseRtpParameters = function(mediaSection) { - var description = { - codecs: [], - headerExtensions: [], - fecMechanisms: [], - rtcp: [] - }; - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].split(' '); - for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] - var pt = mline[i]; - var rtpmapline = SDPUtils.matchPrefix( - mediaSection, 'a=rtpmap:' + pt + ' ')[0]; - if (rtpmapline) { - var codec = SDPUtils.parseRtpMap(rtpmapline); - var fmtps = SDPUtils.matchPrefix( - mediaSection, 'a=fmtp:' + pt + ' '); - // Only the first a=fmtp:<pt> is considered. - codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; - codec.rtcpFeedback = SDPUtils.matchPrefix( - mediaSection, 'a=rtcp-fb:' + pt + ' ') - .map(SDPUtils.parseRtcpFb); - description.codecs.push(codec); - // parse FEC mechanisms from rtpmap lines. - switch (codec.name.toUpperCase()) { - case 'RED': - case 'ULPFEC': - description.fecMechanisms.push(codec.name.toUpperCase()); - break; - default: // only RED and ULPFEC are recognized as FEC mechanisms. - break; - } - } - } - SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { - description.headerExtensions.push(SDPUtils.parseExtmap(line)); - }); - // FIXME: parse rtcp. - return description; - }; - -// Generates parts of the SDP media section describing the capabilities / -// parameters. - SDPUtils.writeRtpDescription = function(kind, caps) { - var sdp = ''; - - // Build the mline. - sdp += 'm=' + kind + ' '; - sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. - sdp += ' UDP/TLS/RTP/SAVPF '; - sdp += caps.codecs.map(function(codec) { - if (codec.preferredPayloadType !== undefined) { - return codec.preferredPayloadType; - } - return codec.payloadType; - }).join(' ') + '\r\n'; - - sdp += 'c=IN IP4 0.0.0.0\r\n'; - sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; - - // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. - caps.codecs.forEach(function(codec) { - sdp += SDPUtils.writeRtpMap(codec); - sdp += SDPUtils.writeFmtp(codec); - sdp += SDPUtils.writeRtcpFb(codec); - }); - var maxptime = 0; - caps.codecs.forEach(function(codec) { - if (codec.maxptime > maxptime) { - maxptime = codec.maxptime; - } - }); - if (maxptime > 0) { - sdp += 'a=maxptime:' + maxptime + '\r\n'; - } - sdp += 'a=rtcp-mux\r\n'; - - caps.headerExtensions.forEach(function(extension) { - sdp += SDPUtils.writeExtmap(extension); - }); - // FIXME: write fecMechanisms. - return sdp; - }; - -// Parses the SDP media section and returns an array of -// RTCRtpEncodingParameters. - SDPUtils.parseRtpEncodingParameters = function(mediaSection) { - var encodingParameters = []; - var description = SDPUtils.parseRtpParameters(mediaSection); - var hasRed = description.fecMechanisms.indexOf('RED') !== -1; - var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; - - // filter a=ssrc:... cname:, ignore PlanB-msid - var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') - .map(function(line) { - return SDPUtils.parseSsrcMedia(line); - }) - .filter(function(parts) { - return parts.attribute === 'cname'; - }); - var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; - var secondarySsrc; - - var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') - .map(function(line) { - var parts = line.split(' '); - parts.shift(); - return parts.map(function(part) { - return parseInt(part, 10); - }); - }); - if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { - secondarySsrc = flows[0][1]; - } - - description.codecs.forEach(function(codec) { - if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { - var encParam = { - ssrc: primarySsrc, - codecPayloadType: parseInt(codec.parameters.apt, 10), - rtx: { - ssrc: secondarySsrc - } - }; - encodingParameters.push(encParam); - if (hasRed) { - encParam = JSON.parse(JSON.stringify(encParam)); - encParam.fec = { - ssrc: secondarySsrc, - mechanism: hasUlpfec ? 'red+ulpfec' : 'red' - }; - encodingParameters.push(encParam); - } - } - }); - if (encodingParameters.length === 0 && primarySsrc) { - encodingParameters.push({ - ssrc: primarySsrc - }); - } - - // we support both b=AS and b=TIAS but interpret AS as TIAS. - var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); - if (bandwidth.length) { - if (bandwidth[0].indexOf('b=TIAS:') === 0) { - bandwidth = parseInt(bandwidth[0].substr(7), 10); - } else if (bandwidth[0].indexOf('b=AS:') === 0) { - bandwidth = parseInt(bandwidth[0].substr(5), 10); - } - encodingParameters.forEach(function(params) { - params.maxBitrate = bandwidth; - }); - } - return encodingParameters; - }; - - SDPUtils.writeSessionBoilerplate = function() { - // FIXME: sess-id should be an NTP timestamp. - return 'v=0\r\n' + - 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + - 's=-\r\n' + - 't=0 0\r\n'; - }; - - SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { - var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); - - // Map ICE parameters (ufrag, pwd) to SDP. - sdp += SDPUtils.writeIceParameters( - transceiver.iceGatherer.getLocalParameters()); - - // Map DTLS parameters to SDP. - sdp += SDPUtils.writeDtlsParameters( - transceiver.dtlsTransport.getLocalParameters(), - type === 'offer' ? 'actpass' : 'active'); - - sdp += 'a=mid:' + transceiver.mid + '\r\n'; - - if (transceiver.rtpSender && transceiver.rtpReceiver) { - sdp += 'a=sendrecv\r\n'; - } else if (transceiver.rtpSender) { - sdp += 'a=sendonly\r\n'; - } else if (transceiver.rtpReceiver) { - sdp += 'a=recvonly\r\n'; - } else { - sdp += 'a=inactive\r\n'; - } - - // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. - if (transceiver.rtpSender) { - var msid = 'msid:' + stream.id + ' ' + - transceiver.rtpSender.track.id + '\r\n'; - sdp += 'a=' + msid; - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' ' + msid; - if (transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' ' + msid; - sdp += 'a=ssrc-group:FID ' + - transceiver.sendEncodingParameters[0].ssrc + ' ' + - transceiver.sendEncodingParameters[0].rtx.ssrc + - '\r\n'; - } - } - // FIXME: this should be written by writeRtpDescription. - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - } - return sdp; - }; - -// Gets the direction from the mediaSection or the sessionpart. - SDPUtils.getDirection = function(mediaSection, sessionpart) { - // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. - var lines = SDPUtils.splitLines(mediaSection); - for (var i = 0; i < lines.length; i++) { - switch (lines[i]) { - case 'a=sendrecv': - case 'a=sendonly': - case 'a=recvonly': - case 'a=inactive': - return lines[i].substr(2); - default: - // FIXME: What should happen here? - } - } - if (sessionpart) { - return SDPUtils.getDirection(sessionpart); - } - return 'sendrecv'; - }; - -// Expose public methods. - module.exports = SDPUtils; - -},{}],63:[function(require,module,exports){ - - module.exports = require('./lib/'); - -},{"./lib/":64}],64:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var url = require('./url'); - var parser = require('socket.io-parser'); - var Manager = require('./manager'); - var debug = require('debug')('socket.io-client'); - - /** - * Module exports. - */ - - module.exports = exports = lookup; - - /** - * Managers cache. - */ - - var cache = exports.managers = {}; - - /** - * Looks up an existing `Manager` for multiplexing. - * If the user summons: - * - * `io('http://localhost/a');` - * `io('http://localhost/b');` - * - * We reuse the existing instance based on same scheme/port/host, - * and we initialize sockets for each namespace. - * - * @api public - */ - - function lookup(uri, opts) { - if (typeof uri == 'object') { - opts = uri; - uri = undefined; - } - - opts = opts || {}; - - var parsed = url(uri); - var source = parsed.source; - var id = parsed.id; - var io; - - if (opts.forceNew || opts['force new connection'] || false === opts.multiplex) { - debug('ignoring socket cache for %s', source); - io = Manager(source, opts); - } else { - if (!cache[id]) { - debug('new io instance for %s', source); - cache[id] = Manager(source, opts); - } - io = cache[id]; - } - - return io.socket(parsed.path); - } - - /** - * Protocol version. - * - * @api public - */ - - exports.protocol = parser.protocol; - - /** - * `connect`. - * - * @param {String} uri - * @api public - */ - - exports.connect = lookup; - - /** - * Expose constructors for standalone build. - * - * @api public - */ - - exports.Manager = require('./manager'); - exports.Socket = require('./socket'); - -},{"./manager":65,"./socket":67,"./url":68,"debug":69,"socket.io-parser":71}],65:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var url = require('./url'); - var eio = require('engine.io-client'); - var Socket = require('./socket'); - var Emitter = require('component-emitter'); - var parser = require('socket.io-parser'); - var on = require('./on'); - var bind = require('component-bind'); - var object = require('object-component'); - var debug = require('debug')('socket.io-client:manager'); - var indexOf = require('indexof'); - var Backoff = require('backo2'); - - /** - * Module exports - */ - - module.exports = Manager; - - /** - * `Manager` constructor. - * - * @param {String} engine instance or engine uri/opts - * @param {Object} options - * @api public - */ - - function Manager(uri, opts){ - if (!(this instanceof Manager)) return new Manager(uri, opts); - if (uri && ('object' == typeof uri)) { - opts = uri; - uri = undefined; - } - opts = opts || {}; - - opts.path = opts.path || '/socket.io'; - this.nsps = {}; - this.subs = []; - this.opts = opts; - this.reconnection(opts.reconnection !== false); - this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); - this.reconnectionDelay(opts.reconnectionDelay || 1000); - this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); - this.randomizationFactor(opts.randomizationFactor || 0.5); - this.backoff = new Backoff({ - min: this.reconnectionDelay(), - max: this.reconnectionDelayMax(), - jitter: this.randomizationFactor() - }); - this.timeout(null == opts.timeout ? 20000 : opts.timeout); - this.readyState = 'closed'; - this.uri = uri; - this.connected = []; - this.encoding = false; - this.packetBuffer = []; - this.encoder = new parser.Encoder(); - this.decoder = new parser.Decoder(); - this.autoConnect = opts.autoConnect !== false; - if (this.autoConnect) this.open(); - } - - /** - * Propagate given event to sockets and emit on `this` - * - * @api private - */ - - Manager.prototype.emitAll = function() { - this.emit.apply(this, arguments); - for (var nsp in this.nsps) { - this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); - } - }; - - /** - * Update `socket.id` of all sockets - * - * @api private - */ - - Manager.prototype.updateSocketIds = function(){ - for (var nsp in this.nsps) { - this.nsps[nsp].id = this.engine.id; - } - }; - - /** - * Mix in `Emitter`. - */ - - Emitter(Manager.prototype); - - /** - * Sets the `reconnection` config. - * - * @param {Boolean} true/false if it should automatically reconnect - * @return {Manager} self or value - * @api public - */ - - Manager.prototype.reconnection = function(v){ - if (!arguments.length) return this._reconnection; - this._reconnection = !!v; - return this; - }; - - /** - * Sets the reconnection attempts config. - * - * @param {Number} max reconnection attempts before giving up - * @return {Manager} self or value - * @api public - */ - - Manager.prototype.reconnectionAttempts = function(v){ - if (!arguments.length) return this._reconnectionAttempts; - this._reconnectionAttempts = v; - return this; - }; - - /** - * Sets the delay between reconnections. - * - * @param {Number} delay - * @return {Manager} self or value - * @api public - */ - - Manager.prototype.reconnectionDelay = function(v){ - if (!arguments.length) return this._reconnectionDelay; - this._reconnectionDelay = v; - this.backoff && this.backoff.setMin(v); - return this; - }; - - Manager.prototype.randomizationFactor = function(v){ - if (!arguments.length) return this._randomizationFactor; - this._randomizationFactor = v; - this.backoff && this.backoff.setJitter(v); - return this; - }; - - /** - * Sets the maximum delay between reconnections. - * - * @param {Number} delay - * @return {Manager} self or value - * @api public - */ - - Manager.prototype.reconnectionDelayMax = function(v){ - if (!arguments.length) return this._reconnectionDelayMax; - this._reconnectionDelayMax = v; - this.backoff && this.backoff.setMax(v); - return this; - }; - - /** - * Sets the connection timeout. `false` to disable - * - * @return {Manager} self or value - * @api public - */ - - Manager.prototype.timeout = function(v){ - if (!arguments.length) return this._timeout; - this._timeout = v; - return this; - }; - - /** - * Starts trying to reconnect if reconnection is enabled and we have not - * started reconnecting yet - * - * @api private - */ - - Manager.prototype.maybeReconnectOnOpen = function() { - // Only try to reconnect if it's the first time we're connecting - if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { - // keeps reconnection from firing twice for the same reconnection loop - this.reconnect(); - } - }; - - - /** - * Sets the current transport `socket`. - * - * @param {Function} optional, callback - * @return {Manager} self - * @api public - */ - - Manager.prototype.open = - Manager.prototype.connect = function(fn){ - debug('readyState %s', this.readyState); - if (~this.readyState.indexOf('open')) return this; - - debug('opening %s', this.uri); - this.engine = eio(this.uri, this.opts); - var socket = this.engine; - var self = this; - this.readyState = 'opening'; - this.skipReconnect = false; - - // emit `open` - var openSub = on(socket, 'open', function() { - self.onopen(); - fn && fn(); - }); - - // emit `connect_error` - var errorSub = on(socket, 'error', function(data){ - debug('connect_error'); - self.cleanup(); - self.readyState = 'closed'; - self.emitAll('connect_error', data); - if (fn) { - var err = new Error('Connection error'); - err.data = data; - fn(err); - } else { - // Only do this if there is no fn to handle the error - self.maybeReconnectOnOpen(); - } - }); - - // emit `connect_timeout` - if (false !== this._timeout) { - var timeout = this._timeout; - debug('connect attempt will timeout after %d', timeout); - - // set timer - var timer = setTimeout(function(){ - debug('connect attempt timed out after %d', timeout); - openSub.destroy(); - socket.close(); - socket.emit('error', 'timeout'); - self.emitAll('connect_timeout', timeout); - }, timeout); - - this.subs.push({ - destroy: function(){ - clearTimeout(timer); - } - }); - } - - this.subs.push(openSub); - this.subs.push(errorSub); - - return this; - }; - - /** - * Called upon transport open. - * - * @api private - */ - - Manager.prototype.onopen = function(){ - debug('open'); - - // clear old subs - this.cleanup(); - - // mark as open - this.readyState = 'open'; - this.emit('open'); - - // add new subs - var socket = this.engine; - this.subs.push(on(socket, 'data', bind(this, 'ondata'))); - this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); - this.subs.push(on(socket, 'error', bind(this, 'onerror'))); - this.subs.push(on(socket, 'close', bind(this, 'onclose'))); - }; - - /** - * Called with data. - * - * @api private - */ - - Manager.prototype.ondata = function(data){ - this.decoder.add(data); - }; - - /** - * Called when parser fully decodes a packet. - * - * @api private - */ - - Manager.prototype.ondecoded = function(packet) { - this.emit('packet', packet); - }; - - /** - * Called upon socket error. - * - * @api private - */ - - Manager.prototype.onerror = function(err){ - debug('error', err); - this.emitAll('error', err); - }; - - /** - * Creates a new socket for the given `nsp`. - * - * @return {Socket} - * @api public - */ - - Manager.prototype.socket = function(nsp){ - var socket = this.nsps[nsp]; - if (!socket) { - socket = new Socket(this, nsp); - this.nsps[nsp] = socket; - var self = this; - socket.on('connect', function(){ - socket.id = self.engine.id; - if (!~indexOf(self.connected, socket)) { - self.connected.push(socket); - } - }); - } - return socket; - }; - - /** - * Called upon a socket close. - * - * @param {Socket} socket - */ - - Manager.prototype.destroy = function(socket){ - var index = indexOf(this.connected, socket); - if (~index) this.connected.splice(index, 1); - if (this.connected.length) return; - - this.close(); - }; - - /** - * Writes a packet. - * - * @param {Object} packet - * @api private - */ - - Manager.prototype.packet = function(packet){ - debug('writing packet %j', packet); - var self = this; - - if (!self.encoding) { - // encode, then write to engine with result - self.encoding = true; - this.encoder.encode(packet, function(encodedPackets) { - for (var i = 0; i < encodedPackets.length; i++) { - self.engine.write(encodedPackets[i]); - } - self.encoding = false; - self.processPacketQueue(); - }); - } else { // add packet to the queue - self.packetBuffer.push(packet); - } - }; - - /** - * If packet buffer is non-empty, begins encoding the - * next packet in line. - * - * @api private - */ - - Manager.prototype.processPacketQueue = function() { - if (this.packetBuffer.length > 0 && !this.encoding) { - var pack = this.packetBuffer.shift(); - this.packet(pack); - } - }; - - /** - * Clean up transport subscriptions and packet buffer. - * - * @api private - */ - - Manager.prototype.cleanup = function(){ - var sub; - while (sub = this.subs.shift()) sub.destroy(); - - this.packetBuffer = []; - this.encoding = false; - - this.decoder.destroy(); - }; - - /** - * Close the current socket. - * - * @api private - */ - - Manager.prototype.close = - Manager.prototype.disconnect = function(){ - this.skipReconnect = true; - this.backoff.reset(); - this.readyState = 'closed'; - this.engine && this.engine.close(); - }; - - /** - * Called upon engine close. - * - * @api private - */ - - Manager.prototype.onclose = function(reason){ - debug('close'); - this.cleanup(); - this.backoff.reset(); - this.readyState = 'closed'; - this.emit('close', reason); - if (this._reconnection && !this.skipReconnect) { - this.reconnect(); - } - }; - - /** - * Attempt a reconnection. - * - * @api private - */ - - Manager.prototype.reconnect = function(){ - if (this.reconnecting || this.skipReconnect) return this; - - var self = this; - - if (this.backoff.attempts >= this._reconnectionAttempts) { - debug('reconnect failed'); - this.backoff.reset(); - this.emitAll('reconnect_failed'); - this.reconnecting = false; - } else { - var delay = this.backoff.duration(); - debug('will wait %dms before reconnect attempt', delay); - - this.reconnecting = true; - var timer = setTimeout(function(){ - if (self.skipReconnect) return; - - debug('attempting reconnect'); - self.emitAll('reconnect_attempt', self.backoff.attempts); - self.emitAll('reconnecting', self.backoff.attempts); - - // check again for the case socket closed in above events - if (self.skipReconnect) return; - - self.open(function(err){ - if (err) { - debug('reconnect attempt error'); - self.reconnecting = false; - self.reconnect(); - self.emitAll('reconnect_error', err.data); - } else { - debug('reconnect success'); - self.onreconnect(); - } - }); - }, delay); - - this.subs.push({ - destroy: function(){ - clearTimeout(timer); - } - }); - } - }; - - /** - * Called upon successful reconnect. - * - * @api private - */ - - Manager.prototype.onreconnect = function(){ - var attempt = this.backoff.attempts; - this.reconnecting = false; - this.backoff.reset(); - this.updateSocketIds(); - this.emitAll('reconnect', attempt); - }; - -},{"./on":66,"./socket":67,"./url":68,"backo2":4,"component-bind":7,"component-emitter":8,"debug":69,"engine.io-client":10,"indexof":44,"object-component":51,"socket.io-parser":71}],66:[function(require,module,exports){ - - /** - * Module exports. - */ - - module.exports = on; - - /** - * Helper for subscriptions. - * - * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` - * @param {String} event name - * @param {Function} callback - * @api public - */ - - function on(obj, ev, fn) { - obj.on(ev, fn); - return { - destroy: function(){ - obj.removeListener(ev, fn); - } - }; - } - -},{}],67:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var parser = require('socket.io-parser'); - var Emitter = require('component-emitter'); - var toArray = require('to-array'); - var on = require('./on'); - var bind = require('component-bind'); - var debug = require('debug')('socket.io-client:socket'); - var hasBin = require('has-binary'); - - /** - * Module exports. - */ - - module.exports = exports = Socket; - - /** - * Internal events (blacklisted). - * These events can't be emitted by the user. - * - * @api private - */ - - var events = { - connect: 1, - connect_error: 1, - connect_timeout: 1, - disconnect: 1, - error: 1, - reconnect: 1, - reconnect_attempt: 1, - reconnect_failed: 1, - reconnect_error: 1, - reconnecting: 1 - }; - - /** - * Shortcut to `Emitter#emit`. - */ - - var emit = Emitter.prototype.emit; - - /** - * `Socket` constructor. - * - * @api public - */ - - function Socket(io, nsp){ - this.io = io; - this.nsp = nsp; - this.json = this; // compat - this.ids = 0; - this.acks = {}; - if (this.io.autoConnect) this.open(); - this.receiveBuffer = []; - this.sendBuffer = []; - this.connected = false; - this.disconnected = true; - } - - /** - * Mix in `Emitter`. - */ - - Emitter(Socket.prototype); - - /** - * Subscribe to open, close and packet events - * - * @api private - */ - - Socket.prototype.subEvents = function() { - if (this.subs) return; - - var io = this.io; - this.subs = [ - on(io, 'open', bind(this, 'onopen')), - on(io, 'packet', bind(this, 'onpacket')), - on(io, 'close', bind(this, 'onclose')) - ]; - }; - - /** - * "Opens" the socket. - * - * @api public - */ - - Socket.prototype.open = - Socket.prototype.connect = function(){ - if (this.connected) return this; - - this.subEvents(); - this.io.open(); // ensure open - if ('open' == this.io.readyState) this.onopen(); - return this; - }; - - /** - * Sends a `message` event. - * - * @return {Socket} self - * @api public - */ - - Socket.prototype.send = function(){ - var args = toArray(arguments); - args.unshift('message'); - this.emit.apply(this, args); - return this; - }; - - /** - * Override `emit`. - * If the event is in `events`, it's emitted normally. - * - * @param {String} event name - * @return {Socket} self - * @api public - */ - - Socket.prototype.emit = function(ev){ - if (events.hasOwnProperty(ev)) { - emit.apply(this, arguments); - return this; - } - - var args = toArray(arguments); - var parserType = parser.EVENT; // default - if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary - var packet = { type: parserType, data: args }; - - // event ack callback - if ('function' == typeof args[args.length - 1]) { - debug('emitting packet with ack id %d', this.ids); - this.acks[this.ids] = args.pop(); - packet.id = this.ids++; - } - - if (this.connected) { - this.packet(packet); - } else { - this.sendBuffer.push(packet); - } - - return this; - }; - - /** - * Sends a packet. - * - * @param {Object} packet - * @api private - */ - - Socket.prototype.packet = function(packet){ - packet.nsp = this.nsp; - this.io.packet(packet); - }; - - /** - * Called upon engine `open`. - * - * @api private - */ - - Socket.prototype.onopen = function(){ - debug('transport is open - connecting'); - - // write connect packet if necessary - if ('/' != this.nsp) { - this.packet({ type: parser.CONNECT }); - } - }; - - /** - * Called upon engine `close`. - * - * @param {String} reason - * @api private - */ - - Socket.prototype.onclose = function(reason){ - debug('close (%s)', reason); - this.connected = false; - this.disconnected = true; - delete this.id; - this.emit('disconnect', reason); - }; - - /** - * Called with socket packet. - * - * @param {Object} packet - * @api private - */ - - Socket.prototype.onpacket = function(packet){ - if (packet.nsp != this.nsp) return; - - switch (packet.type) { - case parser.CONNECT: - this.onconnect(); - break; - - case parser.EVENT: - this.onevent(packet); - break; - - case parser.BINARY_EVENT: - this.onevent(packet); - break; - - case parser.ACK: - this.onack(packet); - break; - - case parser.BINARY_ACK: - this.onack(packet); - break; - - case parser.DISCONNECT: - this.ondisconnect(); - break; - - case parser.ERROR: - this.emit('error', packet.data); - break; - } - }; - - /** - * Called upon a server event. - * - * @param {Object} packet - * @api private - */ - - Socket.prototype.onevent = function(packet){ - var args = packet.data || []; - debug('emitting event %j', args); - - if (null != packet.id) { - debug('attaching ack callback to event'); - args.push(this.ack(packet.id)); - } - - if (this.connected) { - emit.apply(this, args); - } else { - this.receiveBuffer.push(args); - } - }; - - /** - * Produces an ack callback to emit with an event. - * - * @api private - */ - - Socket.prototype.ack = function(id){ - var self = this; - var sent = false; - return function(){ - // prevent double callbacks - if (sent) return; - sent = true; - var args = toArray(arguments); - debug('sending ack %j', args); - - var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; - self.packet({ - type: type, - id: id, - data: args - }); - }; - }; - - /** - * Called upon a server acknowlegement. - * - * @param {Object} packet - * @api private - */ - - Socket.prototype.onack = function(packet){ - debug('calling ack %s with %j', packet.id, packet.data); - var fn = this.acks[packet.id]; - fn.apply(this, packet.data); - delete this.acks[packet.id]; - }; - - /** - * Called upon server connect. - * - * @api private - */ - - Socket.prototype.onconnect = function(){ - this.connected = true; - this.disconnected = false; - this.emit('connect'); - this.emitBuffered(); - }; - - /** - * Emit buffered events (received and emitted). - * - * @api private - */ - - Socket.prototype.emitBuffered = function(){ - var i; - for (i = 0; i < this.receiveBuffer.length; i++) { - emit.apply(this, this.receiveBuffer[i]); - } - this.receiveBuffer = []; - - for (i = 0; i < this.sendBuffer.length; i++) { - this.packet(this.sendBuffer[i]); - } - this.sendBuffer = []; - }; - - /** - * Called upon server disconnect. - * - * @api private - */ - - Socket.prototype.ondisconnect = function(){ - debug('server disconnect (%s)', this.nsp); - this.destroy(); - this.onclose('io server disconnect'); - }; - - /** - * Called upon forced client/server side disconnections, - * this method ensures the manager stops tracking us and - * that reconnections don't get triggered for this. - * - * @api private. - */ - - Socket.prototype.destroy = function(){ - if (this.subs) { - // clean subscriptions to avoid reconnections - for (var i = 0; i < this.subs.length; i++) { - this.subs[i].destroy(); - } - this.subs = null; - } - - this.io.destroy(this); - }; - - /** - * Disconnects the socket manually. - * - * @return {Socket} self - * @api public - */ - - Socket.prototype.close = - Socket.prototype.disconnect = function(){ - if (this.connected) { - debug('performing disconnect (%s)', this.nsp); - this.packet({ type: parser.DISCONNECT }); - } - - // remove socket from pool - this.destroy(); - - if (this.connected) { - // fire events - this.onclose('io client disconnect'); - } - return this; - }; - -},{"./on":66,"component-bind":7,"component-emitter":8,"debug":69,"has-binary":41,"socket.io-parser":71,"to-array":75}],68:[function(require,module,exports){ - (function (global){ - - /** - * Module dependencies. - */ - - var parseuri = require('parseuri'); - var debug = require('debug')('socket.io-client:url'); - - /** - * Module exports. - */ - - module.exports = url; - - /** - * URL parser. - * - * @param {String} url - * @param {Object} An object meant to mimic window.location. - * Defaults to window.location. - * @api public - */ - - function url(uri, loc){ - var obj = uri; - - // default to window.location - var loc = loc || global.location; - if (null == uri) uri = loc.protocol + '//' + loc.host; - - // relative path support - if ('string' == typeof uri) { - if ('/' == uri.charAt(0)) { - if ('/' == uri.charAt(1)) { - uri = loc.protocol + uri; - } else { - uri = loc.hostname + uri; - } - } - - if (!/^(https?|wss?):\/\//.test(uri)) { - debug('protocol-less url %s', uri); - if ('undefined' != typeof loc) { - uri = loc.protocol + '//' + uri; - } else { - uri = 'https://' + uri; - } - } - - // parse - debug('parse %s', uri); - obj = parseuri(uri); - } - - // make sure we treat `localhost:80` and `localhost` equally - if (!obj.port) { - if (/^(http|ws)$/.test(obj.protocol)) { - obj.port = '80'; - } - else if (/^(http|ws)s$/.test(obj.protocol)) { - obj.port = '443'; - } - } - - obj.path = obj.path || '/'; - - // define unique id - obj.id = obj.protocol + '://' + obj.host + ':' + obj.port; - // define href - obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); - - return obj; - } - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"debug":69,"parseuri":54}],69:[function(require,module,exports){ - - /** - * Expose `debug()` as the module. - */ - - module.exports = debug; - - /** - * Create a debugger with the given `name`. - * - * @param {String} name - * @return {Type} - * @api public - */ - - function debug(name) { - if (!debug.enabled(name)) return function(){}; - - return function(fmt){ - fmt = coerce(fmt); - - var curr = new Date; - var ms = curr - (debug[name] || curr); - debug[name] = curr; - - fmt = name - + ' ' - + fmt - + ' +' + debug.humanize(ms); - - // This hackery is required for IE8 - // where `console.log` doesn't have 'apply' - window.console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); - } - } - - /** - * The currently active debug mode names. - */ - - debug.names = []; - debug.skips = []; - - /** - * Enables a debug mode by name. This can include modes - * separated by a colon and wildcards. - * - * @param {String} name - * @api public - */ - - debug.enable = function(name) { - try { - localStorage.debug = name; - } catch(e){} - - var split = (name || '').split(/[\s,]+/) - , len = split.length; - - for (var i = 0; i < len; i++) { - name = split[i].replace('*', '.*?'); - if (name[0] === '-') { - debug.skips.push(new RegExp('^' + name.substr(1) + '$')); - } - else { - debug.names.push(new RegExp('^' + name + '$')); - } - } - }; - - /** - * Disable debug output. - * - * @api public - */ - - debug.disable = function(){ - debug.enable(''); - }; - - /** - * Humanize the given `ms`. - * - * @param {Number} m - * @return {String} - * @api private - */ - - debug.humanize = function(ms) { - var sec = 1000 - , min = 60 * 1000 - , hour = 60 * min; - - if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; - if (ms >= min) return (ms / min).toFixed(1) + 'm'; - if (ms >= sec) return (ms / sec | 0) + 's'; - return ms + 'ms'; - }; - - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - - debug.enabled = function(name) { - for (var i = 0, len = debug.skips.length; i < len; i++) { - if (debug.skips[i].test(name)) { - return false; - } - } - for (var i = 0, len = debug.names.length; i < len; i++) { - if (debug.names[i].test(name)) { - return true; - } - } - return false; - }; - - /** - * Coerce `val`. - */ - - function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; - } - -// persist - - try { - if (window.localStorage) debug.enable(localStorage.debug); - } catch(e){} - -},{}],70:[function(require,module,exports){ - (function (global){ - /*global Blob,File*/ - - /** - * Module requirements - */ - - var isArray = require('isarray'); - var isBuf = require('./is-buffer'); - - /** - * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder. - * Anything with blobs or files should be fed through removeBlobs before coming - * here. - * - * @param {Object} packet - socket.io event packet - * @return {Object} with deconstructed packet and list of buffers - * @api public - */ - - exports.deconstructPacket = function(packet){ - var buffers = []; - var packetData = packet.data; - - function _deconstructPacket(data) { - if (!data) return data; - - if (isBuf(data)) { - var placeholder = { _placeholder: true, num: buffers.length }; - buffers.push(data); - return placeholder; - } else if (isArray(data)) { - var newData = new Array(data.length); - for (var i = 0; i < data.length; i++) { - newData[i] = _deconstructPacket(data[i]); - } - return newData; - } else if ('object' == typeof data && !(data instanceof Date)) { - var newData = {}; - for (var key in data) { - newData[key] = _deconstructPacket(data[key]); - } - return newData; - } - return data; - } - - var pack = packet; - pack.data = _deconstructPacket(packetData); - pack.attachments = buffers.length; // number of binary 'attachments' - return {packet: pack, buffers: buffers}; - }; - - /** - * Reconstructs a binary packet from its placeholder packet and buffers - * - * @param {Object} packet - event packet with placeholders - * @param {Array} buffers - binary buffers to put in placeholder positions - * @return {Object} reconstructed packet - * @api public - */ - - exports.reconstructPacket = function(packet, buffers) { - var curPlaceHolder = 0; - - function _reconstructPacket(data) { - if (data && data._placeholder) { - var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway) - return buf; - } else if (isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = _reconstructPacket(data[i]); - } - return data; - } else if (data && 'object' == typeof data) { - for (var key in data) { - data[key] = _reconstructPacket(data[key]); - } - return data; - } - return data; - } - - packet.data = _reconstructPacket(packet.data); - packet.attachments = undefined; // no longer useful - return packet; - }; - - /** - * Asynchronously removes Blobs or Files from data via - * FileReader's readAsArrayBuffer method. Used before encoding - * data as msgpack. Calls callback with the blobless data. - * - * @param {Object} data - * @param {Function} callback - * @api private - */ - - exports.removeBlobs = function(data, callback) { - function _removeBlobs(obj, curKey, containingObject) { - if (!obj) return obj; - - // convert any blob - if ((global.Blob && obj instanceof Blob) || - (global.File && obj instanceof File)) { - pendingBlobs++; - - // async filereader - var fileReader = new FileReader(); - fileReader.onload = function() { // this.result == arraybuffer - if (containingObject) { - containingObject[curKey] = this.result; - } - else { - bloblessData = this.result; - } - - // if nothing pending its callback time - if(! --pendingBlobs) { - callback(bloblessData); - } - }; - - fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer - } else if (isArray(obj)) { // handle array - for (var i = 0; i < obj.length; i++) { - _removeBlobs(obj[i], i, obj); - } - } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object - for (var key in obj) { - _removeBlobs(obj[key], key, obj); - } - } - } - - var pendingBlobs = 0; - var bloblessData = data; - _removeBlobs(bloblessData); - if (!pendingBlobs) { - callback(bloblessData); - } - }; - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./is-buffer":72,"isarray":74}],71:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var debug = require('debug')('socket.io-parser'); - var json = require('json3'); - var isArray = require('isarray'); - var Emitter = require('component-emitter'); - var binary = require('./binary'); - var isBuf = require('./is-buffer'); - - /** - * Protocol version. - * - * @api public - */ - - exports.protocol = 4; - - /** - * Packet types. - * - * @api public - */ - - exports.types = [ - 'CONNECT', - 'DISCONNECT', - 'EVENT', - 'BINARY_EVENT', - 'ACK', - 'BINARY_ACK', - 'ERROR' - ]; - - /** - * Packet type `connect`. - * - * @api public - */ - - exports.CONNECT = 0; - - /** - * Packet type `disconnect`. - * - * @api public - */ - - exports.DISCONNECT = 1; - - /** - * Packet type `event`. - * - * @api public - */ - - exports.EVENT = 2; - - /** - * Packet type `ack`. - * - * @api public - */ - - exports.ACK = 3; - - /** - * Packet type `error`. - * - * @api public - */ - - exports.ERROR = 4; - - /** - * Packet type 'binary event' - * - * @api public - */ - - exports.BINARY_EVENT = 5; - - /** - * Packet type `binary ack`. For acks with binary arguments. - * - * @api public - */ - - exports.BINARY_ACK = 6; - - /** - * Encoder constructor. - * - * @api public - */ - - exports.Encoder = Encoder; - - /** - * Decoder constructor. - * - * @api public - */ - - exports.Decoder = Decoder; - - /** - * A socket.io Encoder instance - * - * @api public - */ - - function Encoder() {} - - /** - * Encode a packet as a single string if non-binary, or as a - * buffer sequence, depending on packet type. - * - * @param {Object} obj - packet object - * @param {Function} callback - function to handle encodings (likely engine.write) - * @return Calls callback with Array of encodings - * @api public - */ - - Encoder.prototype.encode = function(obj, callback){ - debug('encoding packet %j', obj); - - if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { - encodeAsBinary(obj, callback); - } - else { - var encoding = encodeAsString(obj); - callback([encoding]); - } - }; - - /** - * Encode packet as string. - * - * @param {Object} packet - * @return {String} encoded - * @api private - */ - - function encodeAsString(obj) { - var str = ''; - var nsp = false; - - // first is type - str += obj.type; - - // attachments if we have them - if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { - str += obj.attachments; - str += '-'; - } - - // if we have a namespace other than `/` - // we append it followed by a comma `,` - if (obj.nsp && '/' != obj.nsp) { - nsp = true; - str += obj.nsp; - } - - // immediately followed by the id - if (null != obj.id) { - if (nsp) { - str += ','; - nsp = false; - } - str += obj.id; - } - - // json data - if (null != obj.data) { - if (nsp) str += ','; - str += json.stringify(obj.data); - } - - debug('encoded %j as %s', obj, str); - return str; - } - - /** - * Encode packet as 'buffer sequence' by removing blobs, and - * deconstructing packet into object with placeholders and - * a list of buffers. - * - * @param {Object} packet - * @return {Buffer} encoded - * @api private - */ - - function encodeAsBinary(obj, callback) { - - function writeEncoding(bloblessData) { - var deconstruction = binary.deconstructPacket(bloblessData); - var pack = encodeAsString(deconstruction.packet); - var buffers = deconstruction.buffers; - - buffers.unshift(pack); // add packet info to beginning of data list - callback(buffers); // write all the buffers - } - - binary.removeBlobs(obj, writeEncoding); - } - - /** - * A socket.io Decoder instance - * - * @return {Object} decoder - * @api public - */ - - function Decoder() { - this.reconstructor = null; - } - - /** - * Mix in `Emitter` with Decoder. - */ - - Emitter(Decoder.prototype); - - /** - * Decodes an ecoded packet string into packet JSON. - * - * @param {String} obj - encoded packet - * @return {Object} packet - * @api public - */ - - Decoder.prototype.add = function(obj) { - var packet; - if ('string' == typeof obj) { - packet = decodeString(obj); - if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json - this.reconstructor = new BinaryReconstructor(packet); - - // no attachments, labeled binary but no binary data to follow - if (this.reconstructor.reconPack.attachments === 0) { - this.emit('decoded', packet); - } - } else { // non-binary full packet - this.emit('decoded', packet); - } - } - else if (isBuf(obj) || obj.base64) { // raw binary data - if (!this.reconstructor) { - throw new Error('got binary data when not reconstructing a packet'); - } else { - packet = this.reconstructor.takeBinaryData(obj); - if (packet) { // received final buffer - this.reconstructor = null; - this.emit('decoded', packet); - } - } - } - else { - throw new Error('Unknown type: ' + obj); - } - }; - - /** - * Decode a packet String (JSON data) - * - * @param {String} str - * @return {Object} packet - * @api private - */ - - function decodeString(str) { - var p = {}; - var i = 0; - - // look up type - p.type = Number(str.charAt(0)); - if (null == exports.types[p.type]) return error(); - - // look up attachments if type binary - if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) { - var buf = ''; - while (str.charAt(++i) != '-') { - buf += str.charAt(i); - if (i == str.length) break; - } - if (buf != Number(buf) || str.charAt(i) != '-') { - throw new Error('Illegal attachments'); - } - p.attachments = Number(buf); - } - - // look up namespace (if any) - if ('/' == str.charAt(i + 1)) { - p.nsp = ''; - while (++i) { - var c = str.charAt(i); - if (',' == c) break; - p.nsp += c; - if (i == str.length) break; - } - } else { - p.nsp = '/'; - } - - // look up id - var next = str.charAt(i + 1); - if ('' !== next && Number(next) == next) { - p.id = ''; - while (++i) { - var c = str.charAt(i); - if (null == c || Number(c) != c) { - --i; - break; - } - p.id += str.charAt(i); - if (i == str.length) break; - } - p.id = Number(p.id); - } - - // look up json data - if (str.charAt(++i)) { - try { - p.data = json.parse(str.substr(i)); - } catch(e){ - return error(); - } - } - - debug('decoded %s as %j', str, p); - return p; - } - - /** - * Deallocates a parser's resources - * - * @api public - */ - - Decoder.prototype.destroy = function() { - if (this.reconstructor) { - this.reconstructor.finishedReconstruction(); - } - }; - - /** - * A manager of a binary event's 'buffer sequence'. Should - * be constructed whenever a packet of type BINARY_EVENT is - * decoded. - * - * @param {Object} packet - * @return {BinaryReconstructor} initialized reconstructor - * @api private - */ - - function BinaryReconstructor(packet) { - this.reconPack = packet; - this.buffers = []; - } - - /** - * Method to be called when binary data received from connection - * after a BINARY_EVENT packet. - * - * @param {Buffer | ArrayBuffer} binData - the raw binary data received - * @return {null | Object} returns null if more binary data is expected or - * a reconstructed packet object if all buffers have been received. - * @api private - */ - - BinaryReconstructor.prototype.takeBinaryData = function(binData) { - this.buffers.push(binData); - if (this.buffers.length == this.reconPack.attachments) { // done with buffer list - var packet = binary.reconstructPacket(this.reconPack, this.buffers); - this.finishedReconstruction(); - return packet; - } - return null; - }; - - /** - * Cleans up binary packet reconstruction variables. - * - * @api private - */ - - BinaryReconstructor.prototype.finishedReconstruction = function() { - this.reconPack = null; - this.buffers = []; - }; - - function error(data){ - return { - type: exports.ERROR, - data: 'parser error' - }; - } - -},{"./binary":70,"./is-buffer":72,"component-emitter":8,"debug":73,"isarray":74,"json3":45}],72:[function(require,module,exports){ - (function (global){ - - module.exports = isBuf; - - /** - * Returns true if obj is a buffer or an arraybuffer. - * - * @api private - */ - - function isBuf(obj) { - return (global.Buffer && global.Buffer.isBuffer(obj)) || - (global.ArrayBuffer && obj instanceof ArrayBuffer); - } - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],73:[function(require,module,exports){ - arguments[4][69][0].apply(exports,arguments) -},{"dup":69}],74:[function(require,module,exports){ - arguments[4][42][0].apply(exports,arguments) -},{"dup":42}],75:[function(require,module,exports){ - module.exports = toArray - - function toArray(list, index) { - var array = [] - - index = index || 0 - - for (var i = index || 0; i < list.length; i++) { - array[i - index] = list[i] - } - - return array - } - -},{}],76:[function(require,module,exports){ -// based on https://github.com/ESTOS/strophe.jingle/ -// adds wildemitter support - var util = require('util'); - var adapter = require('webrtc-adapter'); // jshint ignore:line - 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 window.RTCPeerConnection(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); - - ['signalingState', 'iceConnectionState', 'localDescription', 'remoteDescription'].forEach(function (prop) { - Object.defineProperty(TraceablePeerConnection.prototype, prop, { - get: function () { - return this.peerconnection[prop]; - } - }); - }); - - 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)); - return this.peerconnection.setLocalDescription( - description - ).then( - function () { - self.trace('setLocalDescriptionOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('setLocalDescriptionOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); - }; - - TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) { - var self = this; - this.trace('setRemoteDescription', dumpSDP(description)); - return this.peerconnection.setRemoteDescription( - description - ).then( - function () { - self.trace('setRemoteDescriptionOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('setRemoteDescriptionOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); - }; - - TraceablePeerConnection.prototype.close = function () { - this.trace('stopXXX'); - console.trace('Stop Trace'); - if (this.peerconnection.signalingState != 'closed') { - this.peerconnection.close(); - } - }; - - TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createOffer', constraints); - return this.peerconnection.createOffer( - constraints - ).then( - function (offer) { - self.trace('createOfferOnSuccess', dumpSDP(offer)); - if (successCallback) successCallback(offer); - }, - function (err) { - self.trace('createOfferOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); - }; - - TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createAnswer', constraints); - return this.peerconnection.createAnswer( - constraints - ).then( - function (answer) { - self.trace('createAnswerOnSuccess', dumpSDP(answer)); - if (successCallback) successCallback(answer); - }, - function (err) { - self.trace('createAnswerOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); - }; - - TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) { - var self = this; - this.trace('addIceCandidate', candidate); - return this.peerconnection.addIceCandidate( - candidate - ).then( - function () { - //self.trace('addIceCandidateOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('addIceCandidateOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); - }; - - TraceablePeerConnection.prototype.getStats = function () { - this.peerconnection.getStats.apply(this.peerconnection, arguments); - }; - - module.exports = TraceablePeerConnection; - -},{"util":89,"webrtc-adapter":77,"wildemitter":100}],77:[function(require,module,exports){ - arguments[4][30][0].apply(exports,arguments) -},{"./chrome/chrome_shim":78,"./edge/edge_shim":80,"./firefox/firefox_shim":82,"./safari/safari_shim":84,"./utils":85,"dup":30}],78:[function(require,module,exports){ - arguments[4][31][0].apply(exports,arguments) -},{"../utils.js":85,"./getusermedia":79,"dup":31}],79:[function(require,module,exports){ - arguments[4][32][0].apply(exports,arguments) -},{"../utils.js":85,"dup":32}],80:[function(require,module,exports){ - arguments[4][33][0].apply(exports,arguments) -},{"../utils":85,"./getusermedia":81,"dup":33,"sdp":62}],81:[function(require,module,exports){ - arguments[4][34][0].apply(exports,arguments) -},{"dup":34}],82:[function(require,module,exports){ - arguments[4][35][0].apply(exports,arguments) -},{"../utils":85,"./getusermedia":83,"dup":35}],83:[function(require,module,exports){ - arguments[4][36][0].apply(exports,arguments) -},{"../utils":85,"dup":36}],84:[function(require,module,exports){ - arguments[4][37][0].apply(exports,arguments) -},{"dup":37}],85:[function(require,module,exports){ - arguments[4][38][0].apply(exports,arguments) -},{"dup":38}],86:[function(require,module,exports){ - (function (global){ - /*! https://mths.be/utf8js v2.0.0 by @mathias */ - ;(function(root) { - - // Detect free variables `exports` - var freeExports = typeof exports == 'object' && exports; - - // Detect free variable `module` - var freeModule = typeof module == 'object' && module && - module.exports == freeExports && module; - - // Detect free variable `global`, from Node.js or Browserified code, - // and use it as `root` - var freeGlobal = typeof global == 'object' && global; - if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { - root = freeGlobal; - } - - /*--------------------------------------------------------------------------*/ - - var stringFromCharCode = String.fromCharCode; - - // Taken from https://mths.be/punycode - function ucs2decode(string) { - var output = []; - var counter = 0; - var length = string.length; - var value; - var extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - // Taken from https://mths.be/punycode - function ucs2encode(array) { - var length = array.length; - var index = -1; - var value; - var output = ''; - while (++index < length) { - value = array[index]; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - } - return output; - } - - function checkScalarValue(codePoint) { - if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { - throw Error( - 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + - ' is not a scalar value' - ); - } - } - /*--------------------------------------------------------------------------*/ - - function createByte(codePoint, shift) { - return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); - } - - function encodeCodePoint(codePoint) { - if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence - return stringFromCharCode(codePoint); - } - var symbol = ''; - if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence - symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); - } - else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence - checkScalarValue(codePoint); - symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); - symbol += createByte(codePoint, 6); - } - else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence - symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); - symbol += createByte(codePoint, 12); - symbol += createByte(codePoint, 6); - } - symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); - return symbol; - } - - function utf8encode(string) { - var codePoints = ucs2decode(string); - var length = codePoints.length; - var index = -1; - var codePoint; - var byteString = ''; - while (++index < length) { - codePoint = codePoints[index]; - byteString += encodeCodePoint(codePoint); - } - return byteString; - } - - /*--------------------------------------------------------------------------*/ - - function readContinuationByte() { - if (byteIndex >= byteCount) { - throw Error('Invalid byte index'); - } - - var continuationByte = byteArray[byteIndex] & 0xFF; - byteIndex++; - - if ((continuationByte & 0xC0) == 0x80) { - return continuationByte & 0x3F; - } - - // If we end up here, it’s not a continuation byte - throw Error('Invalid continuation byte'); - } - - function decodeSymbol() { - var byte1; - var byte2; - var byte3; - var byte4; - var codePoint; - - if (byteIndex > byteCount) { - throw Error('Invalid byte index'); - } - - if (byteIndex == byteCount) { - return false; - } - - // Read first byte - byte1 = byteArray[byteIndex] & 0xFF; - byteIndex++; - - // 1-byte sequence (no continuation bytes) - if ((byte1 & 0x80) == 0) { - return byte1; - } - - // 2-byte sequence - if ((byte1 & 0xE0) == 0xC0) { - var byte2 = readContinuationByte(); - codePoint = ((byte1 & 0x1F) << 6) | byte2; - if (codePoint >= 0x80) { - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } - - // 3-byte sequence (may include unpaired surrogates) - if ((byte1 & 0xF0) == 0xE0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; - if (codePoint >= 0x0800) { - checkScalarValue(codePoint); - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } - - // 4-byte sequence - if ((byte1 & 0xF8) == 0xF0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - byte4 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | - (byte3 << 0x06) | byte4; - if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { - return codePoint; - } - } - - throw Error('Invalid UTF-8 detected'); - } - - var byteArray; - var byteCount; - var byteIndex; - function utf8decode(byteString) { - byteArray = ucs2decode(byteString); - byteCount = byteArray.length; - byteIndex = 0; - var codePoints = []; - var tmp; - while ((tmp = decodeSymbol()) !== false) { - codePoints.push(tmp); - } - return ucs2encode(codePoints); - } - - /*--------------------------------------------------------------------------*/ - - var utf8 = { - 'version': '2.0.0', - 'encode': utf8encode, - 'decode': utf8decode - }; - - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define(function() { - return utf8; - }); - } else if (freeExports && !freeExports.nodeType) { - if (freeModule) { // in Node.js or RingoJS v0.8.0+ - freeModule.exports = utf8; - } else { // in Narwhal or RingoJS v0.7.0- - var object = {}; - var hasOwnProperty = object.hasOwnProperty; - for (var key in utf8) { - hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); - } - } - } else { // in Rhino or a web browser - root.utf8 = utf8; - } - - }(this)); - - }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],87:[function(require,module,exports){ - if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; - } else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } - } - -},{}],88:[function(require,module,exports){ - module.exports = function isBuffer(arg) { - return arg && typeof arg === 'object' - && typeof arg.copy === 'function' - && typeof arg.fill === 'function' - && typeof arg.readUInt8 === 'function'; - } -},{}],89:[function(require,module,exports){ - (function (process,global){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - - var formatRegExp = /%[sdj%]/g; - exports.format = function(f) { - if (!isString(f)) { - var objects = []; - for (var i = 0; i < arguments.length; i++) { - objects.push(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': - try { - return JSON.stringify(args[i++]); - } catch (_) { - return '[Circular]'; - } - default: - return x; - } - }); - for (var x = args[i]; i < len; x = args[++i]) { - if (isNull(x) || !isObject(x)) { - str += ' ' + x; - } else { - str += ' ' + inspect(x); - } - } - return str; - }; - - -// Mark that a method should not be used. -// Returns a modified function which warns once by default. -// If --no-deprecation is set, then it is a no-op. - exports.deprecate = function(fn, msg) { - // Allow for deprecating things in the process of starting up. - if (isUndefined(global.process)) { - return function() { - return exports.deprecate(fn, msg).apply(this, arguments); - }; - } - - if (process.noDeprecation === true) { - return fn; - } - - var warned = false; - function deprecated() { - if (!warned) { - if (process.throwDeprecation) { - throw new Error(msg); - } else if (process.traceDeprecation) { - console.trace(msg); - } else { - console.error(msg); - } - warned = true; - } - return fn.apply(this, arguments); - } - - return deprecated; - }; - - - var debugs = {}; - var debugEnviron; - exports.debuglog = function(set) { - if (isUndefined(debugEnviron)) - debugEnviron = process.env.NODE_DEBUG || ''; - set = set.toUpperCase(); - if (!debugs[set]) { - if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { - var pid = process.pid; - debugs[set] = function() { - var msg = exports.format.apply(exports, arguments); - console.error('%s %d: %s', set, pid, msg); - }; - } else { - debugs[set] = function() {}; - } - } - return debugs[set]; - }; - - - /** - * Echos the value of a value. Trys to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Object} opts Optional options object that alters the output. - */ - /* legacy: obj, showHidden, depth, colors*/ - function inspect(obj, opts) { - // default options - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - // legacy... - if (arguments.length >= 3) ctx.depth = arguments[2]; - if (arguments.length >= 4) ctx.colors = arguments[3]; - if (isBoolean(opts)) { - // legacy... - ctx.showHidden = opts; - } else if (opts) { - // got an "options" object - exports._extend(ctx, opts); - } - // set default options - if (isUndefined(ctx.showHidden)) ctx.showHidden = false; - if (isUndefined(ctx.depth)) ctx.depth = 2; - if (isUndefined(ctx.colors)) ctx.colors = false; - if (isUndefined(ctx.customInspect)) ctx.customInspect = true; - if (ctx.colors) ctx.stylize = stylizeWithColor; - return formatValue(ctx, obj, ctx.depth); - } - exports.inspect = inspect; - - -// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics - inspect.colors = { - '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] - }; - -// Don't use 'blue' not visible on cmd.exe - inspect.styles = { - 'special': 'cyan', - 'number': 'yellow', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' - }; - - - function stylizeWithColor(str, styleType) { - var style = inspect.styles[styleType]; - - if (style) { - return '\u001b[' + inspect.colors[style][0] + 'm' + str + - '\u001b[' + inspect.colors[style][1] + 'm'; - } else { - return str; - } - } - - - function stylizeNoColor(str, styleType) { - return str; - } - - - function arrayToHash(array) { - var hash = {}; - - array.forEach(function(val, idx) { - hash[val] = true; - }); - - return hash; - } - - - function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (ctx.customInspect && - value && - isFunction(value.inspect) && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes, ctx); - if (!isString(ret)) { - ret = formatValue(ctx, ret, recurseTimes); - } - return ret; - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // Look up the keys of the object. - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - if (ctx.showHidden) { - keys = Object.getOwnPropertyNames(value); - } - - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx - if (isError(value) - && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { - return formatError(value); - } - - // Some type of object without properties can be shortcutted. - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); - } - - - function formatPrimitive(ctx, value) { - if (isUndefined(value)) - return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) - return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) - return ctx.stylize('' + value, 'boolean'); - // For some reason typeof null is "object", so special case here. - if (isNull(value)) - return ctx.stylize('null', 'null'); - } - - - function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; - } - - - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; - } - - - function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } - } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - 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 = ctx.stylize('[Circular]', 'special'); - } - } - if (isUndefined(name)) { - if (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 = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; - } - - - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - -// NOTE: These type checking functions intentionally don't use `instanceof` -// because it is fragile and can be easily faked with `Object.create()`. - function isArray(ar) { - return Array.isArray(ar); - } - exports.isArray = isArray; - - function isBoolean(arg) { - return typeof arg === 'boolean'; - } - exports.isBoolean = isBoolean; - - function isNull(arg) { - return arg === null; - } - exports.isNull = isNull; - - function isNullOrUndefined(arg) { - return arg == null; - } - exports.isNullOrUndefined = isNullOrUndefined; - - function isNumber(arg) { - return typeof arg === 'number'; - } - exports.isNumber = isNumber; - - function isString(arg) { - return typeof arg === 'string'; - } - exports.isString = isString; - - function isSymbol(arg) { - return typeof arg === 'symbol'; - } - exports.isSymbol = isSymbol; - - function isUndefined(arg) { - return arg === void 0; - } - exports.isUndefined = isUndefined; - - function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; - } - exports.isRegExp = isRegExp; - - function isObject(arg) { - return typeof arg === 'object' && arg !== null; - } - exports.isObject = isObject; - - function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; - } - exports.isDate = isDate; - - function isError(e) { - return isObject(e) && - (objectToString(e) === '[object Error]' || e instanceof Error); - } - exports.isError = isError; - - function isFunction(arg) { - return typeof arg === 'function'; - } - exports.isFunction = isFunction; - - function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; - } - exports.isPrimitive = isPrimitive; - - exports.isBuffer = require('./support/isBuffer'); - - function objectToString(o) { - return Object.prototype.toString.call(o); - } - - - 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(' '); - } - - -// log is just a thin wrapper to console.log that prepends a timestamp - exports.log = function() { - console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); - }; - - - /** - * Inherit the prototype methods from one constructor into another. - * - * The Function.prototype.inherits from lang.js rewritten as a standalone - * function (not on Function.prototype). NOTE: If this file is to be loaded - * during bootstrapping this function needs to be rewritten using some native - * functions as prototype setup using normal JavaScript does not work as - * expected during bootstrapping (see mirror.js in r114903). - * - * @param {function} ctor Constructor function which needs to inherit the - * prototype. - * @param {function} superCtor Constructor function to inherit prototype from. - */ - exports.inherits = require('inherits'); - - exports._extend = function(origin, add) { - // Don't do anything if add isn't an object - if (!add || !isObject(add)) return origin; - - var keys = Object.keys(add); - var i = keys.length; - while (i--) { - origin[keys[i]] = add[keys[i]]; - } - return origin; - }; - - function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); - } - - }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./support/isBuffer":88,"_process":55,"inherits":87}],90:[function(require,module,exports){ - arguments[4][30][0].apply(exports,arguments) -},{"./chrome/chrome_shim":91,"./edge/edge_shim":93,"./firefox/firefox_shim":95,"./safari/safari_shim":97,"./utils":98,"dup":30}],91:[function(require,module,exports){ - - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - var logging = require('../utils.js').log; - var browserDetails = require('../utils.js').browserDetails; - - var chromeShim = { - shimMediaStream: function() { - window.MediaStream = window.MediaStream || window.webkitMediaStream; - }, - - shimOnTrack: function() { - if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in - window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { - get: function() { - return this._ontrack; - }, - set: function(f) { - var self = this; - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - this.removeEventListener('addstream', this._ontrackpoly); - } - this.addEventListener('track', this._ontrack = f); - this.addEventListener('addstream', this._ontrackpoly = function(e) { - // onaddstream does not fire when a track is added to an existing - // stream. But stream.onaddtrack is implemented so we use that. - e.stream.addEventListener('addtrack', function(te) { - var event = new Event('track'); - event.track = te.track; - event.receiver = {track: te.track}; - event.streams = [e.stream]; - self.dispatchEvent(event); - }); - e.stream.getTracks().forEach(function(track) { - var event = new Event('track'); - event.track = track; - event.receiver = {track: track}; - event.streams = [e.stream]; - this.dispatchEvent(event); - }.bind(this)); - }.bind(this)); - } - }); - } - }, - - shimSourceObject: function() { - if (typeof window === 'object') { - if (window.HTMLMediaElement && - !('srcObject' in window.HTMLMediaElement.prototype)) { - // Shim the srcObject property, once, when HTMLMediaElement is found. - Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { - get: function() { - return this._srcObject; - }, - set: function(stream) { - var self = this; - // Use _srcObject as a private property for this shim - this._srcObject = stream; - if (this.src) { - URL.revokeObjectURL(this.src); - } - - if (!stream) { - this.src = ''; - return; - } - this.src = URL.createObjectURL(stream); - // We need to recreate the blob url when a track is added or - // removed. Doing it manually since we want to avoid a recursion. - stream.addEventListener('addtrack', function() { - if (self.src) { - URL.revokeObjectURL(self.src); - } - self.src = URL.createObjectURL(stream); - }); - stream.addEventListener('removetrack', function() { - if (self.src) { - URL.revokeObjectURL(self.src); - } - self.src = URL.createObjectURL(stream); - }); - } - }); - } - } - }, - - shimPeerConnection: function() { - // The RTCPeerConnection object. - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - // Translate iceTransportPolicy to iceTransports, - // see https://code.google.com/p/webrtc/issues/detail?id=4869 - logging('PeerConnection'); - if (pcConfig && pcConfig.iceTransportPolicy) { - pcConfig.iceTransports = pcConfig.iceTransportPolicy; - } - - var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); - var origGetStats = pc.getStats.bind(pc); - pc.getStats = function(selector, successCallback, errorCallback) { - var self = this; - var args = arguments; - - // If selector is a function then we are in the old style stats so just - // pass back the original getStats format to avoid breaking old users. - if (arguments.length > 0 && typeof selector === 'function') { - return origGetStats(selector, successCallback); - } - - var fixChromeStats_ = function(response) { - var standardReport = {}; - var reports = response.result(); - reports.forEach(function(report) { - var standardStats = { - id: report.id, - timestamp: report.timestamp, - type: report.type - }; - report.names().forEach(function(name) { - standardStats[name] = report.stat(name); - }); - standardReport[standardStats.id] = standardStats; - }); - - return standardReport; - }; - - // shim getStats with maplike support - var makeMapStats = function(stats, legacyStats) { - var map = new Map(Object.keys(stats).map(function(key) { - return[key, stats[key]]; - })); - legacyStats = legacyStats || stats; - Object.keys(legacyStats).forEach(function(key) { - map[key] = legacyStats[key]; - }); - return map; - }; - - if (arguments.length >= 2) { - var successCallbackWrapper_ = function(response) { - args[1](makeMapStats(fixChromeStats_(response))); - }; - - return origGetStats.apply(this, [successCallbackWrapper_, - arguments[0]]); - } - - // promise-support - return new Promise(function(resolve, reject) { - if (args.length === 1 && typeof selector === 'object') { - origGetStats.apply(self, [ - function(response) { - resolve(makeMapStats(fixChromeStats_(response))); - }, reject]); - } else { - // Preserve legacy chrome stats only on legacy access of stats obj - origGetStats.apply(self, [ - function(response) { - resolve(makeMapStats(fixChromeStats_(response), - response.result())); - }, reject]); - } - }).then(successCallback, errorCallback); - }; - - return pc; - }; - window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (webkitRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - return webkitRTCPeerConnection.generateCertificate; - } - }); - } - - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var self = this; - if (arguments.length < 1 || (arguments.length === 1 && - typeof arguments[0] === 'object')) { - var opts = arguments.length === 1 ? arguments[0] : undefined; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [resolve, reject, opts]); - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - - // add promise support -- natively available in Chrome 51 - if (browserDetails.version < 51) { - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - var promise = new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], resolve, reject]); - }); - if (args.length < 2) { - return promise; - } - return promise.then(function() { - args[1].apply(null, []); - }, - function(err) { - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }); - }; - }); - } - - // shim implicit creation of RTCSessionDescription/RTCIceCandidate - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate') ? - RTCIceCandidate : RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }; - }); - - // support for addIceCandidate(null or undefined) - var nativeAddIceCandidate = - RTCPeerConnection.prototype.addIceCandidate; - RTCPeerConnection.prototype.addIceCandidate = function() { - if (!arguments[0]) { - if (arguments[1]) { - arguments[1].apply(null); - } - return Promise.resolve(); - } - return nativeAddIceCandidate.apply(this, arguments); - }; - } - }; - - -// Expose public methods. - module.exports = { - shimMediaStream: chromeShim.shimMediaStream, - shimOnTrack: chromeShim.shimOnTrack, - shimSourceObject: chromeShim.shimSourceObject, - shimPeerConnection: chromeShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia') - }; - -},{"../utils.js":98,"./getusermedia":92}],92:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - var logging = require('../utils.js').log; - -// Expose public methods. - module.exports = function() { - var constraintsToChrome_ = function(c) { - if (typeof c !== 'object' || c.mandatory || c.optional) { - return c; - } - var cc = {}; - Object.keys(c).forEach(function(key) { - if (key === 'require' || key === 'advanced' || key === 'mediaSource') { - return; - } - var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; - if (r.exact !== undefined && typeof r.exact === 'number') { - r.min = r.max = r.exact; - } - var oldname_ = function(prefix, name) { - if (prefix) { - return prefix + name.charAt(0).toUpperCase() + name.slice(1); - } - return (name === 'deviceId') ? 'sourceId' : name; - }; - if (r.ideal !== undefined) { - cc.optional = cc.optional || []; - var oc = {}; - if (typeof r.ideal === 'number') { - oc[oldname_('min', key)] = r.ideal; - cc.optional.push(oc); - oc = {}; - oc[oldname_('max', key)] = r.ideal; - cc.optional.push(oc); - } else { - oc[oldname_('', key)] = r.ideal; - cc.optional.push(oc); - } - } - if (r.exact !== undefined && typeof r.exact !== 'number') { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_('', key)] = r.exact; - } else { - ['min', 'max'].forEach(function(mix) { - if (r[mix] !== undefined) { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_(mix, key)] = r[mix]; - } - }); - } - }); - if (c.advanced) { - cc.optional = (cc.optional || []).concat(c.advanced); - } - return cc; - }; - - var shimConstraints_ = function(constraints, func) { - constraints = JSON.parse(JSON.stringify(constraints)); - if (constraints && constraints.audio) { - constraints.audio = constraintsToChrome_(constraints.audio); - } - if (constraints && typeof constraints.video === 'object') { - // Shim facingMode for mobile, where it defaults to "user". - var face = constraints.video.facingMode; - face = face && ((typeof face === 'object') ? face : {ideal: face}); - - if ((face && (face.exact === 'user' || face.exact === 'environment' || - face.ideal === 'user' || face.ideal === 'environment')) && - !(navigator.mediaDevices.getSupportedConstraints && - navigator.mediaDevices.getSupportedConstraints().facingMode)) { - delete constraints.video.facingMode; - if (face.exact === 'environment' || face.ideal === 'environment') { - // Look for "back" in label, or use last cam (typically back cam). - return navigator.mediaDevices.enumerateDevices() - .then(function(devices) { - devices = devices.filter(function(d) { - return d.kind === 'videoinput'; - }); - var back = devices.find(function(d) { - return d.label.toLowerCase().indexOf('back') !== -1; - }) || (devices.length && devices[devices.length - 1]); - if (back) { - constraints.video.deviceId = face.exact ? {exact: back.deviceId} : - {ideal: back.deviceId}; - } - constraints.video = constraintsToChrome_(constraints.video); - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }); - } - } - constraints.video = constraintsToChrome_(constraints.video); - } - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }; - - var shimError_ = function(e) { - return { - name: { - PermissionDeniedError: 'NotAllowedError', - ConstraintNotSatisfiedError: 'OverconstrainedError' - }[e.name] || e.name, - message: e.message, - constraint: e.constraintName, - toString: function() { - return this.name + (this.message && ': ') + this.message; - } - }; - }; - - var getUserMedia_ = function(constraints, onSuccess, onError) { - shimConstraints_(constraints, function(c) { - navigator.webkitGetUserMedia(c, onSuccess, function(e) { - onError(shimError_(e)); - }); - }); - }; - - navigator.getUserMedia = getUserMedia_; - - // Returns the result of getUserMedia as a Promise. - var getUserMediaPromise_ = function(constraints) { - return new Promise(function(resolve, reject) { - navigator.getUserMedia(constraints, resolve, reject); - }); - }; - - if (!navigator.mediaDevices) { - navigator.mediaDevices = { - getUserMedia: getUserMediaPromise_, - enumerateDevices: function() { - return new Promise(function(resolve) { - var kinds = {audio: 'audioinput', video: 'videoinput'}; - return MediaStreamTrack.getSources(function(devices) { - resolve(devices.map(function(device) { - return {label: device.label, - kind: kinds[device.kind], - deviceId: device.id, - groupId: ''}; - })); - }); - }); - } - }; - } - - // A shim for getUserMedia method on the mediaDevices object. - // TODO(KaptenJansson) remove once implemented in Chrome stable. - if (!navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia = function(constraints) { - return getUserMediaPromise_(constraints); - }; - } else { - // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia - // function which returns a Promise, it does not accept spec-style - // constraints. - var origGetUserMedia = navigator.mediaDevices.getUserMedia. - bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(cs) { - return shimConstraints_(cs, function(c) { - return origGetUserMedia(c).then(function(stream) { - if (c.audio && !stream.getAudioTracks().length || - c.video && !stream.getVideoTracks().length) { - stream.getTracks().forEach(function(track) { - track.stop(); - }); - throw new DOMException('', 'NotFoundError'); - } - return stream; - }, function(e) { - return Promise.reject(shimError_(e)); - }); - }); - }; - } - - // Dummy devicechange event methods. - // TODO(KaptenJansson) remove once implemented in Chrome stable. - if (typeof navigator.mediaDevices.addEventListener === 'undefined') { - navigator.mediaDevices.addEventListener = function() { - logging('Dummy mediaDevices.addEventListener called.'); - }; - } - if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { - navigator.mediaDevices.removeEventListener = function() { - logging('Dummy mediaDevices.removeEventListener called.'); - }; - } - }; - -},{"../utils.js":98}],93:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var SDPUtils = require('sdp'); - var browserDetails = require('../utils').browserDetails; - - var edgeShim = { - shimPeerConnection: function() { - if (window.RTCIceGatherer) { - // ORTC defines an RTCIceCandidate object but no constructor. - // Not implemented in Edge. - if (!window.RTCIceCandidate) { - window.RTCIceCandidate = function(args) { - return args; - }; - } - // ORTC does not have a session description object but - // other browsers (i.e. Chrome) that will support both PC and ORTC - // in the future might have this defined already. - if (!window.RTCSessionDescription) { - window.RTCSessionDescription = function(args) { - return args; - }; - } - // this adds an additional event listener to MediaStrackTrack that signals - // when a tracks enabled property was changed. - var origMSTEnabled = Object.getOwnPropertyDescriptor( - MediaStreamTrack.prototype, 'enabled'); - Object.defineProperty(MediaStreamTrack.prototype, 'enabled', { - set: function(value) { - origMSTEnabled.set.call(this, value); - var ev = new Event('enabled'); - ev.enabled = value; - this.dispatchEvent(ev); - } - }); - } - - window.RTCPeerConnection = function(config) { - var self = this; - - var _eventTarget = document.createDocumentFragment(); - ['addEventListener', 'removeEventListener', 'dispatchEvent'] - .forEach(function(method) { - self[method] = _eventTarget[method].bind(_eventTarget); - }); - - this.onicecandidate = null; - this.onaddstream = null; - this.ontrack = null; - this.onremovestream = null; - this.onsignalingstatechange = null; - this.oniceconnectionstatechange = null; - this.onnegotiationneeded = null; - this.ondatachannel = null; - - this.localStreams = []; - this.remoteStreams = []; - this.getLocalStreams = function() { - return self.localStreams; - }; - this.getRemoteStreams = function() { - return self.remoteStreams; - }; - - this.localDescription = new RTCSessionDescription({ - type: '', - sdp: '' - }); - this.remoteDescription = new RTCSessionDescription({ - type: '', - sdp: '' - }); - this.signalingState = 'stable'; - this.iceConnectionState = 'new'; - this.iceGatheringState = 'new'; - - this.iceOptions = { - gatherPolicy: 'all', - iceServers: [] - }; - if (config && config.iceTransportPolicy) { - switch (config.iceTransportPolicy) { - case 'all': - case 'relay': - this.iceOptions.gatherPolicy = config.iceTransportPolicy; - break; - case 'none': - // FIXME: remove once implementation and spec have added this. - throw new TypeError('iceTransportPolicy "none" not supported'); - default: - // don't set iceTransportPolicy. - break; - } - } - this.usingBundle = config && config.bundlePolicy === 'max-bundle'; - - if (config && config.iceServers) { - // Edge does not like - // 1) stun: - // 2) turn: that does not have all of turn:host:port?transport=udp - // 3) turn: with ipv6 addresses - var iceServers = JSON.parse(JSON.stringify(config.iceServers)); - this.iceOptions.iceServers = iceServers.filter(function(server) { - if (server && server.urls) { - var urls = server.urls; - if (typeof urls === 'string') { - urls = [urls]; - } - urls = urls.filter(function(url) { - return (url.indexOf('turn:') === 0 && - url.indexOf('transport=udp') !== -1 && - url.indexOf('turn:[') === -1) || - (url.indexOf('stun:') === 0 && - browserDetails.version >= 14393); - })[0]; - return !!urls; - } - return false; - }); - } - this._config = config; - - // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... - // everything that is needed to describe a SDP m-line. - this.transceivers = []; - - // since the iceGatherer is currently created in createOffer but we - // must not emit candidates until after setLocalDescription we buffer - // them in this array. - this._localIceCandidatesBuffer = []; - }; - - window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { - var self = this; - var sections = SDPUtils.splitSections(self.localDescription.sdp); - // FIXME: need to apply ice candidates in a way which is async but - // in-order - this._localIceCandidatesBuffer.forEach(function(event) { - var end = !event.candidate || Object.keys(event.candidate).length === 0; - if (end) { - for (var j = 1; j < sections.length; j++) { - if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) { - sections[j] += 'a=end-of-candidates\r\n'; - } - } - } else if (event.candidate.candidate.indexOf('typ endOfCandidates') - === -1) { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=' + event.candidate.candidate + '\r\n'; - } - self.localDescription.sdp = sections.join(''); - self.dispatchEvent(event); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - if (!event.candidate && self.iceGatheringState !== 'complete') { - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - if (complete) { - self.iceGatheringState = 'complete'; - } - } - }); - this._localIceCandidatesBuffer = []; - }; - - window.RTCPeerConnection.prototype.getConfiguration = function() { - return this._config; - }; - - window.RTCPeerConnection.prototype.addStream = function(stream) { - // Clone is necessary for local demos mostly, attaching directly - // to two different senders does not work (build 10547). - var clonedStream = stream.clone(); - stream.getTracks().forEach(function(track, idx) { - var clonedTrack = clonedStream.getTracks()[idx]; - track.addEventListener('enabled', function(event) { - clonedTrack.enabled = event.enabled; - }); - }); - this.localStreams.push(clonedStream); - this._maybeFireNegotiationNeeded(); - }; - - window.RTCPeerConnection.prototype.removeStream = function(stream) { - var idx = this.localStreams.indexOf(stream); - if (idx > -1) { - this.localStreams.splice(idx, 1); - this._maybeFireNegotiationNeeded(); - } - }; - - window.RTCPeerConnection.prototype.getSenders = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpSender; - }) - .map(function(transceiver) { - return transceiver.rtpSender; - }); - }; - - window.RTCPeerConnection.prototype.getReceivers = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpReceiver; - }) - .map(function(transceiver) { - return transceiver.rtpReceiver; - }); - }; - - // Determines the intersection of local and remote capabilities. - window.RTCPeerConnection.prototype._getCommonCapabilities = - function(localCapabilities, remoteCapabilities) { - var commonCapabilities = { - codecs: [], - headerExtensions: [], - fecMechanisms: [] - }; - localCapabilities.codecs.forEach(function(lCodec) { - for (var i = 0; i < remoteCapabilities.codecs.length; i++) { - var rCodec = remoteCapabilities.codecs[i]; - if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && - lCodec.clockRate === rCodec.clockRate) { - // number of channels is the highest common number of channels - rCodec.numChannels = Math.min(lCodec.numChannels, - rCodec.numChannels); - // push rCodec so we reply with offerer payload type - commonCapabilities.codecs.push(rCodec); - - // determine common feedback mechanisms - rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { - for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { - if (lCodec.rtcpFeedback[j].type === fb.type && - lCodec.rtcpFeedback[j].parameter === fb.parameter) { - return true; - } - } - return false; - }); - // FIXME: also need to determine .parameters - // see https://github.com/openpeer/ortc/issues/569 - break; - } - } - }); - - localCapabilities.headerExtensions - .forEach(function(lHeaderExtension) { - for (var i = 0; i < remoteCapabilities.headerExtensions.length; - i++) { - var rHeaderExtension = remoteCapabilities.headerExtensions[i]; - if (lHeaderExtension.uri === rHeaderExtension.uri) { - commonCapabilities.headerExtensions.push(rHeaderExtension); - break; - } - } - }); - - // FIXME: fecMechanisms - return commonCapabilities; - }; - - // Create ICE gatherer, ICE transport and DTLS transport. - window.RTCPeerConnection.prototype._createIceAndDtlsTransports = - function(mid, sdpMLineIndex) { - var self = this; - var iceGatherer = new RTCIceGatherer(self.iceOptions); - var iceTransport = new RTCIceTransport(iceGatherer); - iceGatherer.onlocalcandidate = function(evt) { - var event = new Event('icecandidate'); - event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; - - var cand = evt.candidate; - var end = !cand || Object.keys(cand).length === 0; - // Edge emits an empty object for RTCIceCandidateComplete‥ - if (end) { - // polyfill since RTCIceGatherer.state is not implemented in - // Edge 10547 yet. - if (iceGatherer.state === undefined) { - iceGatherer.state = 'completed'; - } - - // Emit a candidate with type endOfCandidates to make the samples - // work. Edge requires addIceCandidate with this empty candidate - // to start checking. The real solution is to signal - // end-of-candidates to the other side when getting the null - // candidate but some apps (like the samples) don't do that. - event.candidate.candidate = - 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; - } else { - // RTCIceCandidate doesn't have a component, needs to be added - cand.component = iceTransport.component === 'RTCP' ? 2 : 1; - event.candidate.candidate = SDPUtils.writeCandidate(cand); - } - - // update local description. - var sections = SDPUtils.splitSections(self.localDescription.sdp); - if (event.candidate.candidate.indexOf('typ endOfCandidates') - === -1) { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=' + event.candidate.candidate + '\r\n'; - } else { - sections[event.candidate.sdpMLineIndex + 1] += - 'a=end-of-candidates\r\n'; - } - self.localDescription.sdp = sections.join(''); - - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - - // Emit candidate if localDescription is set. - // Also emits null candidate when all gatherers are complete. - switch (self.iceGatheringState) { - case 'new': - self._localIceCandidatesBuffer.push(event); - if (end && complete) { - self._localIceCandidatesBuffer.push( - new Event('icecandidate')); - } - break; - case 'gathering': - self._emitBufferedCandidates(); - self.dispatchEvent(event); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - if (complete) { - self.dispatchEvent(new Event('icecandidate')); - if (self.onicecandidate !== null) { - self.onicecandidate(new Event('icecandidate')); - } - self.iceGatheringState = 'complete'; - } - break; - case 'complete': - // should not happen... currently! - break; - default: // no-op. - break; - } - }; - iceTransport.onicestatechange = function() { - self._updateConnectionState(); - }; - - var dtlsTransport = new RTCDtlsTransport(iceTransport); - dtlsTransport.ondtlsstatechange = function() { - self._updateConnectionState(); - }; - dtlsTransport.onerror = function() { - // onerror does not set state to failed by itself. - dtlsTransport.state = 'failed'; - self._updateConnectionState(); - }; - - return { - iceGatherer: iceGatherer, - iceTransport: iceTransport, - dtlsTransport: dtlsTransport - }; - }; - - // Start the RTP Sender and Receiver for a transceiver. - window.RTCPeerConnection.prototype._transceive = function(transceiver, - send, recv) { - var params = this._getCommonCapabilities(transceiver.localCapabilities, - transceiver.remoteCapabilities); - if (send && transceiver.rtpSender) { - params.encodings = transceiver.sendEncodingParameters; - params.rtcp = { - cname: SDPUtils.localCName - }; - if (transceiver.recvEncodingParameters.length) { - params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; - } - transceiver.rtpSender.send(params); - } - if (recv && transceiver.rtpReceiver) { - // remove RTX field in Edge 14942 - if (transceiver.kind === 'video' - && transceiver.recvEncodingParameters) { - transceiver.recvEncodingParameters.forEach(function(p) { - delete p.rtx; - }); - } - params.encodings = transceiver.recvEncodingParameters; - params.rtcp = { - cname: transceiver.cname - }; - if (transceiver.sendEncodingParameters.length) { - params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; - } - transceiver.rtpReceiver.receive(params); - } - }; - - window.RTCPeerConnection.prototype.setLocalDescription = - function(description) { - var self = this; - var sections; - var sessionpart; - if (description.type === 'offer') { - // FIXME: What was the purpose of this empty if statement? - // if (!this._pendingOffer) { - // } else { - if (this._pendingOffer) { - // VERY limited support for SDP munging. Limited to: - // * changing the order of codecs - sections = SDPUtils.splitSections(description.sdp); - sessionpart = sections.shift(); - sections.forEach(function(mediaSection, sdpMLineIndex) { - var caps = SDPUtils.parseRtpParameters(mediaSection); - self._pendingOffer[sdpMLineIndex].localCapabilities = caps; - }); - this.transceivers = this._pendingOffer; - delete this._pendingOffer; - } - } else if (description.type === 'answer') { - sections = SDPUtils.splitSections(self.remoteDescription.sdp); - sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var transceiver = self.transceivers[sdpMLineIndex]; - var iceGatherer = transceiver.iceGatherer; - var iceTransport = transceiver.iceTransport; - var dtlsTransport = transceiver.dtlsTransport; - var localCapabilities = transceiver.localCapabilities; - var remoteCapabilities = transceiver.remoteCapabilities; - - var rejected = mediaSection.split('\n', 1)[0] - .split(' ', 2)[1] === '0'; - - if (!rejected && !transceiver.isDatachannel) { - var remoteIceParameters = SDPUtils.getIceParameters( - mediaSection, sessionpart); - if (isIceLite) { - var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') - .map(function(cand) { - return SDPUtils.parseCandidate(cand); - }) - .filter(function(cand) { - return cand.component === '1'; - }); - // ice-lite only includes host candidates in the SDP so we can - // use setRemoteCandidates (which implies an - // RTCIceCandidateComplete) - if (cands.length) { - iceTransport.setRemoteCandidates(cands); - } - } - var remoteDtlsParameters = SDPUtils.getDtlsParameters( - mediaSection, sessionpart); - if (isIceLite) { - remoteDtlsParameters.role = 'server'; - } - - if (!self.usingBundle || sdpMLineIndex === 0) { - iceTransport.start(iceGatherer, remoteIceParameters, - isIceLite ? 'controlling' : 'controlled'); - dtlsTransport.start(remoteDtlsParameters); - } - - // Calculate intersection of capabilities. - var params = self._getCommonCapabilities(localCapabilities, - remoteCapabilities); - - // Start the RTCRtpSender. The RTCRtpReceiver for this - // transceiver has already been started in setRemoteDescription. - self._transceive(transceiver, - params.codecs.length > 0, - false); - } - }); - } - - this.localDescription = { - type: description.type, - sdp: description.sdp - }; - switch (description.type) { - case 'offer': - this._updateSignalingState('have-local-offer'); - break; - case 'answer': - this._updateSignalingState('stable'); - break; - default: - throw new TypeError('unsupported type "' + description.type + - '"'); - } - - // If a success callback was provided, emit ICE candidates after it - // has been executed. Otherwise, emit callback after the Promise is - // resolved. - var hasCallback = arguments.length > 1 && - typeof arguments[1] === 'function'; - if (hasCallback) { - var cb = arguments[1]; - window.setTimeout(function() { - cb(); - if (self.iceGatheringState === 'new') { - self.iceGatheringState = 'gathering'; - } - self._emitBufferedCandidates(); - }, 0); - } - var p = Promise.resolve(); - p.then(function() { - if (!hasCallback) { - if (self.iceGatheringState === 'new') { - self.iceGatheringState = 'gathering'; - } - // Usually candidates will be emitted earlier. - window.setTimeout(self._emitBufferedCandidates.bind(self), 500); - } - }); - return p; - }; - - window.RTCPeerConnection.prototype.setRemoteDescription = - function(description) { - var self = this; - var stream = new MediaStream(); - var receiverList = []; - var sections = SDPUtils.splitSections(description.sdp); - var sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - this.usingBundle = SDPUtils.matchPrefix(sessionpart, - 'a=group:BUNDLE ').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].substr(2).split(' '); - var kind = mline[0]; - var rejected = mline[1] === '0'; - var direction = SDPUtils.getDirection(mediaSection, sessionpart); - - var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); - if (mid.length) { - mid = mid[0].substr(6); - } else { - mid = SDPUtils.generateIdentifier(); - } - - // Reject datachannels which are not implemented yet. - if (kind === 'application' && mline[2] === 'DTLS/SCTP') { - self.transceivers[sdpMLineIndex] = { - mid: mid, - isDatachannel: true - }; - return; - } - - var transceiver; - var iceGatherer; - var iceTransport; - var dtlsTransport; - var rtpSender; - var rtpReceiver; - var sendEncodingParameters; - var recvEncodingParameters; - var localCapabilities; - - var track; - // FIXME: ensure the mediaSection has rtcp-mux set. - var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); - var remoteIceParameters; - var remoteDtlsParameters; - if (!rejected) { - remoteIceParameters = SDPUtils.getIceParameters(mediaSection, - sessionpart); - remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, - sessionpart); - remoteDtlsParameters.role = 'client'; - } - recvEncodingParameters = - SDPUtils.parseRtpEncodingParameters(mediaSection); - - var cname; - // Gets the first SSRC. Note that with RTX there might be multiple - // SSRCs. - var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') - .map(function(line) { - return SDPUtils.parseSsrcMedia(line); - }) - .filter(function(obj) { - return obj.attribute === 'cname'; - })[0]; - if (remoteSsrc) { - cname = remoteSsrc.value; - } - - var isComplete = SDPUtils.matchPrefix(mediaSection, - 'a=end-of-candidates', sessionpart).length > 0; - var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') - .map(function(cand) { - return SDPUtils.parseCandidate(cand); - }) - .filter(function(cand) { - return cand.component === '1'; - }); - if (description.type === 'offer' && !rejected) { - var transports = self.usingBundle && sdpMLineIndex > 0 ? { - iceGatherer: self.transceivers[0].iceGatherer, - iceTransport: self.transceivers[0].iceTransport, - dtlsTransport: self.transceivers[0].dtlsTransport - } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); - - if (isComplete) { - transports.iceTransport.setRemoteCandidates(cands); - } - - localCapabilities = RTCRtpReceiver.getCapabilities(kind); - - // filter RTX until additional stuff needed for RTX is implemented - // in adapter.js - localCapabilities.codecs = localCapabilities.codecs.filter( - function(codec) { - return codec.name !== 'rtx'; - }); - - sendEncodingParameters = [{ - ssrc: (2 * sdpMLineIndex + 2) * 1001 - }]; - - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); - - track = rtpReceiver.track; - receiverList.push([track, rtpReceiver]); - // FIXME: not correct when there are multiple streams but that is - // not currently supported in this shim. - stream.addTrack(track); - - // FIXME: look at direction. - if (self.localStreams.length > 0 && - self.localStreams[0].getTracks().length >= sdpMLineIndex) { - var localTrack; - if (kind === 'audio') { - localTrack = self.localStreams[0].getAudioTracks()[0]; - } else if (kind === 'video') { - localTrack = self.localStreams[0].getVideoTracks()[0]; - } - if (localTrack) { - rtpSender = new RTCRtpSender(localTrack, - transports.dtlsTransport); - } - } - - self.transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: remoteCapabilities, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - cname: cname, - sendEncodingParameters: sendEncodingParameters, - recvEncodingParameters: recvEncodingParameters - }; - // Start the RTCRtpReceiver now. The RTPSender is started in - // setLocalDescription. - self._transceive(self.transceivers[sdpMLineIndex], - false, - direction === 'sendrecv' || direction === 'sendonly'); - } else if (description.type === 'answer' && !rejected) { - transceiver = self.transceivers[sdpMLineIndex]; - iceGatherer = transceiver.iceGatherer; - iceTransport = transceiver.iceTransport; - dtlsTransport = transceiver.dtlsTransport; - rtpSender = transceiver.rtpSender; - rtpReceiver = transceiver.rtpReceiver; - sendEncodingParameters = transceiver.sendEncodingParameters; - localCapabilities = transceiver.localCapabilities; - - self.transceivers[sdpMLineIndex].recvEncodingParameters = - recvEncodingParameters; - self.transceivers[sdpMLineIndex].remoteCapabilities = - remoteCapabilities; - self.transceivers[sdpMLineIndex].cname = cname; - - if ((isIceLite || isComplete) && cands.length) { - iceTransport.setRemoteCandidates(cands); - } - if (!self.usingBundle || sdpMLineIndex === 0) { - iceTransport.start(iceGatherer, remoteIceParameters, - 'controlling'); - dtlsTransport.start(remoteDtlsParameters); - } - - self._transceive(transceiver, - direction === 'sendrecv' || direction === 'recvonly', - direction === 'sendrecv' || direction === 'sendonly'); - - if (rtpReceiver && - (direction === 'sendrecv' || direction === 'sendonly')) { - track = rtpReceiver.track; - receiverList.push([track, rtpReceiver]); - stream.addTrack(track); - } else { - // FIXME: actually the receiver should be created later. - delete transceiver.rtpReceiver; - } - } - }); - - this.remoteDescription = { - type: description.type, - sdp: description.sdp - }; - switch (description.type) { - case 'offer': - this._updateSignalingState('have-remote-offer'); - break; - case 'answer': - this._updateSignalingState('stable'); - break; - default: - throw new TypeError('unsupported type "' + description.type + - '"'); - } - if (stream.getTracks().length) { - self.remoteStreams.push(stream); - window.setTimeout(function() { - var event = new Event('addstream'); - event.stream = stream; - self.dispatchEvent(event); - if (self.onaddstream !== null) { - window.setTimeout(function() { - self.onaddstream(event); - }, 0); - } - - receiverList.forEach(function(item) { - var track = item[0]; - var receiver = item[1]; - var trackEvent = new Event('track'); - trackEvent.track = track; - trackEvent.receiver = receiver; - trackEvent.streams = [stream]; - self.dispatchEvent(event); - if (self.ontrack !== null) { - window.setTimeout(function() { - self.ontrack(trackEvent); - }, 0); - } - }); - }, 0); - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; - - window.RTCPeerConnection.prototype.close = function() { - this.transceivers.forEach(function(transceiver) { - /* not yet - if (transceiver.iceGatherer) { - transceiver.iceGatherer.close(); - } - */ - if (transceiver.iceTransport) { - transceiver.iceTransport.stop(); - } - if (transceiver.dtlsTransport) { - transceiver.dtlsTransport.stop(); - } - if (transceiver.rtpSender) { - transceiver.rtpSender.stop(); - } - if (transceiver.rtpReceiver) { - transceiver.rtpReceiver.stop(); - } - }); - // FIXME: clean up tracks, local streams, remote streams, etc - this._updateSignalingState('closed'); - }; - - // Update the signaling state. - window.RTCPeerConnection.prototype._updateSignalingState = - function(newState) { - this.signalingState = newState; - var event = new Event('signalingstatechange'); - this.dispatchEvent(event); - if (this.onsignalingstatechange !== null) { - this.onsignalingstatechange(event); - } - }; - - // Determine whether to fire the negotiationneeded event. - window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = - function() { - // Fire away (for now). - var event = new Event('negotiationneeded'); - this.dispatchEvent(event); - if (this.onnegotiationneeded !== null) { - this.onnegotiationneeded(event); - } - }; - - // Update the connection state. - window.RTCPeerConnection.prototype._updateConnectionState = function() { - var self = this; - var newState; - var states = { - 'new': 0, - closed: 0, - connecting: 0, - checking: 0, - connected: 0, - completed: 0, - failed: 0 - }; - this.transceivers.forEach(function(transceiver) { - states[transceiver.iceTransport.state]++; - states[transceiver.dtlsTransport.state]++; - }); - // ICETransport.completed and connected are the same for this purpose. - states.connected += states.completed; - - newState = 'new'; - if (states.failed > 0) { - newState = 'failed'; - } else if (states.connecting > 0 || states.checking > 0) { - newState = 'connecting'; - } else if (states.disconnected > 0) { - newState = 'disconnected'; - } else if (states.new > 0) { - newState = 'new'; - } else if (states.connected > 0 || states.completed > 0) { - newState = 'connected'; - } - - if (newState !== self.iceConnectionState) { - self.iceConnectionState = newState; - var event = new Event('iceconnectionstatechange'); - this.dispatchEvent(event); - if (this.oniceconnectionstatechange !== null) { - this.oniceconnectionstatechange(event); - } - } - }; - - window.RTCPeerConnection.prototype.createOffer = function() { - var self = this; - if (this._pendingOffer) { - throw new Error('createOffer called while there is a pending offer.'); - } - var offerOptions; - if (arguments.length === 1 && typeof arguments[0] !== 'function') { - offerOptions = arguments[0]; - } else if (arguments.length === 3) { - offerOptions = arguments[2]; - } - - var tracks = []; - var numAudioTracks = 0; - var numVideoTracks = 0; - // Default to sendrecv. - if (this.localStreams.length) { - numAudioTracks = this.localStreams[0].getAudioTracks().length; - numVideoTracks = this.localStreams[0].getVideoTracks().length; - } - // Determine number of audio and video tracks we need to send/recv. - if (offerOptions) { - // Reject Chrome legacy constraints. - if (offerOptions.mandatory || offerOptions.optional) { - throw new TypeError( - 'Legacy mandatory/optional constraints not supported.'); - } - if (offerOptions.offerToReceiveAudio !== undefined) { - numAudioTracks = offerOptions.offerToReceiveAudio; - } - if (offerOptions.offerToReceiveVideo !== undefined) { - numVideoTracks = offerOptions.offerToReceiveVideo; - } - } - if (this.localStreams.length) { - // Push local streams. - this.localStreams[0].getTracks().forEach(function(track) { - tracks.push({ - kind: track.kind, - track: track, - wantReceive: track.kind === 'audio' ? - numAudioTracks > 0 : numVideoTracks > 0 - }); - if (track.kind === 'audio') { - numAudioTracks--; - } else if (track.kind === 'video') { - numVideoTracks--; - } - }); - } - // Create M-lines for recvonly streams. - while (numAudioTracks > 0 || numVideoTracks > 0) { - if (numAudioTracks > 0) { - tracks.push({ - kind: 'audio', - wantReceive: true - }); - numAudioTracks--; - } - if (numVideoTracks > 0) { - tracks.push({ - kind: 'video', - wantReceive: true - }); - numVideoTracks--; - } - } - - var sdp = SDPUtils.writeSessionBoilerplate(); - var transceivers = []; - tracks.forEach(function(mline, sdpMLineIndex) { - // For each track, create an ice gatherer, ice transport, - // dtls transport, potentially rtpsender and rtpreceiver. - var track = mline.track; - var kind = mline.kind; - var mid = SDPUtils.generateIdentifier(); - - var transports = self.usingBundle && sdpMLineIndex > 0 ? { - iceGatherer: transceivers[0].iceGatherer, - iceTransport: transceivers[0].iceTransport, - dtlsTransport: transceivers[0].dtlsTransport - } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); - - var localCapabilities = RTCRtpSender.getCapabilities(kind); - // filter RTX until additional stuff needed for RTX is implemented - // in adapter.js - localCapabilities.codecs = localCapabilities.codecs.filter( - function(codec) { - return codec.name !== 'rtx'; - }); - localCapabilities.codecs.forEach(function(codec) { - // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 - // by adding level-asymmetry-allowed=1 - if (codec.name === 'H264' && - codec.parameters['level-asymmetry-allowed'] === undefined) { - codec.parameters['level-asymmetry-allowed'] = '1'; - } - }); - - var rtpSender; - var rtpReceiver; - - // generate an ssrc now, to be used later in rtpSender.send - var sendEncodingParameters = [{ - ssrc: (2 * sdpMLineIndex + 1) * 1001 - }]; - if (track) { - rtpSender = new RTCRtpSender(track, transports.dtlsTransport); - } - - if (mline.wantReceive) { - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); - } - - transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: null, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - sendEncodingParameters: sendEncodingParameters, - recvEncodingParameters: null - }; - }); - if (this.usingBundle) { - sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; - } - tracks.forEach(function(mline, sdpMLineIndex) { - var transceiver = transceivers[sdpMLineIndex]; - sdp += SDPUtils.writeMediaSection(transceiver, - transceiver.localCapabilities, 'offer', self.localStreams[0]); - }); - - this._pendingOffer = transceivers; - var desc = new RTCSessionDescription({ - type: 'offer', - sdp: sdp - }); - if (arguments.length && typeof arguments[0] === 'function') { - window.setTimeout(arguments[0], 0, desc); - } - return Promise.resolve(desc); - }; - - window.RTCPeerConnection.prototype.createAnswer = function() { - var self = this; - - var sdp = SDPUtils.writeSessionBoilerplate(); - if (this.usingBundle) { - sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; - } - this.transceivers.forEach(function(transceiver) { - if (transceiver.isDatachannel) { - sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + - 'c=IN IP4 0.0.0.0\r\n' + - 'a=mid:' + transceiver.mid + '\r\n'; - return; - } - // Calculate intersection of capabilities. - var commonCapabilities = self._getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); - - sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, - 'answer', self.localStreams[0]); - }); - - var desc = new RTCSessionDescription({ - type: 'answer', - sdp: sdp - }); - if (arguments.length && typeof arguments[0] === 'function') { - window.setTimeout(arguments[0], 0, desc); - } - return Promise.resolve(desc); - }; - - window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - if (!candidate) { - this.transceivers.forEach(function(transceiver) { - transceiver.iceTransport.addRemoteCandidate({}); - }); - } else { - var mLineIndex = candidate.sdpMLineIndex; - if (candidate.sdpMid) { - for (var i = 0; i < this.transceivers.length; i++) { - if (this.transceivers[i].mid === candidate.sdpMid) { - mLineIndex = i; - break; - } - } - } - var transceiver = this.transceivers[mLineIndex]; - if (transceiver) { - var cand = Object.keys(candidate.candidate).length > 0 ? - SDPUtils.parseCandidate(candidate.candidate) : {}; - // Ignore Chrome's invalid candidates since Edge does not like them. - if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { - return; - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component !== '1') { - return; - } - // A dirty hack to make samples work. - if (cand.type === 'endOfCandidates') { - cand = {}; - } - transceiver.iceTransport.addRemoteCandidate(cand); - - // update the remoteDescription. - var sections = SDPUtils.splitSections(this.remoteDescription.sdp); - sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() - : 'a=end-of-candidates') + '\r\n'; - this.remoteDescription.sdp = sections.join(''); - } - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; - - window.RTCPeerConnection.prototype.getStats = function() { - var promises = []; - this.transceivers.forEach(function(transceiver) { - ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', - 'dtlsTransport'].forEach(function(method) { - if (transceiver[method]) { - promises.push(transceiver[method].getStats()); - } - }); - }); - var cb = arguments.length > 1 && typeof arguments[1] === 'function' && - arguments[1]; - return new Promise(function(resolve) { - // shim getStats with maplike support - var results = new Map(); - Promise.all(promises).then(function(res) { - res.forEach(function(result) { - Object.keys(result).forEach(function(id) { - results.set(id, result[id]); - results[id] = result[id]; - }); - }); - if (cb) { - window.setTimeout(cb, 0, results); - } - resolve(results); - }); - }); - }; - } - }; - -// Expose public methods. - module.exports = { - shimPeerConnection: edgeShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia') - }; - -},{"../utils":98,"./getusermedia":94,"sdp":62}],94:[function(require,module,exports){ - arguments[4][34][0].apply(exports,arguments) -},{"dup":34}],95:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var browserDetails = require('../utils').browserDetails; - - var firefoxShim = { - shimOnTrack: function() { - if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in - window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { - get: function() { - return this._ontrack; - }, - set: function(f) { - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - this.removeEventListener('addstream', this._ontrackpoly); - } - this.addEventListener('track', this._ontrack = f); - this.addEventListener('addstream', this._ontrackpoly = function(e) { - e.stream.getTracks().forEach(function(track) { - var event = new Event('track'); - event.track = track; - event.receiver = {track: track}; - event.streams = [e.stream]; - this.dispatchEvent(event); - }.bind(this)); - }.bind(this)); - } - }); - } - }, - - shimSourceObject: function() { - // Firefox has supported mozSrcObject since FF22, unprefixed in 42. - if (typeof window === 'object') { - if (window.HTMLMediaElement && - !('srcObject' in window.HTMLMediaElement.prototype)) { - // Shim the srcObject property, once, when HTMLMediaElement is found. - Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { - get: function() { - return this.mozSrcObject; - }, - set: function(stream) { - this.mozSrcObject = stream; - } - }); - } - } - }, - - shimPeerConnection: function() { - if (typeof window !== 'object' || !(window.RTCPeerConnection || - window.mozRTCPeerConnection)) { - return; // probably media.peerconnection.enabled=false in about:config - } - // The RTCPeerConnection object. - if (!window.RTCPeerConnection) { - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - if (browserDetails.version < 38) { - // .urls is not supported in FF < 38. - // create RTCIceServers with a single url. - if (pcConfig && pcConfig.iceServers) { - var newIceServers = []; - for (var i = 0; i < pcConfig.iceServers.length; i++) { - var server = pcConfig.iceServers[i]; - if (server.hasOwnProperty('urls')) { - for (var j = 0; j < server.urls.length; j++) { - var newServer = { - url: server.urls[j] - }; - if (server.urls[j].indexOf('turn') === 0) { - newServer.username = server.username; - newServer.credential = server.credential; - } - newIceServers.push(newServer); - } - } else { - newIceServers.push(pcConfig.iceServers[i]); - } - } - pcConfig.iceServers = newIceServers; - } - } - return new mozRTCPeerConnection(pcConfig, pcConstraints); - }; - window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (mozRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - return mozRTCPeerConnection.generateCertificate; - } - }); - } - - window.RTCSessionDescription = mozRTCSessionDescription; - window.RTCIceCandidate = mozRTCIceCandidate; - } - - // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] - .forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate') ? - RTCIceCandidate : RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }; - }); - - // support for addIceCandidate(null or undefined) - var nativeAddIceCandidate = - RTCPeerConnection.prototype.addIceCandidate; - RTCPeerConnection.prototype.addIceCandidate = function() { - if (!arguments[0]) { - if (arguments[1]) { - arguments[1].apply(null); - } - return Promise.resolve(); - } - return nativeAddIceCandidate.apply(this, arguments); - }; - - if (browserDetails.version < 48) { - // shim getStats with maplike support - var makeMapStats = function(stats) { - var map = new Map(); - Object.keys(stats).forEach(function(key) { - map.set(key, stats[key]); - map[key] = stats[key]; - }); - return map; - }; - - var nativeGetStats = RTCPeerConnection.prototype.getStats; - RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { - return nativeGetStats.apply(this, [selector || null]) - .then(function(stats) { - return makeMapStats(stats); - }) - .then(onSucc, onErr); - }; - } - } - }; - -// Expose public methods. - module.exports = { - shimOnTrack: firefoxShim.shimOnTrack, - shimSourceObject: firefoxShim.shimSourceObject, - shimPeerConnection: firefoxShim.shimPeerConnection, - shimGetUserMedia: require('./getusermedia') - }; - -},{"../utils":98,"./getusermedia":96}],96:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var logging = require('../utils').log; - var browserDetails = require('../utils').browserDetails; - -// Expose public methods. - module.exports = function() { - var shimError_ = function(e) { - return { - name: { - SecurityError: 'NotAllowedError', - PermissionDeniedError: 'NotAllowedError' - }[e.name] || e.name, - message: { - 'The operation is insecure.': 'The request is not allowed by the ' + - 'user agent or the platform in the current context.' - }[e.message] || e.message, - constraint: e.constraint, - toString: function() { - return this.name + (this.message && ': ') + this.message; - } - }; - }; - - // getUserMedia constraints shim. - var getUserMedia_ = function(constraints, onSuccess, onError) { - var constraintsToFF37_ = function(c) { - if (typeof c !== 'object' || c.require) { - return c; - } - var require = []; - Object.keys(c).forEach(function(key) { - if (key === 'require' || key === 'advanced' || key === 'mediaSource') { - return; - } - var r = c[key] = (typeof c[key] === 'object') ? - c[key] : {ideal: c[key]}; - if (r.min !== undefined || - r.max !== undefined || r.exact !== undefined) { - require.push(key); - } - if (r.exact !== undefined) { - if (typeof r.exact === 'number') { - r. min = r.max = r.exact; - } else { - c[key] = r.exact; - } - delete r.exact; - } - if (r.ideal !== undefined) { - c.advanced = c.advanced || []; - var oc = {}; - if (typeof r.ideal === 'number') { - oc[key] = {min: r.ideal, max: r.ideal}; - } else { - oc[key] = r.ideal; - } - c.advanced.push(oc); - delete r.ideal; - if (!Object.keys(r).length) { - delete c[key]; - } - } - }); - if (require.length) { - c.require = require; - } - return c; - }; - constraints = JSON.parse(JSON.stringify(constraints)); - if (browserDetails.version < 38) { - logging('spec: ' + JSON.stringify(constraints)); - if (constraints.audio) { - constraints.audio = constraintsToFF37_(constraints.audio); - } - if (constraints.video) { - constraints.video = constraintsToFF37_(constraints.video); - } - logging('ff37: ' + JSON.stringify(constraints)); - } - return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { - onError(shimError_(e)); - }); - }; - - // Returns the result of getUserMedia as a Promise. - var getUserMediaPromise_ = function(constraints) { - return new Promise(function(resolve, reject) { - getUserMedia_(constraints, resolve, reject); - }); - }; - - // Shim for mediaDevices on older versions. - if (!navigator.mediaDevices) { - navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, - addEventListener: function() { }, - removeEventListener: function() { } - }; - } - navigator.mediaDevices.enumerateDevices = - navigator.mediaDevices.enumerateDevices || function() { - return new Promise(function(resolve) { - var infos = [ - {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, - {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} - ]; - resolve(infos); - }); - }; - - if (browserDetails.version < 41) { - // Work around http://bugzil.la/1169665 - var orgEnumerateDevices = - navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); - navigator.mediaDevices.enumerateDevices = function() { - return orgEnumerateDevices().then(undefined, function(e) { - if (e.name === 'NotFoundError') { - return []; - } - throw e; - }); - }; - } - if (browserDetails.version < 49) { - var origGetUserMedia = navigator.mediaDevices.getUserMedia. - bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(c) { - return origGetUserMedia(c).then(function(stream) { - // Work around https://bugzil.la/802326 - if (c.audio && !stream.getAudioTracks().length || - c.video && !stream.getVideoTracks().length) { - stream.getTracks().forEach(function(track) { - track.stop(); - }); - throw new DOMException('The object can not be found here.', - 'NotFoundError'); - } - return stream; - }, function(e) { - return Promise.reject(shimError_(e)); - }); - }; - } - navigator.getUserMedia = function(constraints, onSuccess, onError) { - if (browserDetails.version < 44) { - return getUserMedia_(constraints, onSuccess, onError); - } - // Replace Firefox 44+'s deprecation warning with unprefixed version. - console.warn('navigator.getUserMedia has been replaced by ' + - 'navigator.mediaDevices.getUserMedia'); - navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); - }; - }; - -},{"../utils":98}],97:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - 'use strict'; - var safariShim = { - // TODO: DrAlex, should be here, double check against LayoutTests - // shimOnTrack: function() { }, - - // TODO: once the back-end for the mac port is done, add. - // TODO: check for webkitGTK+ - // shimPeerConnection: function() { }, - - shimGetUserMedia: function() { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - }; - -// Expose public methods. - module.exports = { - shimGetUserMedia: safariShim.shimGetUserMedia - // TODO - // shimOnTrack: safariShim.shimOnTrack, - // shimPeerConnection: safariShim.shimPeerConnection - }; - -},{}],98:[function(require,module,exports){ - /* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ - /* eslint-env node */ - 'use strict'; - - var logDisabled_ = true; - -// Utility methods. - var utils = { - disableLog: function(bool) { - if (typeof bool !== 'boolean') { - return new Error('Argument type: ' + typeof bool + - '. Please use a boolean.'); - } - logDisabled_ = bool; - return (bool) ? 'adapter.js logging disabled' : - 'adapter.js logging enabled'; - }, - - log: function() { - if (typeof window === 'object') { - if (logDisabled_) { - return; - } - if (typeof console !== 'undefined' && typeof console.log === 'function') { - console.log.apply(console, arguments); - } - } - }, - - /** - * Extract browser version out of the provided user agent string. - * - * @param {!string} uastring userAgent string. - * @param {!string} expr Regular expression used as match criteria. - * @param {!number} pos position in the version string to be returned. - * @return {!number} browser version. - */ - extractVersion: function(uastring, expr, pos) { - var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); - }, - - /** - * Browser detector. - * - * @return {object} result containing browser and version - * properties. - */ - detectBrowser: function() { - // Returned result object. - var result = {}; - result.browser = null; - result.version = null; - - // Fail early if it's not a browser - if (typeof window === 'undefined' || !window.navigator) { - result.browser = 'Not a browser.'; - return result; - } - - // Firefox. - if (navigator.mozGetUserMedia) { - result.browser = 'firefox'; - result.version = this.extractVersion(navigator.userAgent, - /Firefox\/([0-9]+)\./, 1); - - // all webkit-based browsers - } else if (navigator.webkitGetUserMedia) { - // Chrome, Chromium, Webview, Opera, all use the chrome shim for now - if (window.webkitRTCPeerConnection) { - result.browser = 'chrome'; - result.version = this.extractVersion(navigator.userAgent, - /Chrom(e|ium)\/([0-9]+)\./, 2); - - // Safari or unknown webkit-based - // for the time being Safari has support for MediaStreams but not webRTC - } else { - // Safari UA substrings of interest for reference: - // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr) - // - safari UI version: Version/9.0.3 (unique to Safari) - // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr) - // - // if the webkit version and safari UI webkit versions are equals, - // ... this is a stable version. - // - // only the internal webkit version is important today to know if - // media streams are supported - // - if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { - result.browser = 'safari'; - result.version = this.extractVersion(navigator.userAgent, - /AppleWebKit\/([0-9]+)\./, 1); - - // unknown webkit-based browser - } else { - result.browser = 'Unsupported webkit-based browser ' + - 'with GUM support but no WebRTC support.'; - return result; - } - } - - // Edge. - } else if (navigator.mediaDevices && - navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { - result.browser = 'edge'; - result.version = this.extractVersion(navigator.userAgent, - /Edge\/(\d+).(\d+)$/, 2); - - // Default fallthrough: not supported. - } else { - result.browser = 'Not a supported browser.'; - return result; - } - - return result; - } - }; - -// Export. - module.exports = { - log: utils.log, - disableLog: utils.disableLog, - browserDetails: utils.detectBrowser(), - extractVersion: utils.extractVersion - }; - -},{}],99:[function(require,module,exports){ -// created by @HenrikJoreteg - var prefix; - var version; - - if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { - prefix = 'moz'; - version = navigator.userAgent.match(/Firefox/) && parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); - } else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { - prefix = 'webkit'; - version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); - } - - var PC = window.RTCPeerConnection || 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:' && - ((prefix === 'webkit' && version >= 26) || - (prefix === 'moz' && version >= 33)) - var AudioContext = window.AudioContext || window.webkitAudioContext; - var videoEl = document.createElement('video'); - var supportVp8 = videoEl && videoEl.canPlayType && videoEl.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, - browserVersion: version, - support: !!PC && !!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, - // constructors - AudioContext: AudioContext, - PeerConnection: PC, - SessionDescription: SessionDescription, - IceCandidate: IceCandidate, - MediaStream: MediaStream, - getUserMedia: getUserMedia - }; - -},{}],100:[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() { } - - WildEmitter.mixin = function (constructor) { - var prototype = constructor.prototype || constructor; - - prototype.isWildEmitter= true; - - // Listen on the given `event` with `fn`. Store a group name if present. - prototype.on = function (event, groupName, fn) { - this.callbacks = this.callbacks || {}; - 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. - 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 - prototype.releaseGroup = function (groupName) { - this.callbacks = this.callbacks || {}; - 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. - prototype.off = function (event, fn) { - this.callbacks = this.callbacks || {}; - 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); - if (callbacks.length === 0) { - delete this.callbacks[event]; - } - return this; - }; - - /// Emit `event` with the given args. - // also calls any `*` handlers - prototype.emit = function (event) { - this.callbacks = this.callbacks || {}; - 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]) { - break; - } - listeners[i].apply(this, args); - } - } - - if (specialCallbacks) { - len = specialCallbacks.length; - listeners = specialCallbacks.slice(); - for (i = 0, len = listeners.length; i < len; ++i) { - if (!listeners[i]) { - break; - } - listeners[i].apply(this, [event].concat(args)); - } - } - - return this; - }; - - // Helper for for finding special wildcard event handlers that match the event - prototype.getWildcardCallbacks = function (eventName) { - this.callbacks = this.callbacks || {}; - 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; - }; - - }; - - WildEmitter.mixin(WildEmitter); - -},{}],101:[function(require,module,exports){ - - /** - * Module dependencies. - */ - - var global = (function() { return this; })(); - - /** - * WebSocket constructor. - */ - - var WebSocket = global.WebSocket || global.MozWebSocket; - - /** - * Module exports. - */ - - module.exports = WebSocket ? ws : null; - - /** - * WebSocket constructor. - * - * The third `opts` options object gets ignored in web browsers, since it's - * non-standard, and throws a TypeError if passed to the constructor. - * See: https://github.com/einaros/ws/issues/227 - * - * @param {String} uri - * @param {Array} protocols (optional) - * @param {Object) opts (optional) - * @api public - */ - - function ws(uri, protocols, opts) { - var instance; - if (protocols) { - instance = new WebSocket(uri, protocols); - } else { - instance = new WebSocket(uri); - } - return instance; - } - - if (WebSocket) ws.prototype = WebSocket.prototype; - -},{}],102:[function(require,module,exports){ - var util = require('util'); - var webrtcSupport = 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 isAllTracksEnded(stream) { - var isAllTracksEnded = true; - stream.getTracks().forEach(function (t) { - isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; - }); - return isAllTracksEnded; - } - - function Peer(options) { - var self = this; - - // call emitter constructor - WildEmitter.call(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.pendingDCMessages = []; // key (datachannel label) -> value (array[pending messages]) - 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('endOfCandidates', function (event) { - self.send('endOfCandidates', event); - }); - this.pc.on('offer', function (offer) { - if (self.parent.config.nick) offer.nick = self.parent.config.nick; - self.send('offer', offer); - }); - this.pc.on('answer', function (answer) { - if (self.parent.config.nick) answer.nick = self.parent.config.nick; - self.send('answer', answer); - }); - 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); - }); - } - - 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') { - if (!this.nick) this.nick = message.payload.nick; - delete message.payload.nick; - this.pc.handleOffer(message.payload, function (err) { - if (err) { - return; - } - // auto-accept - self.pc.answer(function (err, sessionDescription) { - //self.send('answer', sessionDescription); - }); - }); - } else if (message.type === 'answer') { - if (!this.nick) this.nick = message.payload.nick; - delete message.payload.nick; - 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}); - } else if (message.type === 'endOfCandidates') { - // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the - // shim this will only be called in Edge. - var mLines = this.pc.pc.peerconnection.transceivers || []; - mLines.forEach(function (mLine) { - if (mLine.iceTransport) { - mLine.iceTransport.addRemoteCandidate({}); - } - }); - } else if (message.type === 'unshareScreen') { - this.parent.emit('unshareScreen', {id: message.from}); - this.end(); - } - }; - -// send via signaling 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: webrtcSupport.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') { - if (!this.pendingDCMessages.hasOwnProperty(channel)) { - this.pendingDCMessages[channel] = []; - } - this.pendingDCMessages[channel].push(message); - 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 = function () { - self.emit('channelOpen', channel); - // Check if there are messages that could not be send - if (self.pendingDCMessages.hasOwnProperty(channel.label)) { - var pendingMessages = self.pendingDCMessages[channel.label]; - for (var i = 0; i < pendingMessages.length; i++) { - self.sendDirectly(channel.label, pendingMessages[i].type, pendingMessages[i].payload); - } - self.pendingDCMessages[channel.label] = []; - } - }; - }; - -// Fetch or create a data channel by the given name - Peer.prototype.getDataChannel = function (name, opts) { - if (!webrtcSupport.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) { - var pcConfig = this.parent.config.peerConnectionConfig; - if (webrtcSupport.prefix === 'moz' && pcConfig && pcConfig.iceTransports && - candidate.candidate && candidate.candidate.candidate && - candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) { - this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports); - } else { - 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.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; - - this.stream.getTracks().forEach(function (track) { - track.addEventListener('ended', function () { - if (isAllTracksEnded(self.stream)) { - self.end(); - } - }); - }); - - this.parent.emit('peerStreamAdded', this); - } - }; - - Peer.prototype.handleStreamRemoved = function () { - var peerIndex = this.parent.peers.indexOf(this); - if (peerIndex > -1) { - this.parent.peers.splice(peerIndex, 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":26,"rtcpeerconnection":56,"util":89,"webrtcsupport":99,"wildemitter":100}],103:[function(require,module,exports){ - var WebRTC = require('./webrtc'); - var WildEmitter = require('wildemitter'); - var webrtcSupport = require('webrtcsupport'); - var attachMediaStream = require('attachmediastream'); - var mockconsole = require('mockconsole'); - var SocketIoConnection = require('./socketioconnection'); - - function SimpleWebRTC(opts) { - var self = this; - var options = opts || {}; - var config = this.config = { - url: 'https://sandbox.simplewebrtc.com:443/', - socketio: {/* 'force new connection':true*/}, - connection: null, - debug: false, - localVideoEl: '', - remoteVideosEl: '', - enableDataChannels: true, - autoRequestMedia: false, - autoRemoveVideos: true, - adjustPeerVolume: false, - peerVolumeWhenSpeaking: 0.25, - media: { - video: true, - audio: true - }, - receiveMedia: { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 - }, - localVideo: { - autoplay: true, - mirror: true, - muted: true - } - }; - var item, connection; - - // 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 our config from options - for (item in options) { - if (options.hasOwnProperty(item)) { - this.config[item] = options[item]; - } - } - - // attach detected support for convenience - this.capabilities = webrtcSupport; - - // call WildEmitter constructor - WildEmitter.call(this); - - // create default SocketIoConnection if it's not passed in - if (this.config.connection === null) { - connection = this.connection = new SocketIoConnection(this.config); - } else { - connection = this.connection = this.config.connection; - } - - connection.on('connect', function () { - console.warn('CONNECTED'); - self.emit('connectionReady', connection.getSessionid()); - self.sessionReady = true; - self.testReadiness(); - }); - - connection.on('message', function (message) { - var peers = self.webrtc.getPeers(message.from, message.roomType); - var peer; - - if (message.type === 'offer') { - if (peers.length) { - peers.forEach(function (p) { - if (p.sid == message.sid) peer = p; - }); - //if (!peer) peer = peers[0]; // fallback for old protocol versions - } - if (!peer) { - peer = self.webrtc.createPeer({ - id: message.from, - sid: message.sid, - type: message.roomType, - enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen', - sharemyscreen: message.roomType === 'screen' && !message.broadcaster, - broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null - }); - self.emit('createdPeer', peer); - } - peer.handleMessage(message); - } else if (peers.length) { - peers.forEach(function (peer) { - if (message.sid) { - if (peer.sid === message.sid) { - peer.handleMessage(message); - } - } else { - peer.handleMessage(message); - } - }); - } - }); - - connection.on('remove', function (room) { - if (room.id !== self.connection.getSessionid()) { - self.webrtc.removePeers(room.id, room.type); - } - }); - - // instantiate our main WebRTC helper - // using same logger from logic here - opts.logger = this.logger; - opts.debug = false; - this.webrtc = new WebRTC(opts); - - // attach a few methods from underlying lib to simple. - ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers', 'createPeer', 'removePeers'].forEach(function (method) { - self[method] = self.webrtc[method].bind(self.webrtc); - }); - - // proxy events from WebRTC - this.webrtc.on('*', function () { - self.emit.apply(self, arguments); - }); - - // log all events in debug mode - if (config.debug) { - this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:')); - } - - // check for readiness - this.webrtc.on('localStream', function () { - self.testReadiness(); - }); - - this.webrtc.on('message', function (payload) { - self.connection.emit('message', payload); - }); - - this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this)); - this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this)); - - // echo cancellation attempts - if (this.config.adjustPeerVolume) { - this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking)); - this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1)); - } - - connection.on('stunservers', function (args) { - // appends to the config - self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args); - self.emit('stunservers', args); - }); - connection.on('turnservers', function (args) { - // appends to the config - self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args); - self.emit('turnservers', args); - }); - - this.webrtc.on('iceFailed', function (peer) { - // TODO: local ice failure - console.error('iceFailed event received'); - var pc = peer.pc; - console.log('had local relay candidate', pc.hadLocalRelayCandidate); - console.log('had remote relay candidate', pc.hadRemoteRelayCandidate); - }); - this.webrtc.on('connectivityError', function (peer) { - // TODO: remote ice failure - console.error('connectivityError event received'); - var pc = peer.pc; - console.log('had local relay candidate', pc.hadLocalRelayCandidate); - console.log('had remote relay candidate', pc.hadRemoteRelayCandidate); - }); - - // screensharing events - this.webrtc.on('localScreen', function (stream) { - var item, - el = document.createElement('video'), - container = self.getRemoteVideoContainer(); - - el.oncontextmenu = function () { return false; }; - el.id = 'localScreen'; - attachMediaStream(stream, el); - if (container) { - container.appendChild(el); - } - - self.emit('localScreenAdded', el); - self.connection.emit('shareScreen'); - // NOTE: we don't create screen peers for existing video peers here, - // this is done by the application code in "webrtc.js". - }); - this.webrtc.on('localScreenStopped', function (stream) { - if (self.getLocalScreen()) { - self.stopScreenShare(); - } - /* - self.connection.emit('unshareScreen'); - self.webrtc.peers.forEach(function (peer) { - if (peer.sharemyscreen) { - peer.end(); - } - }); - */ - }); - - this.webrtc.on('channelMessage', function (peer, label, data) { - if (data.type == 'volume') { - self.emit('remoteVolumeChange', peer, data.volume); - } - }); - } - - - SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, { - constructor: { - value: SimpleWebRTC - } - }); - - SimpleWebRTC.prototype.leaveCall = function () { - if (this.roomName) { - while (this.webrtc.peers.length) { - this.webrtc.peers[0].end(); - } - if (this.getLocalScreen()) { - this.stopScreenShare(); - } - this.emit('leftRoom', this.roomName); - this.stopLocalVideo(); - this.roomName = undefined; - } - }; - - SimpleWebRTC.prototype.disconnect = function () { - this.connection.disconnect(); - delete this.connection; - }; - - SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) { - var self = this; - var container = this.getRemoteVideoContainer(); - var video = attachMediaStream(peer.stream); - - // store video element as part of peer for easy removal - peer.videoEl = video; - video.id = this.getDomId(peer); - - if (container) container.appendChild(video); - - this.emit('videoAdded', video, peer); - - // send our mute status to new peer if we're muted - // currently called with a small delay because it arrives before - // the video element is created otherwise (which happens after - // the async setRemoteDescription-createAnswer) - window.setTimeout(function () { - if (!self.webrtc.isAudioEnabled()) { - peer.send('mute', {name: 'audio'}); - } - if (!self.webrtc.isVideoEnabled()) { - peer.send('mute', {name: 'video'}); - } - }, 250); - }; - - SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) { - var container = this.getRemoteVideoContainer(); - var videoEl = peer.videoEl; - if (this.config.autoRemoveVideos && container && videoEl) { - container.removeChild(videoEl); - } - if (videoEl) this.emit('videoRemoved', videoEl, peer); - }; - - SimpleWebRTC.prototype.getDomId = function (peer) { - return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_'); - }; - -// set volume on video tag for all peers takse a value between 0 and 1 - SimpleWebRTC.prototype.setVolumeForAll = function (volume) { - this.webrtc.peers.forEach(function (peer) { - if (peer.videoEl) peer.videoEl.volume = volume; - }); - }; - - SimpleWebRTC.prototype.joinCall = function (name) { - if (this.config.autoRequestMedia) this.startLocalVideo(); - this.roomName = name; - this.emit('joinedRoom', name); - }; - - SimpleWebRTC.prototype.getEl = function (idOrEl) { - if (typeof idOrEl === 'string') { - return document.getElementById(idOrEl); - } else { - return idOrEl; - } - }; - - SimpleWebRTC.prototype.startLocalVideo = function () { - var self = this; - console.log('startLocalVideo'); - this.webrtc.startLocalMedia(this.config.media, function (err, stream) { - if (err) { - self.emit('localMediaError', err); - } else { - self.emit('localMediaStarted', self.config.media); - attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo); - } - }); - }; - - SimpleWebRTC.prototype.stopLocalVideo = function () { - this.webrtc.stopLocalMedia(); - }; - -// this accepts either element ID or element -// and either the video tag itself or a container -// that will be used to put the video tag into. - SimpleWebRTC.prototype.getLocalVideoContainer = function () { - var el = this.getEl(this.config.localVideoEl); - if (el && el.tagName === 'VIDEO') { - el.oncontextmenu = function () { return false; }; - return el; - } else if (el) { - var video = document.createElement('video'); - video.oncontextmenu = function () { return false; }; - el.appendChild(video); - return video; - } else { - return; - } - }; - - SimpleWebRTC.prototype.getRemoteVideoContainer = function () { - return this.getEl(this.config.remoteVideosEl); - }; - - SimpleWebRTC.prototype.shareScreen = function (mode, cb) { - this.webrtc.startScreenShare(mode, cb); - }; - - SimpleWebRTC.prototype.getLocalScreen = function () { - return this.webrtc.localScreen; - }; - - SimpleWebRTC.prototype.stopScreenShare = function () { - this.connection.emit('unshareScreen'); - var videoEl = document.getElementById('localScreen'); - var container = this.getRemoteVideoContainer(); - - if (this.config.autoRemoveVideos && container && videoEl) { - container.removeChild(videoEl); - } - - // a hack to emit the event the removes the video - // element that we want - if (videoEl) { - this.emit('videoRemoved', videoEl); - } - if (this.getLocalScreen()) { - this.webrtc.stopScreenShare(); - } - // Notify peers were sending to. - this.webrtc.peers.forEach(function (peer) { - if (peer.type === 'screen' && peer.sharemyscreen) { - peer.send('unshareScreen'); - } - if (peer.broadcaster) { - peer.end(); - } - }); - }; - - SimpleWebRTC.prototype.testReadiness = function () { - var self = this; - if (this.sessionReady) { - if (!this.config.media.video && !this.config.media.audio) { - self.emit('readyToCall', self.connection.getSessionid()); - } else if (this.webrtc.localStreams.length > 0) { - self.emit('readyToCall', self.connection.getSessionid()); - } - } - }; - - SimpleWebRTC.prototype.createRoom = function (name, cb) { - this.roomName = name; - if (arguments.length === 2) { - this.connection.emit('create', name, cb); - } else { - this.connection.emit('create', name); - } - }; - - SimpleWebRTC.prototype.sendFile = function () { - if (!webrtcSupport.dataChannel) { - return this.emit('error', new Error('DataChannelNotSupported')); - } - - }; - - module.exports = SimpleWebRTC; - -},{"./socketioconnection":104,"./webrtc":105,"attachmediastream":3,"mockconsole":50,"webrtcsupport":99,"wildemitter":100}],104:[function(require,module,exports){ - var io = require('socket.io-client'); - - function SocketIoConnection(config) { - this.connection = io.connect(config.url, config.socketio); - } - - SocketIoConnection.prototype.on = function (ev, fn) { - this.connection.on(ev, fn); - }; - - SocketIoConnection.prototype.emit = function () { - this.connection.emit.apply(this.connection, arguments); - }; - - SocketIoConnection.prototype.getSessionid = function () { - return this.connection.id; - }; - - SocketIoConnection.prototype.disconnect = function () { - return this.connection.disconnect(); - }; - - module.exports = SocketIoConnection; - -},{"socket.io-client":63}],105:[function(require,module,exports){ - var util = require('util'); - var webrtcSupport = require('webrtcsupport'); - 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: [] - }, - peerConnectionConstraints: { - optional: [] - }, - receiveMedia: { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 - }, - enableDataChannels: true - }; - var item; - - // 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) { - if (options.hasOwnProperty(item)) { - this.config[item] = options[item]; - } - } - - // check for support - if (!webrtcSupport.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 })); - } - }); - } - }); - - this.on('unshareScreen', function(message) { - // End peers we were receiving the screensharing stream from. - var peers = self.getPeers(message.from, 'screen'); - peers.forEach(function(peer) { - if (!peer.sharemyscreen) { - peer.end(); - } - }); - }); - - - // 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) { - console.warn('Peer ended', id); - 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":102,"localmedia":46,"mockconsole":50,"util":89,"webrtcsupport":99}]},{},[103])(103) -}); diff --git a/js/simplewebrtc/bundled.js b/js/simplewebrtc/bundled.js new file mode 100644 index 000000000..c14d76f18 --- /dev/null +++ b/js/simplewebrtc/bundled.js @@ -0,0 +1,8245 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.SimpleWebRTC = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ +"use strict"; + +// getScreenMedia helper by @HenrikJoreteg +var getUserMedia = function getUserMedia(constraints, callback) { + window.navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + callback(null, stream); + }).catch(function (error) { + callback(error, null); + }); +}; // cache for constraints and callback + + +var cache = {}; + +module.exports = function (mode, constraints, cb) { + var hasConstraints = arguments.length === 3; + 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 (navigator.webkitGetUserMedia) { + var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(\d+)\./)[1], 10); + var maxver = 33; // Chrome 71 dropped support for "window.chrome.webstore;". + + var isCef = chromever < 71 && !window.chrome.webstore; // "known" crash in chrome 34 and 35 on linux + + if (window.navigator.userAgent.match('Linux')) maxver = 35; // check that the extension is installed by looking for a + // sessionStorage variable that contains the extension id + // this has to be set after installation unless the contest + // script does that + + if (sessionStorage.getScreenMediaJSExtensionId) { + chrome.runtime.sendMessage(sessionStorage.getScreenMediaJSExtensionId, { + type: 'getScreen', + id: 1 + }, null, function (data) { + if (!data || data.sourceId === '') { + // user canceled + var error = new Error('NavigatorUserMediaError'); + error.name = 'PERMISSION_DENIED'; + callback(error); + } else { + constraints = hasConstraints && constraints || { + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + } + } + }; + constraints.video.mandatory.chromeMediaSourceId = data.sourceId; + getUserMedia(constraints, callback); + } + }); + } else if (window.cefGetScreenMedia) { + //window.cefGetScreenMedia is experimental - may be removed without notice + window.cefGetScreenMedia(function (sourceId) { + if (!sourceId) { + var error = new Error('cefGetScreenMediaError'); + error.name = 'CEF_GETSCREENMEDIA_CANCELED'; + callback(error); + } else { + constraints = hasConstraints && 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 = sourceId; + getUserMedia(constraints, callback); + } + }); + } else 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 ? constraints : 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 >= 52) { + mode = mode || 'window'; + constraints = hasConstraints && constraints || { + video: { + mozMediaSource: mode, + mediaSource: mode + } + }; + getUserMedia(constraints, function (err, stream) { + callback(err, stream); + + if (err) { + return; + } // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 + + + 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 = 'FF52_REQUIRED'; + return callback(error); + } + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + navigator.mediaDevices.getDisplayMedia({ + video: true + }).then(function (stream) { + callback(null, stream); + }).catch(function (error) { + callback(error, null); + }); + } +}; + +typeof window !== 'undefined' && window.addEventListener('message', function (event) { + if (event.origin != window.location.origin && !event.isTrusted) { + 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); + } +}); + +},{}],2:[function(require,module,exports){ +"use strict"; + +var util = require('util'); + +var hark = require('hark'); + +var getScreenMedia = require('./getscreenmedia'); + +var WildEmitter = require('wildemitter'); + +var mockconsole = require('mockconsole'); + +function isAllTracksEnded(stream) { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + return isAllTracksEnded; +} + +function LocalMedia(opts) { + WildEmitter.call(this); + var config = this.config = { + detectSpeakingEvents: false, + audioFallback: false, + media: { + audio: true, + video: true + }, + harkOptions: null, + logger: mockconsole + }; + var item; + + for (item in opts) { + if (opts.hasOwnProperty(item)) { + 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.localStreams = []; + this.localScreens = []; + + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + this._logerror('Your browser does not support local media capture.'); + } + + this._audioMonitors = []; + this.on('localStreamStopped', this._stopAudioMonitor.bind(this)); + this.on('localScreenStopped', this._stopAudioMonitor.bind(this)); +} + +util.inherits(LocalMedia, WildEmitter); + +LocalMedia.prototype.start = function (mediaConstraints, cb) { + var self = this; + var constraints = mediaConstraints || this.config.media; + this.emit('localStreamRequested', constraints); + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + // Although the promise should be resolved only if all the constraints + // are met Edge resolves it if both audio and video are requested but + // only audio is available. + if (constraints.video && stream.getVideoTracks().length === 0) { + constraints.video = false; + self.start(constraints, cb); + return; + } + + if (constraints.audio && self.config.detectSpeakingEvents) { + self._setupAudioMonitor(stream, self.config.harkOptions); + } + + self.localStreams.push(stream); + stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + if (isAllTracksEnded(stream)) { + self._removeStream(stream); + } + }); + }); + self.emit('localStream', stream); + + if (cb) { + return cb(null, stream); + } + }).catch(function (err) { + // Fallback for users without a camera + if (self.config.audioFallback && err.name === 'NotFoundError' && constraints.video !== false) { + constraints.video = false; + self.start(constraints, cb); + return; + } + + self.emit('localStreamRequestFailed', constraints); + + if (cb) { + return cb(err, null); + } + }); +}; + +LocalMedia.prototype.stop = function (stream) { + this.stopStream(stream); + this.stopScreenShare(stream); +}; + +LocalMedia.prototype.stopStream = function (stream) { + var self = this; + + if (stream) { + var idx = this.localStreams.indexOf(stream); + + if (idx > -1) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + + this._removeStream(stream); + } + } else { + this.localStreams.forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + + self._removeStream(stream); + }); + } +}; + +LocalMedia.prototype.startScreenShare = function (mode, constraints, cb) { + var self = this; + this.emit('localScreenRequested'); + + if (typeof constraints === 'function' && !cb) { + cb = constraints; + constraints = null; + } + + getScreenMedia(mode, constraints, function (err, stream) { + if (!err) { + self.localScreens.push(stream); + stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + + if (isAllTracksEnded) { + self._removeStream(stream); + } + }); + }); + self.emit('localScreen', stream); + } else { + self.emit('localScreenRequestFailed'); + } // enable the callback + + + if (cb) { + return cb(err, stream); + } + }); +}; + +LocalMedia.prototype.stopScreenShare = function (stream) { + var self = this; + + if (stream) { + var idx = this.localScreens.indexOf(stream); + + if (idx > -1) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + + this._removeStream(stream); + } + } else { + this.localScreens.forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + + self._removeStream(stream); + }); + } +}; // Audio controls + + +LocalMedia.prototype.mute = function () { + this._audioEnabled(false); + + this.emit('audioOff'); +}; + +LocalMedia.prototype.unmute = function () { + this._audioEnabled(true); + + this.emit('audioOn'); +}; // 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) { + 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; + var hasAudioTracks = false; + this.localStreams.forEach(function (stream) { + var audioTracks = stream.getAudioTracks(); + + if (audioTracks.length > 0) { + hasAudioTracks = true; + audioTracks.forEach(function (track) { + enabled = enabled && track.enabled; + }); + } + }); // If no audioTracks were found, that means there is no microphone device. + // In that case, isAudioEnabled should return false. + + if (!hasAudioTracks) { + return false; + } + + return enabled; +}; // check if all video streams are enabled + + +LocalMedia.prototype.isVideoEnabled = function () { + var enabled = true; + var hasVideoTracks = false; + this.localStreams.forEach(function (stream) { + var videoTracks = stream.getVideoTracks(); + + if (videoTracks.length > 0) { + hasVideoTracks = true; + videoTracks.forEach(function (track) { + enabled = enabled && track.enabled; + }); + } + }); // If no videoTracks were found, that means there is no camera device. + // In that case, isVideoEnabled should return false. + + if (!hasVideoTracks) { + return false; + } + + return enabled; +}; + +LocalMedia.prototype._removeStream = function (stream) { + var idx = this.localStreams.indexOf(stream); + + if (idx > -1) { + this.localStreams.splice(idx, 1); + this.emit('localStreamStopped', stream); + } else { + idx = this.localScreens.indexOf(stream); + + if (idx > -1) { + this.localScreens.splice(idx, 1); + this.emit('localScreenStopped', stream); + } + } +}; + +LocalMedia.prototype._setupAudioMonitor = function (stream, harkOptions) { + this._log('Setup audio'); + + var audio = hark(stream, harkOptions); + var self = this; + var timeout; + audio.on('speaking', function () { + self.emit('speaking'); + }); + audio.on('stopped_speaking', function () { + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(function () { + self.emit('stoppedSpeaking'); + }, 1000); + }); + audio.on('volume_change', function (volume, threshold) { + self.emit('volumeChange', volume, threshold); + }); + + this._audioMonitors.push({ + audio: audio, + stream: stream + }); +}; + +LocalMedia.prototype._stopAudioMonitor = function (stream) { + var idx = -1; + + this._audioMonitors.forEach(function (monitors, i) { + if (monitors.stream === stream) { + idx = i; + } + }); + + if (idx > -1) { + this._audioMonitors[idx].audio.stop(); + + this._audioMonitors.splice(idx, 1); + } +}; // fallback for old .localScreen behaviour + + +Object.defineProperty(LocalMedia.prototype, 'localScreen', { + get: function get() { + return this.localScreens.length > 0 ? this.localScreens[0] : null; + } +}); +module.exports = LocalMedia; + +},{"./getscreenmedia":1,"hark":9,"mockconsole":11,"util":8,"wildemitter":31}],3:[function(require,module,exports){ +"use strict"; + +var util = require('util'); + +var webrtcSupport = require('webrtcsupport'); + +var WildEmitter = require('wildemitter'); + +function isAllTracksEnded(stream) { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + return isAllTracksEnded; +} + +function Peer(options) { + var self = this; // call emitter constructor + + WildEmitter.call(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.pendingDCMessages = []; // key (datachannel label) -> value (array[pending messages]) + + this.sid = options.sid || Date.now().toString(); + this.pc = new RTCPeerConnection(this.parent.config.peerConnectionConfig); + this.pc.addEventListener('icecandidate', this.onIceCandidate.bind(this)); + this.pc.addEventListener('endofcandidates', function (event) { + self.send('endOfCandidates', event); + }); + this.pc.addEventListener('addstream', this.handleRemoteStreamAdded.bind(this)); + this.pc.addEventListener('datachannel', this.handleDataChannelAdded.bind(this)); + this.pc.addEventListener('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.addEventListener('negotiationneeded', this.emit.bind(this, 'negotiationNeeded')); + this.pc.addEventListener('iceconnectionstatechange', this.emit.bind(this, 'iceConnectionStateChange')); + this.pc.addEventListener('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.localDescription.type === 'offer') { + self.parent.emit('iceFailed', self); + self.send('connectivityError'); + } + + break; + } + }); + this.pc.addEventListener('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); + }); + } // proxy events to parent + + + this.on('*', function () { + self.parent.emit.apply(self.parent, arguments); + }); +} + +util.inherits(Peer, WildEmitter); + +Peer.prototype.offer = function (options) { + this.pc.createOffer(options).then(function (offer) { + this.pc.setLocalDescription(offer).then(function () { + if (this.parent.config.nick) offer.nick = this.parent.config.nick; + this.send('offer', offer); + }.bind(this)).catch(function (error) { + console.warn("setLocalDescription for offer failed: ", error); + }.bind(this)); + }.bind(this)).catch(function (error) { + console.warn("createOffer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.handleOffer = function (offer) { + this.pc.setRemoteDescription(offer).then(function () { + this.answer(); + }.bind(this)).catch(function (error) { + console.warn("setRemoteDescription for offer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.answer = function () { + this.pc.createAnswer().then(function (answer) { + this.pc.setLocalDescription(answer).then(function () { + if (this.parent.config.nick) answer.nick = this.parent.config.nick; + this.send('answer', answer); + }.bind(this)).catch(function (error) { + console.warn("setLocalDescription for answer failed: ", error); + }.bind(this)); + }.bind(this)).catch(function (error) { + console.warn("createAnswer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.handleAnswer = function (answer) { + this.pc.setRemoteDescription(answer).catch(function (error) { + console.warn("setRemoteDescription for answer failed: ", error); + }.bind(this)); +}; + +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') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.handleOffer(message.payload); + } else if (message.type === 'answer') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.handleAnswer(message.payload); + } else if (message.type === 'candidate') { + this.pc.addIceCandidate(message.payload.candidate); + } 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 + }); + } else if (message.type === 'endOfCandidates') { + this.pc.addIceCandidate(''); + } else if (message.type === 'unshareScreen') { + this.parent.emit('unshareScreen', { + id: message.from + }); + this.end(); + } +}; // 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: webrtcSupport.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') { + if (!this.pendingDCMessages.hasOwnProperty(channel)) { + this.pendingDCMessages[channel] = []; + } + + this.pendingDCMessages[channel].push(message); + 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 = function () { + self.emit('channelOpen', channel); // Check if there are messages that could not be send + + if (self.pendingDCMessages.hasOwnProperty(channel.label)) { + var pendingMessages = self.pendingDCMessages[channel.label]; + + for (var i = 0; i < pendingMessages.length; i++) { + self.sendDirectly(channel.label, pendingMessages[i].type, pendingMessages[i].payload); + } + + self.pendingDCMessages[channel.label] = []; + } + }; +}; // Fetch or create a data channel by the given name + + +Peer.prototype.getDataChannel = function (name, opts) { + if (!webrtcSupport.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 (event) { + var candidate = event.candidate; + if (this.closed) return; + + if (candidate) { + var pcConfig = this.parent.config.peerConnectionConfig; + + if (webrtcSupport.prefix === 'moz' && pcConfig && pcConfig.iceTransports && candidate.candidate && candidate.candidate.candidate && candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) { + this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports); + } else { + // Retain legacy data structure for compatibility with + // mobile clients. + var expandedCandidate = { + candidate: { + candidate: candidate.candidate, + sdpMid: candidate.sdpMid, + sdpMLineIndex: candidate.sdpMLineIndex + } + }; + this.send('candidate', expandedCandidate); + } + } 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.offer(this.receiveMedia); +}; + +Peer.prototype.icerestart = function () { + var constraints = this.receiveMedia; + constraints.iceRestart = true; + this.offer(constraints); +}; + +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; + this.stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + if (isAllTracksEnded(self.stream)) { + self.end(); + } + }); + }); + this.parent.emit('peerStreamAdded', this); + } +}; + +Peer.prototype.handleStreamRemoved = function () { + var peerIndex = this.parent.peers.indexOf(this); + + if (peerIndex > -1) { + this.parent.peers.splice(peerIndex, 1); + this.closed = true; + this.parent.emit('peerStreamRemoved', this); + } +}; + +Peer.prototype.handleDataChannelAdded = function (event) { + var channel = event.channel; + this.channels[channel.label] = channel; + + this._observeDataChannel(channel); +}; + +module.exports = Peer; + +},{"util":8,"webrtcsupport":30,"wildemitter":31}],4:[function(require,module,exports){ +"use strict"; + +var WebRTC = require('./webrtc'); + +var WildEmitter = require('wildemitter'); + +var webrtcSupport = require('webrtcsupport'); + +var attachMediaStream = require('attachmediastream'); + +var mockconsole = require('mockconsole'); + +function SimpleWebRTC(opts) { + var self = this; + var options = opts || {}; + var config = this.config = { + socketio: { + /* 'force new connection':true*/ + }, + connection: null, + debug: false, + localVideoEl: '', + remoteVideosEl: '', + enableDataChannels: true, + autoRequestMedia: false, + autoRemoveVideos: true, + adjustPeerVolume: false, + peerVolumeWhenSpeaking: 0.25, + media: { + video: true, + audio: true + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + localVideo: { + autoplay: true, + mirror: true, + muted: true + } + }; + var item, connection; // 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 our config from options + + + for (item in options) { + if (options.hasOwnProperty(item)) { + this.config[item] = options[item]; + } + } // attach detected support for convenience + + + this.capabilities = webrtcSupport; // call WildEmitter constructor + + WildEmitter.call(this); + + if (this.config.connection === null) { + throw 'no connection object given in the configuration'; + } else { + connection = this.connection = this.config.connection; + } + + connection.on('connect', function () { + self.emit('connectionReady', connection.getSessionid()); + self.sessionReady = true; + self.testReadiness(); + }); + connection.on('message', function (message) { + var peers = self.webrtc.getPeers(message.from, message.roomType); + var peer; + + if (message.type === 'offer') { + if (peers.length) { + peers.forEach(function (p) { + if (p.sid == message.sid) peer = p; + }); //if (!peer) peer = peers[0]; // fallback for old protocol versions + } + + if (!peer) { + peer = self.webrtc.createPeer({ + id: message.from, + sid: message.sid, + type: message.roomType, + enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen', + sharemyscreen: message.roomType === 'screen' && !message.broadcaster, + broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null + }); + self.emit('createdPeer', peer); + } + + peer.handleMessage(message); + } else if (peers.length) { + peers.forEach(function (peer) { + if (message.sid) { + if (peer.sid === message.sid) { + peer.handleMessage(message); + } + } else { + peer.handleMessage(message); + } + }); + } + }); + connection.on('remove', function (room) { + if (room.id !== self.connection.getSessionid()) { + self.webrtc.removePeers(room.id, room.type); + } + }); // instantiate our main WebRTC helper + // using same logger from logic here + + opts.logger = this.logger; + opts.debug = false; + this.webrtc = new WebRTC(opts); // attach a few methods from underlying lib to simple. + + ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers', 'createPeer', 'removePeers'].forEach(function (method) { + self[method] = self.webrtc[method].bind(self.webrtc); + }); // proxy events from WebRTC + + this.webrtc.on('*', function () { + self.emit.apply(self, arguments); + }); // log all events in debug mode + + if (config.debug) { + this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:')); + } // check for readiness + + + this.webrtc.on('localStream', function () { + self.testReadiness(); + }); + this.webrtc.on('message', function (payload) { + self.connection.emit('message', payload); + }); + this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this)); + this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this)); // echo cancellation attempts + + if (this.config.adjustPeerVolume) { + this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking)); + this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1)); + } + + connection.on('stunservers', function (args) { + // resets/overrides the config + self.webrtc.config.peerConnectionConfig.iceServers = args; + self.emit('stunservers', args); + }); + connection.on('turnservers', function (args) { + // appends to the config + self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args); + self.emit('turnservers', args); + }); + this.webrtc.on('iceFailed', function (peer) {// local ice failure + }); + this.webrtc.on('connectivityError', function (peer) {// remote ice failure + }); // sending mute/unmute to all peers + + this.webrtc.on('audioOn', function () { + self.webrtc.sendToAll('unmute', { + name: 'audio' + }); + }); + this.webrtc.on('audioOff', function () { + self.webrtc.sendToAll('mute', { + name: 'audio' + }); + }); + this.webrtc.on('videoOn', function () { + self.webrtc.sendToAll('unmute', { + name: 'video' + }); + }); + this.webrtc.on('videoOff', function () { + self.webrtc.sendToAll('mute', { + name: 'video' + }); + }); // screensharing events + + this.webrtc.on('localScreen', function (stream) { + var item, + el = document.createElement('video'), + container = self.getRemoteVideoContainer(); + + el.oncontextmenu = function () { + return false; + }; + + el.id = 'localScreen'; + attachMediaStream(stream, el); + + if (container) { + container.appendChild(el); + } + + self.emit('localScreenAdded', el); + self.connection.emit('shareScreen'); // NOTE: we don't create screen peers for existing video peers here, + // this is done by the application code in "webrtc.js". + }); + this.webrtc.on('localScreenStopped', function (stream) { + if (self.getLocalScreen()) { + self.stopScreenShare(); + } + /* + self.connection.emit('unshareScreen'); + self.webrtc.peers.forEach(function (peer) { + if (peer.sharemyscreen) { + peer.end(); + } + }); + */ + + }); + this.webrtc.on('channelMessage', function (peer, label, data) { + if (data.type == 'volume') { + self.emit('remoteVolumeChange', peer, data.volume); + } + }); +} + +SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, { + constructor: { + value: SimpleWebRTC + } +}); + +SimpleWebRTC.prototype.leaveCall = function () { + if (this.roomName) { + while (this.webrtc.peers.length) { + this.webrtc.peers[0].end(); + } + + if (this.getLocalScreen()) { + this.stopScreenShare(); + } + + this.emit('leftRoom', this.roomName); + this.stopLocalVideo(); + this.roomName = undefined; + } +}; + +SimpleWebRTC.prototype.disconnect = function () { + this.connection.disconnect(); + delete this.connection; +}; + +SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) { + var self = this; + var container = this.getRemoteVideoContainer(); + var video = attachMediaStream(peer.stream); // At least Firefox, Opera and Edge move the video to a wrong position + // instead of keeping it unchanged when "transform: scaleX(1)" is used + // ("transform: scaleX(-1)" is fine); as it should have no effect the + // transform is removed. + + if (video.style.transform === 'scaleX(1)') { + video.style.transform = ''; + } // store video element as part of peer for easy removal + + + peer.videoEl = video; + video.id = this.getDomId(peer); + if (container) container.appendChild(video); + this.emit('videoAdded', video, peer); // send our mute status to new peer if we're muted + // currently called with a small delay because it arrives before + // the video element is created otherwise (which happens after + // the async setRemoteDescription-createAnswer) + + window.setTimeout(function () { + if (!self.webrtc.isAudioEnabled()) { + peer.send('mute', { + name: 'audio' + }); + } + + if (!self.webrtc.isVideoEnabled()) { + peer.send('mute', { + name: 'video' + }); + } + }, 250); +}; + +SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) { + var container = this.getRemoteVideoContainer(); + var videoEl = peer.videoEl; + + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } + + if (videoEl) this.emit('videoRemoved', videoEl, peer); +}; + +SimpleWebRTC.prototype.getDomId = function (peer) { + return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_'); +}; // set volume on video tag for all peers takse a value between 0 and 1 + + +SimpleWebRTC.prototype.setVolumeForAll = function (volume) { + this.webrtc.peers.forEach(function (peer) { + if (peer.videoEl) peer.videoEl.volume = volume; + }); +}; + +SimpleWebRTC.prototype.joinCall = function (name) { + if (this.config.autoRequestMedia) this.startLocalVideo(); + this.roomName = name; + this.emit('joinedRoom', name); +}; + +SimpleWebRTC.prototype.getEl = function (idOrEl) { + if (typeof idOrEl === 'string') { + return document.getElementById(idOrEl); + } else { + return idOrEl; + } +}; + +SimpleWebRTC.prototype.startLocalVideo = function () { + var self = this; + this.webrtc.start(this.config.media, function (err, stream) { + if (err) { + self.emit('localMediaError', err); + } else { + self.emit('localMediaStarted', self.config.media); + attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo); + } + }); +}; + +SimpleWebRTC.prototype.stopLocalVideo = function () { + this.webrtc.stop(); +}; // this accepts either element ID or element +// and either the video tag itself or a container +// that will be used to put the video tag into. + + +SimpleWebRTC.prototype.getLocalVideoContainer = function () { + var el = this.getEl(this.config.localVideoEl); + + if (el && el.tagName === 'VIDEO') { + el.oncontextmenu = function () { + return false; + }; + + return el; + } else if (el) { + var video = document.createElement('video'); + + video.oncontextmenu = function () { + return false; + }; + + el.appendChild(video); + return video; + } else { + return; + } +}; + +SimpleWebRTC.prototype.getRemoteVideoContainer = function () { + return this.getEl(this.config.remoteVideosEl); +}; + +SimpleWebRTC.prototype.shareScreen = function (mode, cb) { + this.webrtc.startScreenShare(mode, cb); +}; + +SimpleWebRTC.prototype.getLocalScreen = function () { + return this.webrtc.localScreen; +}; + +SimpleWebRTC.prototype.stopScreenShare = function () { + this.connection.emit('unshareScreen'); + var videoEl = document.getElementById('localScreen'); + var container = this.getRemoteVideoContainer(); + + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } // a hack to emit the event the removes the video + // element that we want + + + if (videoEl) { + this.emit('videoRemoved', videoEl); + } + + if (this.getLocalScreen()) { + this.webrtc.stopScreenShare(); + } // Notify peers were sending to. + + + this.webrtc.peers.forEach(function (peer) { + if (peer.type === 'screen' && peer.sharemyscreen) { + peer.send('unshareScreen'); + } + + if (peer.broadcaster) { + peer.end(); + } + }); +}; + +SimpleWebRTC.prototype.testReadiness = function () { + var self = this; + + if (this.sessionReady) { + if (!this.config.media.video && !this.config.media.audio) { + self.emit('readyToCall', self.connection.getSessionid()); + } else if (this.webrtc.localStreams.length > 0) { + self.emit('readyToCall', self.connection.getSessionid()); + } + } +}; + +SimpleWebRTC.prototype.createRoom = function (name, cb) { + this.roomName = name; + + if (arguments.length === 2) { + this.connection.emit('create', name, cb); + } else { + this.connection.emit('create', name); + } +}; + +module.exports = SimpleWebRTC; + +},{"./webrtc":5,"attachmediastream":6,"mockconsole":11,"webrtcsupport":30,"wildemitter":31}],5:[function(require,module,exports){ +"use strict"; + +var util = require('util'); + +var webrtcSupport = require('webrtcsupport'); + +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: [] + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + enableDataChannels: true + }; + var item; // 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) { + if (options.hasOwnProperty(item)) { + this.config[item] = options[item]; + } + } // check for support + + + if (!webrtcSupport.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 + })); + } + }); + } + }); + this.on('unshareScreen', function (message) { + // End peers we were receiving the screensharing stream from. + var peers = self.getPeers(message.from, 'screen'); + peers.forEach(function (peer) { + if (!peer.sharemyscreen) { + peer.end(); + } + }); + }); // 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; + +},{"./localmedia":2,"./peer":3,"mockconsole":11,"util":8,"webrtcsupport":30}],6:[function(require,module,exports){ +"use strict"; + +var adapter = require('webrtc-adapter'); + +module.exports = function (stream, el, options) { + var item; + var element = el; + var opts = { + autoplay: true, + mirror: false, + muted: false, + audio: false, + disableContextMenu: false + }; + + if (options) { + for (item in options) { + opts[item] = options[item]; + } + } + + if (!element) { + element = document.createElement(opts.audio ? 'audio' : 'video'); + } else if (element.tagName.toLowerCase() === 'audio') { + opts.audio = true; + } + + if (opts.disableContextMenu) { + element.oncontextmenu = function (e) { + e.preventDefault(); + }; + } + + if (opts.autoplay) element.autoplay = 'autoplay'; + element.muted = !!opts.muted; + + if (!opts.audio) { + ['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) { + var styleName = prefix ? prefix + 'Transform' : 'transform'; + element.style[styleName] = opts.mirror ? 'scaleX(-1)' : 'scaleX(1)'; + }); + } + + if (adapter.browserDetails.browser === 'safari') { + element.setAttribute('playsinline', true); + } + + element.srcObject = stream; + return element; +}; + +},{"webrtc-adapter":15}],7:[function(require,module,exports){ +"use strict"; + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +module.exports = function isBuffer(arg) { + return arg && _typeof(arg) === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; +}; + +},{}],8:[function(require,module,exports){ +(function (process,global){ +"use strict"; + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +var formatRegExp = /%[sdj%]/g; + +exports.format = function (f) { + if (!isString(f)) { + var objects = []; + + for (var i = 0; i < arguments.length; i++) { + objects.push(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': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + + default: + return x; + } + }); + + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + + return str; +}; // Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. + + +exports.deprecate = function (fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function () { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + + warned = true; + } + + return fn.apply(this, arguments); + } + + return deprecated; +}; + +var debugs = {}; +var debugEnviron; + +exports.debuglog = function (set) { + if (isUndefined(debugEnviron)) debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + + debugs[set] = function () { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function () {}; + } + } + + return debugs[set]; +}; +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ + +/* legacy: obj, showHidden, depth, colors*/ + + +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; // legacy... + + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } // set default options + + + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} + +exports.inspect = inspect; // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + +inspect.colors = { + '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] +}; // Don't use 'blue' not visible on cmd.exe + +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return "\x1B[" + inspect.colors[style][0] + 'm' + str + "\x1B[" + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + +function stylizeNoColor(str, styleType) { + return str; +} + +function arrayToHash(array) { + var hash = {}; + array.forEach(function (val, idx) { + hash[val] = true; + }); + return hash; +} + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && value && isFunction(value.inspect) && // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + + return ret; + } // Primitive types cannot have properties + + + var primitive = formatPrimitive(ctx, value); + + if (primitive) { + return primitive; + } // Look up the keys of the object. + + + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + + + if (isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } // Some type of object without properties can be shortcutted. + + + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + + if (isError(value)) { + return formatError(value); + } + } + + var base = '', + array = false, + braces = ['{', '}']; // Make Array say that they are Array + + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } // Make functions say that they are functions + + + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } // Make RegExps say that they are RegExps + + + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } // Make dates with properties first say the date + + + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } // Make error with message first say the error + + + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + var output; + + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function (key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + return reduceToSingleString(output, base, braces); +} + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) return ctx.stylize('undefined', 'undefined'); + + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '').replace(/'/g, "\\'").replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + + if (isNumber(value)) return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); // For some reason typeof null is "object", so special case here. + + if (isNull(value)) return ctx.stylize('null', 'null'); +} + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); + } else { + output.push(''); + } + } + + keys.forEach(function (key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); + } + }); + return output; +} + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { + value: value[key] + }; + + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + + if (str.indexOf('\n') > -1) { + if (array) { + 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 = ctx.stylize('[Circular]', 'special'); + } + } + + if (isUndefined(name)) { + if (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 = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'").replace(/\\"/g, '"').replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function (prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} // NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + + +function isArray(ar) { + return Array.isArray(ar); +} + +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} + +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} + +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} + +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} + +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} + +exports.isString = isString; + +function isSymbol(arg) { + return _typeof(arg) === 'symbol'; +} + +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} + +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} + +exports.isRegExp = isRegExp; + +function isObject(arg) { + return _typeof(arg) === 'object' && arg !== null; +} + +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} + +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error); +} + +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || _typeof(arg) === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} + +exports.isPrimitive = isPrimitive; +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +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(' '); +} // log is just a thin wrapper to console.log that prepends a timestamp + + +exports.log = function () { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ + + +exports.inherits = require('inherits'); + +exports._extend = function (origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + var keys = Object.keys(add); + var i = keys.length; + + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":7,"_process":12,"inherits":10}],9:[function(require,module,exports){ +"use strict"; + +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; + +if (typeof window !== 'undefined') { + 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; // Ensure that just a single AudioContext is internally created + + audioContext = options.audioContext || audioContext || new audioContextType(); + var sourceNode, fftBins, analyser; + analyser = audioContext.createAnalyser(); + analyser.fftSize = 512; + analyser.smoothingTimeConstant = smoothing; + fftBins = new Float32Array(analyser.frequencyBinCount); + 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.suspend = function () { + return audioContext.suspend(); + }; + + harker.resume = function () { + return audioContext.resume(); + }; + + Object.defineProperty(harker, 'state', { + get: function get() { + return audioContext.state; + } + }); + + audioContext.onstatechange = function () { + harker.emit('state_change', audioContext.state); + }; + + 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'); + } + + analyser.disconnect(); + sourceNode.disconnect(); + }; + + 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 looper() { + 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":31}],10:[function(require,module,exports){ +"use strict"; + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + + var TempCtor = function TempCtor() {}; + + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + }; +} + +},{}],11:[function(require,module,exports){ +"use strict"; + +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 fn() {}; + +var mockconsole = {}; + +while (l--) { + mockconsole[methods[l]] = fn; +} + +module.exports = mockconsole; + +},{}],12:[function(require,module,exports){ +"use strict"; + +// shim for using process in browser +var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} + +function defaultClearTimeout() { + throw new Error('clearTimeout has not been defined'); +} + +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +})(); + +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } // if setTimeout wasn't available but was latter defined + + + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } +} + +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } // if clearTimeout wasn't available but was latter defined + + + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } +} + +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + + draining = false; + + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + + var timeout = runTimeout(cleanUpNextTick); + draining = true; + var len = queue.length; + + while (len) { + currentQueue = queue; + queue = []; + + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + + queueIndex = -1; + len = queue.length; + } + + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + + queue.push(new Item(fun, args)); + + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; // v8 likes predictible objects + + +function Item(fun, array) { + this.fun = fun; + this.array = array; +} + +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues + +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { + return []; +}; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { + return '/'; +}; + +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +process.umask = function () { + return 0; +}; + +},{}],13:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +var SDPUtils = require('sdp'); + +function fixStatsType(stat) { + return { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[stat.type] || stat.type; +} + +function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); // Map ICE parameters (ufrag, pwd) to SDP. + + sdp += SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters()); // Map DTLS parameters to SDP. + + sdp += SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(), type === 'offer' ? 'actpass' : dtlsRole || 'active'); + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + var trackId = transceiver.rtpSender._initialTrackId || transceiver.rtpSender.track.id; + transceiver.rtpSender._initialTrackId = trackId; // spec. + + var msid = 'msid:' + (stream ? stream.id : '-') + ' ' + trackId + '\r\n'; + sdp += 'a=' + msid; // for Chrome. Legacy should no longer be required. + + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' ' + msid; // RTX + + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + transceiver.sendEncodingParameters[0].ssrc + ' ' + transceiver.sendEncodingParameters[0].rtx.ssrc + '\r\n'; + } + } // FIXME: this should be written by writeRtpDescription. + + + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; + + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; + } + + return sdp; +} // Edge does not like +// 1) stun: filtered after 14393 unless ?transport=udp is present +// 2) turn: that does not have all of turn:host:port?transport=udp +// 3) turn: with ipv6 addresses +// 4) turn: occurring muliple times + + +function filterIceServers(iceServers, edgeVersion) { + var hasTurn = false; + iceServers = JSON.parse(JSON.stringify(iceServers)); + return iceServers.filter(function (server) { + if (server && (server.urls || server.url)) { + var urls = server.urls || server.url; + + if (server.url && !server.urls) { + console.warn('RTCIceServer.url is deprecated! Use urls instead.'); + } + + var isString = typeof urls === 'string'; + + if (isString) { + urls = [urls]; + } + + urls = urls.filter(function (url) { + var validTurn = url.indexOf('turn:') === 0 && url.indexOf('transport=udp') !== -1 && url.indexOf('turn:[') === -1 && !hasTurn; + + if (validTurn) { + hasTurn = true; + return true; + } + + return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && url.indexOf('?transport=udp') === -1; + }); + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} // Determines the intersection of local and remote capabilities. + + +function getCommonCapabilities(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + + var findCodecByPayloadType = function findCodecByPayloadType(pt, codecs) { + pt = parseInt(pt, 10); + + for (var i = 0; i < codecs.length; i++) { + if (codecs[i].payloadType === pt || codecs[i].preferredPayloadType === pt) { + return codecs[i]; + } + } + }; + + var rtxCapabilityMatches = function rtxCapabilityMatches(lRtx, rRtx, lCodecs, rCodecs) { + var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); + var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); + return lCodec && rCodec && lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); + }; + + localCapabilities.codecs.forEach(function (lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && lCodec.clockRate === rCodec.clockRate) { + if (lCodec.name.toLowerCase() === 'rtx' && lCodec.parameters && rCodec.parameters.apt) { + // for RTX we need to find the local rtx that has a apt + // which points to the same local codec as the remote one. + if (!rtxCapabilityMatches(lCodec, rCodec, localCapabilities.codecs, remoteCapabilities.codecs)) { + continue; + } + } + + rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy + // number of channels is the highest common number of channels + + rCodec.numChannels = Math.min(lCodec.numChannels, rCodec.numChannels); // push rCodec so we reply with offerer payload type + + commonCapabilities.codecs.push(rCodec); // determine common feedback mechanisms + + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function (fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + + return false; + }); // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + + break; + } + } + }); + localCapabilities.headerExtensions.forEach(function (lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); // FIXME: fecMechanisms + + return commonCapabilities; +} // is action=setLocalDescription with type allowed in signalingState + + +function isActionAllowedInSignalingState(action, type, signalingState) { + return { + offer: { + setLocalDescription: ['stable', 'have-local-offer'], + setRemoteDescription: ['stable', 'have-remote-offer'] + }, + answer: { + setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], + setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + } + }[type][action].indexOf(signalingState) !== -1; +} + +function maybeAddCandidate(iceTransport, candidate) { + // Edge's internal representation adds some fields therefore + // not all fieldѕ are taken into account. + var alreadyAdded = iceTransport.getRemoteCandidates().find(function (remoteCandidate) { + return candidate.foundation === remoteCandidate.foundation && candidate.ip === remoteCandidate.ip && candidate.port === remoteCandidate.port && candidate.priority === remoteCandidate.priority && candidate.protocol === remoteCandidate.protocol && candidate.type === remoteCandidate.type; + }); + + if (!alreadyAdded) { + iceTransport.addRemoteCandidate(candidate); + } + + return !alreadyAdded; +} + +function makeError(name, description) { + var e = new Error(description); + e.name = name; // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names + + e.code = { + NotSupportedError: 9, + InvalidStateError: 11, + InvalidAccessError: 15, + TypeError: undefined, + OperationError: undefined + }[name]; + return e; +} + +module.exports = function (window, edgeVersion) { + // https://w3c.github.io/mediacapture-main/#mediastream + // Helper function to add the track to the stream and + // dispatch the event ourselves. + function addTrackToStreamAndFireEvent(track, stream) { + stream.addTrack(track); + stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', { + track: track + })); + } + + function removeTrackFromStreamAndFireEvent(track, stream) { + stream.removeTrack(track); + stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', { + track: track + })); + } + + function fireAddTrack(pc, track, receiver, streams) { + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = { + receiver: receiver + }; + trackEvent.streams = streams; + window.setTimeout(function () { + pc._dispatchEvent('track', trackEvent); + }); + } + + var RTCPeerConnection = function RTCPeerConnection(config) { + var pc = this; + + var _eventTarget = document.createDocumentFragment(); + + ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach(function (method) { + pc[method] = _eventTarget[method].bind(_eventTarget); + }); + this.canTrickleIceCandidates = null; + this.needNegotiation = false; + this.localStreams = []; + this.remoteStreams = []; + this._localDescription = null; + this._remoteDescription = null; + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.connectionState = 'new'; + this.iceGatheringState = 'new'; + config = JSON.parse(JSON.stringify(config || {})); + this.usingBundle = config.bundlePolicy === 'max-bundle'; + + if (config.rtcpMuxPolicy === 'negotiate') { + throw makeError('NotSupportedError', 'rtcpMuxPolicy \'negotiate\' is not supported'); + } else if (!config.rtcpMuxPolicy) { + config.rtcpMuxPolicy = 'require'; + } + + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + break; + + default: + config.iceTransportPolicy = 'all'; + break; + } + + switch (config.bundlePolicy) { + case 'balanced': + case 'max-compat': + case 'max-bundle': + break; + + default: + config.bundlePolicy = 'balanced'; + break; + } + + config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); + this._iceGatherers = []; + + if (config.iceCandidatePoolSize) { + for (var i = config.iceCandidatePoolSize; i > 0; i--) { + this._iceGatherers.push(new window.RTCIceGatherer({ + iceServers: config.iceServers, + gatherPolicy: config.iceTransportPolicy + })); + } + } else { + config.iceCandidatePoolSize = 0; + } + + this._config = config; // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + + this.transceivers = []; + this._sdpSessionId = SDPUtils.generateSessionId(); + this._sdpSessionVersion = 0; + this._dtlsRole = undefined; // role for a=setup to use in answers. + + this._isClosed = false; + }; + + Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { + configurable: true, + get: function get() { + return this._localDescription; + } + }); + Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { + configurable: true, + get: function get() { + return this._remoteDescription; + } + }); // set up event handlers on prototype + + RTCPeerConnection.prototype.onicecandidate = null; + RTCPeerConnection.prototype.onaddstream = null; + RTCPeerConnection.prototype.ontrack = null; + RTCPeerConnection.prototype.onremovestream = null; + RTCPeerConnection.prototype.onsignalingstatechange = null; + RTCPeerConnection.prototype.oniceconnectionstatechange = null; + RTCPeerConnection.prototype.onconnectionstatechange = null; + RTCPeerConnection.prototype.onicegatheringstatechange = null; + RTCPeerConnection.prototype.onnegotiationneeded = null; + RTCPeerConnection.prototype.ondatachannel = null; + + RTCPeerConnection.prototype._dispatchEvent = function (name, event) { + if (this._isClosed) { + return; + } + + this.dispatchEvent(event); + + if (typeof this['on' + name] === 'function') { + this['on' + name](event); + } + }; + + RTCPeerConnection.prototype._emitGatheringStateChange = function () { + var event = new Event('icegatheringstatechange'); + + this._dispatchEvent('icegatheringstatechange', event); + }; + + RTCPeerConnection.prototype.getConfiguration = function () { + return this._config; + }; + + RTCPeerConnection.prototype.getLocalStreams = function () { + return this.localStreams; + }; + + RTCPeerConnection.prototype.getRemoteStreams = function () { + return this.remoteStreams; + }; // internal helper to create a transceiver object. + // (which is not yet the same as the WebRTC 1.0 transceiver) + + + RTCPeerConnection.prototype._createTransceiver = function (kind, doNotAdd) { + var hasBundleTransport = this.transceivers.length > 0; + var transceiver = { + track: null, + iceGatherer: null, + iceTransport: null, + dtlsTransport: null, + localCapabilities: null, + remoteCapabilities: null, + rtpSender: null, + rtpReceiver: null, + kind: kind, + mid: null, + sendEncodingParameters: null, + recvEncodingParameters: null, + stream: null, + associatedRemoteMediaStreams: [], + wantReceive: true + }; + + if (this.usingBundle && hasBundleTransport) { + transceiver.iceTransport = this.transceivers[0].iceTransport; + transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; + } else { + var transports = this._createIceAndDtlsTransports(); + + transceiver.iceTransport = transports.iceTransport; + transceiver.dtlsTransport = transports.dtlsTransport; + } + + if (!doNotAdd) { + this.transceivers.push(transceiver); + } + + return transceiver; + }; + + RTCPeerConnection.prototype.addTrack = function (track, stream) { + if (this._isClosed) { + throw makeError('InvalidStateError', 'Attempted to call addTrack on a closed peerconnection.'); + } + + var alreadyExists = this.transceivers.find(function (s) { + return s.track === track; + }); + + if (alreadyExists) { + throw makeError('InvalidAccessError', 'Track already exists.'); + } + + var transceiver; + + for (var i = 0; i < this.transceivers.length; i++) { + if (!this.transceivers[i].track && this.transceivers[i].kind === track.kind) { + transceiver = this.transceivers[i]; + } + } + + if (!transceiver) { + transceiver = this._createTransceiver(track.kind); + } + + this._maybeFireNegotiationNeeded(); + + if (this.localStreams.indexOf(stream) === -1) { + this.localStreams.push(stream); + } + + transceiver.track = track; + transceiver.stream = stream; + transceiver.rtpSender = new window.RTCRtpSender(track, transceiver.dtlsTransport); + return transceiver.rtpSender; + }; + + RTCPeerConnection.prototype.addStream = function (stream) { + var pc = this; + + if (edgeVersion >= 15025) { + stream.getTracks().forEach(function (track) { + pc.addTrack(track, stream); + }); + } else { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + // Fixed in 15025 (or earlier) + var clonedStream = stream.clone(); + stream.getTracks().forEach(function (track, idx) { + var clonedTrack = clonedStream.getTracks()[idx]; + track.addEventListener('enabled', function (event) { + clonedTrack.enabled = event.enabled; + }); + }); + clonedStream.getTracks().forEach(function (track) { + pc.addTrack(track, clonedStream); + }); + } + }; + + RTCPeerConnection.prototype.removeTrack = function (sender) { + if (this._isClosed) { + throw makeError('InvalidStateError', 'Attempted to call removeTrack on a closed peerconnection.'); + } + + if (!(sender instanceof window.RTCRtpSender)) { + throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.'); + } + + var transceiver = this.transceivers.find(function (t) { + return t.rtpSender === sender; + }); + + if (!transceiver) { + throw makeError('InvalidAccessError', 'Sender was not created by this connection.'); + } + + var stream = transceiver.stream; + transceiver.rtpSender.stop(); + transceiver.rtpSender = null; + transceiver.track = null; + transceiver.stream = null; // remove the stream from the set of local streams + + var localStreams = this.transceivers.map(function (t) { + return t.stream; + }); + + if (localStreams.indexOf(stream) === -1 && this.localStreams.indexOf(stream) > -1) { + this.localStreams.splice(this.localStreams.indexOf(stream), 1); + } + + this._maybeFireNegotiationNeeded(); + }; + + RTCPeerConnection.prototype.removeStream = function (stream) { + var pc = this; + stream.getTracks().forEach(function (track) { + var sender = pc.getSenders().find(function (s) { + return s.track === track; + }); + + if (sender) { + pc.removeTrack(sender); + } + }); + }; + + RTCPeerConnection.prototype.getSenders = function () { + return this.transceivers.filter(function (transceiver) { + return !!transceiver.rtpSender; + }).map(function (transceiver) { + return transceiver.rtpSender; + }); + }; + + RTCPeerConnection.prototype.getReceivers = function () { + return this.transceivers.filter(function (transceiver) { + return !!transceiver.rtpReceiver; + }).map(function (transceiver) { + return transceiver.rtpReceiver; + }); + }; + + RTCPeerConnection.prototype._createIceGatherer = function (sdpMLineIndex, usingBundle) { + var pc = this; + + if (usingBundle && sdpMLineIndex > 0) { + return this.transceivers[0].iceGatherer; + } else if (this._iceGatherers.length) { + return this._iceGatherers.shift(); + } + + var iceGatherer = new window.RTCIceGatherer({ + iceServers: this._config.iceServers, + gatherPolicy: this._config.iceTransportPolicy + }); + Object.defineProperty(iceGatherer, 'state', { + value: 'new', + writable: true + }); + this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; + + this.transceivers[sdpMLineIndex].bufferCandidates = function (event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + + iceGatherer.state = end ? 'completed' : 'gathering'; + + if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { + pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); + } + }; + + iceGatherer.addEventListener('localcandidate', this.transceivers[sdpMLineIndex].bufferCandidates); + return iceGatherer; + }; // start gathering from an RTCIceGatherer. + + + RTCPeerConnection.prototype._gather = function (mid, sdpMLineIndex) { + var pc = this; + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + + if (iceGatherer.onlocalcandidate) { + return; + } + + var bufferedCandidateEvents = this.transceivers[sdpMLineIndex].bufferedCandidateEvents; + this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; + iceGatherer.removeEventListener('localcandidate', this.transceivers[sdpMLineIndex].bufferCandidates); + + iceGatherer.onlocalcandidate = function (evt) { + if (pc.usingBundle && sdpMLineIndex > 0) { + // if we know that we use bundle we can drop candidates with + // ѕdpMLineIndex > 0. If we don't do this then our state gets + // confused since we dispose the extra ice gatherer. + return; + } + + var event = new Event('icecandidate'); + event.candidate = { + sdpMid: mid, + sdpMLineIndex: sdpMLineIndex + }; + var cand = evt.candidate; // Edge emits an empty object for RTCIceCandidateComplete‥ + + var end = !cand || Object.keys(cand).length === 0; + + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { + iceGatherer.state = 'completed'; + } + } else { + if (iceGatherer.state === 'new') { + iceGatherer.state = 'gathering'; + } // RTCIceCandidate doesn't have a component, needs to be added + + + cand.component = 1; // also the usernameFragment. TODO: update SDP to take both variants. + + cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; + var serializedCandidate = SDPUtils.writeCandidate(cand); + event.candidate = Object.assign(event.candidate, SDPUtils.parseCandidate(serializedCandidate)); + event.candidate.candidate = serializedCandidate; + + event.candidate.toJSON = function () { + return { + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex, + usernameFragment: event.candidate.usernameFragment + }; + }; + } // update local description. + + + var sections = SDPUtils.getMediaSections(pc._localDescription.sdp); + + if (!end) { + sections[event.candidate.sdpMLineIndex] += 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex] += 'a=end-of-candidates\r\n'; + } + + pc._localDescription.sdp = SDPUtils.getDescription(pc._localDescription.sdp) + sections.join(''); + var complete = pc.transceivers.every(function (transceiver) { + return transceiver.iceGatherer && transceiver.iceGatherer.state === 'completed'; + }); + + if (pc.iceGatheringState !== 'gathering') { + pc.iceGatheringState = 'gathering'; + + pc._emitGatheringStateChange(); + } // Emit candidate. Also emit null candidate when all gatherers are + // complete. + + + if (!end) { + pc._dispatchEvent('icecandidate', event); + } + + if (complete) { + pc._dispatchEvent('icecandidate', new Event('icecandidate')); + + pc.iceGatheringState = 'complete'; + + pc._emitGatheringStateChange(); + } + }; // emit already gathered candidates. + + + window.setTimeout(function () { + bufferedCandidateEvents.forEach(function (e) { + iceGatherer.onlocalcandidate(e); + }); + }, 0); + }; // Create ICE transport and DTLS transport. + + + RTCPeerConnection.prototype._createIceAndDtlsTransports = function () { + var pc = this; + var iceTransport = new window.RTCIceTransport(null); + + iceTransport.onicestatechange = function () { + pc._updateIceConnectionState(); + + pc._updateConnectionState(); + }; + + var dtlsTransport = new window.RTCDtlsTransport(iceTransport); + + dtlsTransport.ondtlsstatechange = function () { + pc._updateConnectionState(); + }; + + dtlsTransport.onerror = function () { + // onerror does not set state to failed by itself. + Object.defineProperty(dtlsTransport, 'state', { + value: 'failed', + writable: true + }); + + pc._updateConnectionState(); + }; + + return { + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; // Destroy ICE gatherer, ICE transport and DTLS transport. + // Without triggering the callbacks. + + + RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function (sdpMLineIndex) { + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + + if (iceGatherer) { + delete iceGatherer.onlocalcandidate; + delete this.transceivers[sdpMLineIndex].iceGatherer; + } + + var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; + + if (iceTransport) { + delete iceTransport.onicestatechange; + delete this.transceivers[sdpMLineIndex].iceTransport; + } + + var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; + + if (dtlsTransport) { + delete dtlsTransport.ondtlsstatechange; + delete dtlsTransport.onerror; + delete this.transceivers[sdpMLineIndex].dtlsTransport; + } + }; // Start the RTP Sender and Receiver for a transceiver. + + + RTCPeerConnection.prototype._transceive = function (transceiver, send, recv) { + var params = getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities); + + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName, + compound: transceiver.rtcpParameters.compound + }; + + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + + transceiver.rtpSender.send(params); + } + + if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { + // remove RTX field in Edge 14942 + if (transceiver.kind === 'video' && transceiver.recvEncodingParameters && edgeVersion < 15019) { + transceiver.recvEncodingParameters.forEach(function (p) { + delete p.rtx; + }); + } + + if (transceiver.recvEncodingParameters.length) { + params.encodings = transceiver.recvEncodingParameters; + } else { + params.encodings = [{}]; + } + + params.rtcp = { + compound: transceiver.rtcpParameters.compound + }; + + if (transceiver.rtcpParameters.cname) { + params.rtcp.cname = transceiver.rtcpParameters.cname; + } + + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + + transceiver.rtpReceiver.receive(params); + } + }; + + RTCPeerConnection.prototype.setLocalDescription = function (description) { + var pc = this; // Note: pranswer is not supported. + + if (['offer', 'answer'].indexOf(description.type) === -1) { + return Promise.reject(makeError('TypeError', 'Unsupported type "' + description.type + '"')); + } + + if (!isActionAllowedInSignalingState('setLocalDescription', description.type, pc.signalingState) || pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', 'Can not set local ' + description.type + ' in state ' + pc.signalingState)); + } + + var sections; + var sessionpart; + + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function (mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + pc.transceivers[sdpMLineIndex].localCapabilities = caps; + }); + pc.transceivers.forEach(function (transceiver, sdpMLineIndex) { + pc._gather(transceiver.mid, sdpMLineIndex); + }); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc._remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0; + sections.forEach(function (mediaSection, sdpMLineIndex) { + var transceiver = pc.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; // treat bundle-only as not-rejected. + + var rejected = SDPUtils.isRejected(mediaSection) && SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + + if (!rejected && !transceiver.rejected) { + var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, sessionpart); + + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!pc.usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver.mid, sdpMLineIndex); + + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, isIceLite ? 'controlling' : 'controlled'); + } + + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } // Calculate intersection of capabilities. + + + var params = getCommonCapabilities(localCapabilities, remoteCapabilities); // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + + pc._transceive(transceiver, params.codecs.length > 0, false); + } + }); + } + + pc._localDescription = { + type: description.type, + sdp: description.sdp + }; + + if (description.type === 'offer') { + pc._updateSignalingState('have-local-offer'); + } else { + pc._updateSignalingState('stable'); + } + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.setRemoteDescription = function (description) { + var pc = this; // Note: pranswer is not supported. + + if (['offer', 'answer'].indexOf(description.type) === -1) { + return Promise.reject(makeError('TypeError', 'Unsupported type "' + description.type + '"')); + } + + if (!isActionAllowedInSignalingState('setRemoteDescription', description.type, pc.signalingState) || pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', 'Can not set remote ' + description.type + ' in state ' + pc.signalingState)); + } + + var streams = {}; + pc.remoteStreams.forEach(function (stream) { + streams[stream.id] = stream; + }); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0; + var usingBundle = SDPUtils.matchPrefix(sessionpart, 'a=group:BUNDLE ').length > 0; + pc.usingBundle = usingBundle; + var iceOptions = SDPUtils.matchPrefix(sessionpart, 'a=ice-options:')[0]; + + if (iceOptions) { + pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ').indexOf('trickle') >= 0; + } else { + pc.canTrickleIceCandidates = false; + } + + sections.forEach(function (mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var kind = SDPUtils.getKind(mediaSection); // treat bundle-only as not-rejected. + + var rejected = SDPUtils.isRejected(mediaSection) && SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + var protocol = lines[0].substr(2).split(' ')[2]; + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + var remoteMsid = SDPUtils.parseMsid(mediaSection); + var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); // Reject datachannels which are not implemented yet. + + if (rejected || kind === 'application' && (protocol === 'DTLS/SCTP' || protocol === 'UDP/DTLS/SCTP')) { + // TODO: this is dangerous in the case where a non-rejected m-line + // becomes rejected. + pc.transceivers[sdpMLineIndex] = { + mid: mid, + kind: kind, + protocol: protocol, + rejected: true + }; + return; + } + + if (!rejected && pc.transceivers[sdpMLineIndex] && pc.transceivers[sdpMLineIndex].rejected) { + // recycle a rejected transceiver. + pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); + } + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + var track; // FIXME: ensure the mediaSection has rtcp-mux set. + + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, sessionpart); + remoteDtlsParameters.role = 'client'; + } + + recvEncodingParameters = SDPUtils.parseRtpEncodingParameters(mediaSection); + var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); + var isComplete = SDPUtils.matchPrefix(mediaSection, 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:').map(function (cand) { + return SDPUtils.parseCandidate(cand); + }).filter(function (cand) { + return cand.component === 1; + }); // Check if we can use BUNDLE and dispose transports. + + if ((description.type === 'offer' || description.type === 'answer') && !rejected && usingBundle && sdpMLineIndex > 0 && pc.transceivers[sdpMLineIndex]) { + pc._disposeIceAndDtlsTransports(sdpMLineIndex); + + pc.transceivers[sdpMLineIndex].iceGatherer = pc.transceivers[0].iceGatherer; + pc.transceivers[sdpMLineIndex].iceTransport = pc.transceivers[0].iceTransport; + pc.transceivers[sdpMLineIndex].dtlsTransport = pc.transceivers[0].dtlsTransport; + + if (pc.transceivers[sdpMLineIndex].rtpSender) { + pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport); + } + + if (pc.transceivers[sdpMLineIndex].rtpReceiver) { + pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport); + } + } + + if (description.type === 'offer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex] || pc._createTransceiver(kind); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, usingBundle); + } + + if (cands.length && transceiver.iceTransport.state === 'new') { + if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { + transceiver.iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function (candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter(function (codec) { + return codec.name !== 'rtx'; + }); + } + + sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + + var isNewTrack = false; + + if (direction === 'sendrecv' || direction === 'sendonly') { + isNewTrack = !transceiver.rtpReceiver; + rtpReceiver = transceiver.rtpReceiver || new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + + if (isNewTrack) { + var stream; + track = rtpReceiver.track; // FIXME: does not work with Plan B. + + if (remoteMsid && remoteMsid.stream === '-') {// no-op. a stream id of '-' means: no associated stream. + } else if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + Object.defineProperty(streams[remoteMsid.stream], 'id', { + get: function get() { + return remoteMsid.stream; + } + }); + } + + Object.defineProperty(track, 'id', { + get: function get() { + return remoteMsid.track; + } + }); + stream = streams[remoteMsid.stream]; + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + + stream = streams.default; + } + + if (stream) { + addTrackToStreamAndFireEvent(track, stream); + transceiver.associatedRemoteMediaStreams.push(stream); + } + + receiverList.push([track, rtpReceiver, stream]); + } + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { + transceiver.associatedRemoteMediaStreams.forEach(function (s) { + var nativeTrack = s.getTracks().find(function (t) { + return t.id === transceiver.rtpReceiver.track.id; + }); + + if (nativeTrack) { + removeTrackFromStreamAndFireEvent(nativeTrack, s); + } + }); + transceiver.associatedRemoteMediaStreams = []; + } + + transceiver.localCapabilities = localCapabilities; + transceiver.remoteCapabilities = remoteCapabilities; + transceiver.rtpReceiver = rtpReceiver; + transceiver.rtcpParameters = rtcpParameters; + transceiver.sendEncodingParameters = sendEncodingParameters; + transceiver.recvEncodingParameters = recvEncodingParameters; // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + + pc._transceive(pc.transceivers[sdpMLineIndex], false, isNewTrack); + } else if (description.type === 'answer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + pc.transceivers[sdpMLineIndex].recvEncodingParameters = recvEncodingParameters; + pc.transceivers[sdpMLineIndex].remoteCapabilities = remoteCapabilities; + pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + + if (cands.length && iceTransport.state === 'new') { + if ((isIceLite || isComplete) && (!usingBundle || sdpMLineIndex === 0)) { + iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function (candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + if (!usingBundle || sdpMLineIndex === 0) { + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, 'controlling'); + } + + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } // If the offer contained RTX but the answer did not, + // remove RTX from sendEncodingParameters. + + + var commonCapabilities = getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities); + var hasRtx = commonCapabilities.codecs.filter(function (c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + pc._transceive(transceiver, direction === 'sendrecv' || direction === 'recvonly', direction === 'sendrecv' || direction === 'sendonly'); // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + + + if (rtpReceiver && (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + + if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + } + + addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + + addTrackToStreamAndFireEvent(track, streams.default); + receiverList.push([track, rtpReceiver, streams.default]); + } + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + if (pc._dtlsRole === undefined) { + pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + } + + pc._remoteDescription = { + type: description.type, + sdp: description.sdp + }; + + if (description.type === 'offer') { + pc._updateSignalingState('have-remote-offer'); + } else { + pc._updateSignalingState('stable'); + } + + Object.keys(streams).forEach(function (sid) { + var stream = streams[sid]; + + if (stream.getTracks().length) { + if (pc.remoteStreams.indexOf(stream) === -1) { + pc.remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + window.setTimeout(function () { + pc._dispatchEvent('addstream', event); + }); + } + + receiverList.forEach(function (item) { + var track = item[0]; + var receiver = item[1]; + + if (stream.id !== item[2].id) { + return; + } + + fireAddTrack(pc, track, receiver, [stream]); + }); + } + }); + receiverList.forEach(function (item) { + if (item[2]) { + return; + } + + fireAddTrack(pc, item[0], item[1], []); + }); // check whether addIceCandidate({}) was called within four seconds after + // setRemoteDescription. + + window.setTimeout(function () { + if (!(pc && pc.transceivers)) { + return; + } + + pc.transceivers.forEach(function (transceiver) { + if (transceiver.iceTransport && transceiver.iceTransport.state === 'new' && transceiver.iceTransport.getRemoteCandidates().length > 0) { + console.warn('Timeout for addRemoteCandidate. Consider sending ' + 'an end-of-candidates notification'); + transceiver.iceTransport.addRemoteCandidate({}); + } + }); + }, 4000); + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.close = function () { + this.transceivers.forEach(function (transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); // FIXME: clean up tracks, local streams, remote streams, etc + + this._isClosed = true; + + this._updateSignalingState('closed'); + }; // Update the signaling state. + + + RTCPeerConnection.prototype._updateSignalingState = function (newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + + this._dispatchEvent('signalingstatechange', event); + }; // Determine whether to fire the negotiationneeded event. + + + RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function () { + var pc = this; + + if (this.signalingState !== 'stable' || this.needNegotiation === true) { + return; + } + + this.needNegotiation = true; + window.setTimeout(function () { + if (pc.needNegotiation) { + pc.needNegotiation = false; + var event = new Event('negotiationneeded'); + + pc._dispatchEvent('negotiationneeded', event); + } + }, 0); + }; // Update the ice connection state. + + + RTCPeerConnection.prototype._updateIceConnectionState = function () { + var newState; + var states = { + 'new': 0, + closed: 0, + checking: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function (transceiver) { + if (transceiver.iceTransport && !transceiver.rejected) { + states[transceiver.iceTransport.state]++; + } + }); + newState = 'new'; + + if (states.failed > 0) { + newState = 'failed'; + } else if (states.checking > 0) { + newState = 'checking'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0) { + newState = 'connected'; + } else if (states.completed > 0) { + newState = 'completed'; + } + + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + + this._dispatchEvent('iceconnectionstatechange', event); + } + }; // Update the connection state. + + + RTCPeerConnection.prototype._updateConnectionState = function () { + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function (transceiver) { + if (transceiver.iceTransport && transceiver.dtlsTransport && !transceiver.rejected) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + } + }); // ICETransport.completed and connected are the same for this purpose. + + states.connected += states.completed; + newState = 'new'; + + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0) { + newState = 'connected'; + } + + if (newState !== this.connectionState) { + this.connectionState = newState; + var event = new Event('connectionstatechange'); + + this._dispatchEvent('connectionstatechange', event); + } + }; + + RTCPeerConnection.prototype.createOffer = function () { + var pc = this; + + if (pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', 'Can not call createOffer after close')); + } + + var numAudioTracks = pc.transceivers.filter(function (t) { + return t.kind === 'audio'; + }).length; + var numVideoTracks = pc.transceivers.filter(function (t) { + return t.kind === 'video'; + }).length; // Determine number of audio and video tracks we need to send/recv. + + var offerOptions = arguments[0]; + + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError('Legacy mandatory/optional constraints not supported.'); + } + + if (offerOptions.offerToReceiveAudio !== undefined) { + if (offerOptions.offerToReceiveAudio === true) { + numAudioTracks = 1; + } else if (offerOptions.offerToReceiveAudio === false) { + numAudioTracks = 0; + } else { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + } + + if (offerOptions.offerToReceiveVideo !== undefined) { + if (offerOptions.offerToReceiveVideo === true) { + numVideoTracks = 1; + } else if (offerOptions.offerToReceiveVideo === false) { + numVideoTracks = 0; + } else { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + + pc.transceivers.forEach(function (transceiver) { + if (transceiver.kind === 'audio') { + numAudioTracks--; + + if (numAudioTracks < 0) { + transceiver.wantReceive = false; + } + } else if (transceiver.kind === 'video') { + numVideoTracks--; + + if (numVideoTracks < 0) { + transceiver.wantReceive = false; + } + } + }); // Create M-lines for recvonly streams. + + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + pc._createTransceiver('audio'); + + numAudioTracks--; + } + + if (numVideoTracks > 0) { + pc._createTransceiver('video'); + + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); + pc.transceivers.forEach(function (transceiver, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = transceiver.track; + var kind = transceiver.kind; + var mid = transceiver.mid || SDPUtils.generateIdentifier(); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, pc.usingBundle); + } + + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter(function (codec) { + return codec.name !== 'rtx'; + }); + } + + localCapabilities.codecs.forEach(function (codec) { + // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 + // by adding level-asymmetry-allowed=1 + if (codec.name === 'H264' && codec.parameters['level-asymmetry-allowed'] === undefined) { + codec.parameters['level-asymmetry-allowed'] = '1'; + } // for subsequent offers, we might have to re-use the payload + // type of the last offer. + + + if (transceiver.remoteCapabilities && transceiver.remoteCapabilities.codecs) { + transceiver.remoteCapabilities.codecs.forEach(function (remoteCodec) { + if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && codec.clockRate === remoteCodec.clockRate) { + codec.preferredPayloadType = remoteCodec.payloadType; + } + }); + } + }); + localCapabilities.headerExtensions.forEach(function (hdrExt) { + var remoteExtensions = transceiver.remoteCapabilities && transceiver.remoteCapabilities.headerExtensions || []; + remoteExtensions.forEach(function (rHdrExt) { + if (hdrExt.uri === rHdrExt.uri) { + hdrExt.id = rHdrExt.id; + } + }); + }); // generate an ssrc now, to be used later in rtpSender.send + + var sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + + if (track) { + // add RTX + if (edgeVersion >= 15019 && kind === 'video' && !sendEncodingParameters[0].rtx) { + sendEncodingParameters[0].rtx = { + ssrc: sendEncodingParameters[0].ssrc + 1 + }; + } + } + + if (transceiver.wantReceive) { + transceiver.rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + } + + transceiver.localCapabilities = localCapabilities; + transceiver.sendEncodingParameters = sendEncodingParameters; + }); // always offer BUNDLE and dispose on return if not supported. + + if (pc._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function (t) { + return t.mid; + }).join(' ') + '\r\n'; + } + + sdp += 'a=ice-options:trickle\r\n'; + pc.transceivers.forEach(function (transceiver, sdpMLineIndex) { + sdp += writeMediaSection(transceiver, transceiver.localCapabilities, 'offer', transceiver.stream, pc._dtlsRole); + sdp += 'a=rtcp-rsize\r\n'; + + if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && (sdpMLineIndex === 0 || !pc.usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function (cand) { + cand.component = 1; + sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'completed') { + sdp += 'a=end-of-candidates\r\n'; + } + } + }); + var desc = new window.RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.createAnswer = function () { + var pc = this; + + if (pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', 'Can not call createAnswer after close')); + } + + if (!(pc.signalingState === 'have-remote-offer' || pc.signalingState === 'have-local-pranswer')) { + return Promise.reject(makeError('InvalidStateError', 'Can not call createAnswer in signalingState ' + pc.signalingState)); + } + + var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, pc._sdpSessionVersion++); + + if (pc.usingBundle) { + sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function (t) { + return t.mid; + }).join(' ') + '\r\n'; + } + + sdp += 'a=ice-options:trickle\r\n'; + var mediaSectionsInOffer = SDPUtils.getMediaSections(pc._remoteDescription.sdp).length; + pc.transceivers.forEach(function (transceiver, sdpMLineIndex) { + if (sdpMLineIndex + 1 > mediaSectionsInOffer) { + return; + } + + if (transceiver.rejected) { + if (transceiver.kind === 'application') { + if (transceiver.protocol === 'DTLS/SCTP') { + // legacy fmt + sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; + } else { + sdp += 'm=application 0 ' + transceiver.protocol + ' webrtc-datachannel\r\n'; + } + } else if (transceiver.kind === 'audio') { + sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + 'a=rtpmap:0 PCMU/8000\r\n'; + } else if (transceiver.kind === 'video') { + sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + 'a=rtpmap:120 VP8/90000\r\n'; + } + + sdp += 'c=IN IP4 0.0.0.0\r\n' + 'a=inactive\r\n' + 'a=mid:' + transceiver.mid + '\r\n'; + return; + } // FIXME: look at direction. + + + if (transceiver.stream) { + var localTrack; + + if (transceiver.kind === 'audio') { + localTrack = transceiver.stream.getAudioTracks()[0]; + } else if (transceiver.kind === 'video') { + localTrack = transceiver.stream.getVideoTracks()[0]; + } + + if (localTrack) { + // add RTX + if (edgeVersion >= 15019 && transceiver.kind === 'video' && !transceiver.sendEncodingParameters[0].rtx) { + transceiver.sendEncodingParameters[0].rtx = { + ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 + }; + } + } + } // Calculate intersection of capabilities. + + + var commonCapabilities = getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities); + var hasRtx = commonCapabilities.codecs.filter(function (c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + sdp += writeMediaSection(transceiver, commonCapabilities, 'answer', transceiver.stream, pc._dtlsRole); + + if (transceiver.rtcpParameters && transceiver.rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + }); + var desc = new window.RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.addIceCandidate = function (candidate) { + var pc = this; + var sections; + + if (candidate && !(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { + return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); + } // TODO: needs to go into ops queue. + + + return new Promise(function (resolve, reject) { + if (!pc._remoteDescription) { + return reject(makeError('InvalidStateError', 'Can not add ICE candidate without a remote description')); + } else if (!candidate || candidate.candidate === '') { + for (var j = 0; j < pc.transceivers.length; j++) { + if (pc.transceivers[j].rejected) { + continue; + } + + pc.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); + sections[j] += 'a=end-of-candidates\r\n'; + pc._remoteDescription.sdp = SDPUtils.getDescription(pc._remoteDescription.sdp) + sections.join(''); + + if (pc.usingBundle) { + break; + } + } + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + + if (candidate.sdpMid) { + for (var i = 0; i < pc.transceivers.length; i++) { + if (pc.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; + break; + } + } + } + + var transceiver = pc.transceivers[sdpMLineIndex]; + + if (transceiver) { + if (transceiver.rejected) { + return resolve(); + } + + var cand = Object.keys(candidate.candidate).length > 0 ? SDPUtils.parseCandidate(candidate.candidate) : {}; // Ignore Chrome's invalid candidates since Edge does not like them. + + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return resolve(); + } // Ignore RTCP candidates, we assume RTCP-MUX. + + + if (cand.component && cand.component !== 1) { + return resolve(); + } // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + + + if (sdpMLineIndex === 0 || sdpMLineIndex > 0 && transceiver.iceTransport !== pc.transceivers[0].iceTransport) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + return reject(makeError('OperationError', 'Can not add ICE candidate')); + } + } // update the remoteDescription. + + + var candidateString = candidate.candidate.trim(); + + if (candidateString.indexOf('a=') === 0) { + candidateString = candidateString.substr(2); + } + + sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); + sections[sdpMLineIndex] += 'a=' + (cand.type ? candidateString : 'end-of-candidates') + '\r\n'; + pc._remoteDescription.sdp = SDPUtils.getDescription(pc._remoteDescription.sdp) + sections.join(''); + } else { + return reject(makeError('OperationError', 'Can not add ICE candidate')); + } + } + + resolve(); + }); + }; + + RTCPeerConnection.prototype.getStats = function (selector) { + if (selector && selector instanceof window.MediaStreamTrack) { + var senderOrReceiver = null; + this.transceivers.forEach(function (transceiver) { + if (transceiver.rtpSender && transceiver.rtpSender.track === selector) { + senderOrReceiver = transceiver.rtpSender; + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track === selector) { + senderOrReceiver = transceiver.rtpReceiver; + } + }); + + if (!senderOrReceiver) { + throw makeError('InvalidAccessError', 'Invalid selector.'); + } + + return senderOrReceiver.getStats(); + } + + var promises = []; + this.transceivers.forEach(function (transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', 'dtlsTransport'].forEach(function (method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + return Promise.all(promises).then(function (allStats) { + var results = new Map(); + allStats.forEach(function (stats) { + stats.forEach(function (stat) { + results.set(stat.id, stat); + }); + }); + return results; + }); + }; // fix low-level stat names and return Map instead of object. + + + var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', 'RTCIceTransport', 'RTCDtlsTransport']; + ortcObjects.forEach(function (ortcObjectName) { + var obj = window[ortcObjectName]; + + if (obj && obj.prototype && obj.prototype.getStats) { + var nativeGetstats = obj.prototype.getStats; + + obj.prototype.getStats = function () { + return nativeGetstats.apply(this).then(function (nativeStats) { + var mapStats = new Map(); + Object.keys(nativeStats).forEach(function (id) { + nativeStats[id].type = fixStatsType(nativeStats[id]); + mapStats.set(id, nativeStats[id]); + }); + return mapStats; + }); + }; + } + }); // legacy callback shims. Should be moved to adapter.js some days. + + var methods = ['createOffer', 'createAnswer']; + methods.forEach(function (method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + + RTCPeerConnection.prototype[method] = function () { + var args = arguments; + + if (typeof args[0] === 'function' || typeof args[1] === 'function') { + // legacy + return nativeMethod.apply(this, [arguments[2]]).then(function (description) { + if (typeof args[0] === 'function') { + args[0].apply(null, [description]); + } + }, function (error) { + if (typeof args[1] === 'function') { + args[1].apply(null, [error]); + } + }); + } + + return nativeMethod.apply(this, arguments); + }; + }); + methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; + methods.forEach(function (method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + + RTCPeerConnection.prototype[method] = function () { + var args = arguments; + + if (typeof args[1] === 'function' || typeof args[2] === 'function') { + // legacy + return nativeMethod.apply(this, arguments).then(function () { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }, function (error) { + if (typeof args[2] === 'function') { + args[2].apply(null, [error]); + } + }); + } + + return nativeMethod.apply(this, arguments); + }; + }); // getStats is special. It doesn't have a spec legacy method yet we support + // getStats(something, cb) without error callbacks. + + ['getStats'].forEach(function (method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + + RTCPeerConnection.prototype[method] = function () { + var args = arguments; + + if (typeof args[1] === 'function') { + return nativeMethod.apply(this, arguments).then(function () { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }); + } + + return nativeMethod.apply(this, arguments); + }; + }); + return RTCPeerConnection; +}; + +},{"sdp":14}],14:[function(require,module,exports){ +/* eslint-env node */ +'use strict'; // SDP helpers. + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +var SDPUtils = {}; // Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 + +SDPUtils.generateIdentifier = function () { + return Math.random().toString(36).substr(2, 10); +}; // The RTCP CNAME used by all peerconnections from the same JS. + + +SDPUtils.localCName = SDPUtils.generateIdentifier(); // Splits SDP into lines, dealing with both CRLF and LF. + +SDPUtils.splitLines = function (blob) { + return blob.trim().split('\n').map(function (line) { + return line.trim(); + }); +}; // Splits SDP into sessionpart and mediasections. Ensures CRLF. + + +SDPUtils.splitSections = function (blob) { + var parts = blob.split('\nm='); + return parts.map(function (part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); +}; // returns the session description. + + +SDPUtils.getDescription = function (blob) { + var sections = SDPUtils.splitSections(blob); + return sections && sections[0]; +}; // returns the individual media sections. + + +SDPUtils.getMediaSections = function (blob) { + var sections = SDPUtils.splitSections(blob); + sections.shift(); + return sections; +}; // Returns lines that start with a certain prefix. + + +SDPUtils.matchPrefix = function (blob, prefix) { + return SDPUtils.splitLines(blob).filter(function (line) { + return line.indexOf(prefix) === 0; + }); +}; // Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" + + +SDPUtils.parseCandidate = function (line) { + var parts; // Parse both variants. + + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + address: parts[4], + // address is an alias for ip. + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compability. + + candidate.usernameFragment = parts[i + 1]; + break; + + default: + // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + + return candidate; +}; // Translates a candidate object into SDP candidate attribute. + + +SDPUtils.writeCandidate = function (candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.port); + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + + if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); + sdp.push('rport'); + sdp.push(candidate.relatedPort); + } + + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + + if (candidate.usernameFragment || candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.usernameFragment || candidate.ufrag); + } + + return 'candidate:' + sdp.join(' '); +}; // Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar + + +SDPUtils.parseIceOptions = function (line) { + return line.substr(14).split(' '); +}; // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 + + +SDPUtils.parseRtpMap = function (line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + + }; + parts = parts[0].split('/'); + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + + parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // legacy alias, got renamed back to channels in ORTC. + + parsed.numChannels = parsed.channels; + return parsed; +}; // Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. + + +SDPUtils.writeRtpMap = function (codec) { + var pt = codec.payloadType; + + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + + var channels = codec.channels || codec.numChannels || 1; + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; +}; // Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset + + +SDPUtils.parseExtmap = function (line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; +}; // Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. + + +SDPUtils.writeExtmap = function (headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + '\r\n'; +}; // Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on + + +SDPUtils.parseFmtp = function (line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + + return parsed; +}; // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. + + +SDPUtils.writeFmtp = function (codec) { + var line = ''; + var pt = codec.payloadType; + + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function (param) { + if (codec.parameters[param]) { + params.push(param + '=' + codec.parameters[param]); + } else { + params.push(param); + } + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + + return line; +}; // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi + + +SDPUtils.parseRtcpFb = function (line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; +}; // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. + + +SDPUtils.writeRtcpFb = function (codec) { + var lines = ''; + var pt = codec.payloadType; + + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function (fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; + }); + } + + return lines; +}; // Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something + + +SDPUtils.parseSsrcMedia = function (line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + + return parts; +}; + +SDPUtils.parseSsrcGroup = function (line) { + var parts = line.substr(13).split(' '); + return { + semantics: parts.shift(), + ssrcs: parts.map(function (ssrc) { + return parseInt(ssrc, 10); + }) + }; +}; // Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. + + +SDPUtils.getMid = function (mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + + if (mid) { + return mid.substr(6); + } +}; + +SDPUtils.parseFingerprint = function (line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), + // algorithm is case-sensitive in Edge. + value: parts[1] + }; +}; // Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. + + +SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; +}; // Serializes DTLS parameters to SDP. + + +SDPUtils.writeDtlsParameters = function (params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function (fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; +}; // Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. + + +SDPUtils.getIceParameters = function (mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); // Search in session part, too. + + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function (line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function (line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; +}; // Serializes ICE parameters to SDP. + + +SDPUtils.writeIceParameters = function (params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; +}; // Parses the SDP media section and returns RTCRtpParameters. + + +SDPUtils.parseRtpParameters = function (mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + + for (var i = 3; i < mline.length; i++) { + // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' '); // Only the first a=fmtp:<pt> is considered. + + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); // parse FEC mechanisms from rtpmap lines. + + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + + default: + // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function (line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); // FIXME: parse rtcp. + + return description; +}; // Generates parts of the SDP media section describing the capabilities / +// parameters. + + +SDPUtils.writeRtpDescription = function (kind, caps) { + var sdp = ''; // Build the mline. + + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function (codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + + return codec.payloadType; + }).join(' ') + '\r\n'; + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + + caps.codecs.forEach(function (codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function (codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + + sdp += 'a=rtcp-mux\r\n'; + + if (caps.headerExtensions) { + caps.headerExtensions.forEach(function (extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + } // FIXME: write fecMechanisms. + + + return sdp; +}; // Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. + + +SDPUtils.parseRtpEncodingParameters = function (mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; // filter a=ssrc:... cname:, ignore PlanB-msid + + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(function (line) { + var parts = line.substr(17).split(' '); + return parts.map(function (part) { + return parseInt(part, 10); + }); + }); + + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function (codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10) + }; + + if (primarySsrc && secondarySsrc) { + encParam.rtx = { + ssrc: secondarySsrc + }; + } + + encodingParameters.push(encParam); + + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: primarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } // we support both b=AS and b=TIAS but interpret AS as TIAS. + + + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8; + } else { + bandwidth = undefined; + } + + encodingParameters.forEach(function (params) { + params.maxBitrate = bandwidth; + }); + } + + return encodingParameters; +}; // parses http://draft.ortc.org/#rtcrtcpparameters* + + +SDPUtils.parseRtcpParameters = function (mediaSection) { + var rtcpParameters = {}; // Gets the first SSRC. Note tha with RTX there might be multiple + // SSRCs. + + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (obj) { + return obj.attribute === 'cname'; + })[0]; + + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + + + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + return rtcpParameters; +}; // parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. + + +SDPUtils.parseMsid = function (mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return { + stream: parts[0], + track: parts[1] + }; + } + + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { + return SDPUtils.parseSsrcMedia(line); + }).filter(function (msidParts) { + return msidParts.attribute === 'msid'; + }); + + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return { + stream: parts[0], + track: parts[1] + }; + } +}; // Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range + + +SDPUtils.generateSessionId = function () { + return Math.random().toString().substr(2, 21); +}; // Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' + + +SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + + var user = sessUser || 'thisisadapterortc'; // FIXME: sess-id should be an NTP timestamp. + + return 'v=0\r\n' + 'o=' + user + ' ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; +}; + +SDPUtils.writeMediaSection = function (transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); // Map ICE parameters (ufrag, pwd) to SDP. + + sdp += SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters()); // Map DTLS parameters to SDP. + + sdp += SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(), type === 'offer' ? 'actpass' : 'active'); + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.direction) { + sdp += 'a=' + transceiver.direction + '\r\n'; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + // spec. + var msid = 'msid:' + stream.id + ' ' + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; // for Chrome. + + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' ' + msid; + + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + transceiver.sendEncodingParameters[0].ssrc + ' ' + transceiver.sendEncodingParameters[0].rtx.ssrc + '\r\n'; + } + } // FIXME: this should be written by writeRtpDescription. + + + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; + + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + ' cname:' + SDPUtils.localCName + '\r\n'; + } + + return sdp; +}; // Gets the direction from the mediaSection or the sessionpart. + + +SDPUtils.getDirection = function (mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + + default: // FIXME: What should happen here? + + } + } + + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + + return 'sendrecv'; +}; + +SDPUtils.getKind = function (mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function (mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; +}; + +SDPUtils.parseMLine = function (mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var parts = lines[0].substr(2).split(' '); + return { + kind: parts[0], + port: parseInt(parts[1], 10), + protocol: parts[2], + fmt: parts.slice(3).join(' ') + }; +}; + +SDPUtils.parseOLine = function (mediaSection) { + var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; + var parts = line.substr(2).split(' '); + return { + username: parts[0], + sessionId: parts[1], + sessionVersion: parseInt(parts[2], 10), + netType: parts[3], + addressType: parts[4], + address: parts[5] + }; +}; // a very naive interpretation of a valid SDP. + + +SDPUtils.isValidSDP = function (blob) { + if (typeof blob !== 'string' || blob.length === 0) { + return false; + } + + var lines = SDPUtils.splitLines(blob); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { + return false; + } // TODO: check the modifier a bit more. + + } + + return true; +}; // Expose public methods. + + +if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === 'object') { + module.exports = SDPUtils; +} + +},{}],15:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +var _adapter_factory = require("./adapter_factory.js"); + +var adapter = (0, _adapter_factory.adapterFactory)({ + window: window +}); +module.exports = adapter; // this is the difference from adapter_core. + +},{"./adapter_factory.js":16}],16:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.adapterFactory = adapterFactory; + +var utils = _interopRequireWildcard(require("./utils")); + +var chromeShim = _interopRequireWildcard(require("./chrome/chrome_shim")); + +var edgeShim = _interopRequireWildcard(require("./edge/edge_shim")); + +var firefoxShim = _interopRequireWildcard(require("./firefox/firefox_shim")); + +var safariShim = _interopRequireWildcard(require("./safari/safari_shim")); + +var commonShim = _interopRequireWildcard(require("./common_shim")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +// Browser shims. +// Shimming starts here. +function adapterFactory() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + window = _ref.window; + + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + shimChrome: true, + shimFirefox: true, + shimEdge: true, + shimSafari: true + }; + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; // Shim browser if found. + + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + + logging('adapter.js shimming chrome.'); // Export to the adapter global object visible in the browser. + + adapter.browserShim = chromeShim; + chromeShim.shimGetUserMedia(window); + chromeShim.shimMediaStream(window); + chromeShim.shimPeerConnection(window); + chromeShim.shimOnTrack(window); + chromeShim.shimAddTrackRemoveTrack(window); + chromeShim.shimGetSendersWithDtmf(window); + chromeShim.shimSenderReceiverGetStats(window); + chromeShim.fixNegotiationNeeded(window); + commonShim.shimRTCIceCandidate(window); + commonShim.shimConnectionState(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + commonShim.removeAllowExtmapMixed(window); + break; + + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + + logging('adapter.js shimming firefox.'); // Export to the adapter global object visible in the browser. + + adapter.browserShim = firefoxShim; + firefoxShim.shimGetUserMedia(window); + firefoxShim.shimPeerConnection(window); + firefoxShim.shimOnTrack(window); + firefoxShim.shimRemoveStream(window); + firefoxShim.shimSenderGetStats(window); + firefoxShim.shimReceiverGetStats(window); + firefoxShim.shimRTCDataChannel(window); + commonShim.shimRTCIceCandidate(window); + commonShim.shimConnectionState(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { + logging('MS edge shim is not included in this adapter release.'); + return adapter; + } + + logging('adapter.js shimming edge.'); // Export to the adapter global object visible in the browser. + + adapter.browserShim = edgeShim; + edgeShim.shimGetUserMedia(window); + edgeShim.shimGetDisplayMedia(window); + edgeShim.shimPeerConnection(window); + edgeShim.shimReplaceTrack(window); // the edge shim implements the full RTCIceCandidate object. + + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + + logging('adapter.js shimming safari.'); // Export to the adapter global object visible in the browser. + + adapter.browserShim = safariShim; + safariShim.shimRTCIceServerUrls(window); + safariShim.shimCreateOfferLegacy(window); + safariShim.shimCallbacksAPI(window); + safariShim.shimLocalStreamsAPI(window); + safariShim.shimRemoteStreamsAPI(window); + safariShim.shimTrackEventTransceiver(window); + safariShim.shimGetUserMedia(window); + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + commonShim.removeAllowExtmapMixed(window); + break; + + default: + logging('Unsupported browser!'); + break; + } + + return adapter; +} + +},{"./chrome/chrome_shim":17,"./common_shim":20,"./edge/edge_shim":21,"./firefox/firefox_shim":25,"./safari/safari_shim":28,"./utils":29}],17:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimMediaStream = shimMediaStream; +exports.shimOnTrack = shimOnTrack; +exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; +exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; +exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; +exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; +exports.shimPeerConnection = shimPeerConnection; +exports.fixNegotiationNeeded = fixNegotiationNeeded; +Object.defineProperty(exports, "shimGetUserMedia", { + enumerable: true, + get: function get() { + return _getusermedia.shimGetUserMedia; + } +}); +Object.defineProperty(exports, "shimGetDisplayMedia", { + enumerable: true, + get: function get() { + return _getdisplaymedia.shimGetDisplayMedia; + } +}); + +var utils = _interopRequireWildcard(require("../utils.js")); + +var _getusermedia = require("./getusermedia"); + +var _getdisplaymedia = require("./getdisplaymedia"); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +/* iterates the stats graph recursively. */ +function walkStats(stats, base, resultSet) { + if (!base || resultSet.has(base.id)) { + return; + } + + resultSet.set(base.id, base); + Object.keys(base).forEach(function (name) { + if (name.endsWith('Id')) { + walkStats(stats, stats.get(base[name]), resultSet); + } else if (name.endsWith('Ids')) { + base[name].forEach(function (id) { + walkStats(stats, stats.get(id), resultSet); + }); + } + }); +} +/* filter getStats for a sender/receiver track. */ + + +function filterStats(result, track, outbound) { + var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; + var filteredResult = new Map(); + + if (track === null) { + return filteredResult; + } + + var trackStats = []; + result.forEach(function (value) { + if (value.type === 'track' && value.trackIdentifier === track.id) { + trackStats.push(value); + } + }); + trackStats.forEach(function (trackStat) { + result.forEach(function (stats) { + if (stats.type === streamStatsType && stats.trackId === trackStat.id) { + walkStats(result, stats, filteredResult); + } + }); + }); + return filteredResult; +} + +function shimMediaStream(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; +} + +function shimOnTrack(window) { + if (_typeof(window) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function get() { + return this._ontrack; + }, + set: function set(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + + this.addEventListener('track', this._ontrack = f); + }, + enumerable: true, + configurable: true + }); + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + + window.RTCPeerConnection.prototype.setRemoteDescription = function () { + var _this = this; + + if (!this._ontrackpoly) { + this._ontrackpoly = function (e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function (te) { + var receiver; + + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = _this.getReceivers().find(function (r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = { + track: te.track + }; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = { + receiver: receiver + }; + event.streams = [e.stream]; + + _this.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function (track) { + var receiver; + + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = _this.getReceivers().find(function (r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = { + track: track + }; + } + + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = { + receiver: receiver + }; + event.streams = [e.stream]; + + _this.dispatchEvent(event); + }); + }; + + this.addEventListener('addstream', this._ontrackpoly); + } + + return origSetRemoteDescription.apply(this, arguments); + }; + } else { + // even if RTCRtpTransceiver is in window, it is only used and + // emitted in unified-plan. Unfortunately this means we need + // to unconditionally wrap the event. + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + if (!e.transceiver) { + Object.defineProperty(e, 'transceiver', { + value: { + receiver: e.receiver + } + }); + } + + return e; + }); + } +} + +function shimGetSendersWithDtmf(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if (_typeof(window) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) { + return { + track: track, + + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + + return this._dtmf; + }, + + _pc: pc + }; + }; // augment addTrack when getSenders is not available. + + + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function () { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + + window.RTCPeerConnection.prototype.addTrack = function (track, stream) { + var sender = origAddTrack.apply(this, arguments); + + if (!sender) { + sender = shimSenderWithDtmf(this, track); + + this._senders.push(sender); + } + + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + + window.RTCPeerConnection.prototype.removeTrack = function (sender) { + origRemoveTrack.apply(this, arguments); + + var idx = this._senders.indexOf(sender); + + if (idx !== -1) { + this._senders.splice(idx, 1); + } + }; + } + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + + window.RTCPeerConnection.prototype.addStream = function (stream) { + var _this2 = this; + + this._senders = this._senders || []; + origAddStream.apply(this, [stream]); + stream.getTracks().forEach(function (track) { + _this2._senders.push(shimSenderWithDtmf(_this2, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + + window.RTCPeerConnection.prototype.removeStream = function (stream) { + var _this3 = this; + + this._senders = this._senders || []; + origRemoveStream.apply(this, [stream]); + stream.getTracks().forEach(function (track) { + var sender = _this3._senders.find(function (s) { + return s.track === track; + }); + + if (sender) { + // remove sender + _this3._senders.splice(_this3._senders.indexOf(sender), 1); + } + }); + }; + } else if (_typeof(window) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + + window.RTCPeerConnection.prototype.getSenders = function () { + var _this4 = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this4; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function get() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + + return this._dtmf; + } + }); + } +} + +function shimSenderReceiverGetStats(window) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { + return; + } // shim sender stats. + + + if (!('getStats' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function () { + var _this5 = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this5; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function () { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + + window.RTCRtpSender.prototype.getStats = function () { + var sender = this; + return this._pc.getStats().then(function (result) { + return ( + /* Note: this will include stats of all senders that + * send a track with the same id as sender.track as + * it is not possible to identify the RTCRtpSender. + */ + filterStats(result, sender.track, true) + ); + }); + }; + } // shim receiver stats. + + + if (!('getStats' in window.RTCRtpReceiver.prototype)) { + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function () { + var _this6 = this; + + var receivers = origGetReceivers.apply(this, []); + receivers.forEach(function (receiver) { + return receiver._pc = _this6; + }); + return receivers; + }; + } + + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + e.receiver._pc = e.srcElement; + return e; + }); + + window.RTCRtpReceiver.prototype.getStats = function () { + var receiver = this; + return this._pc.getStats().then(function (result) { + return filterStats(result, receiver.track, false); + }); + }; + } + + if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { + return; + } // shim RTCPeerConnection.getStats(track). + + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + + window.RTCPeerConnection.prototype.getStats = function () { + if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { + var track = arguments[0]; + var sender; + var receiver; + var err; + this.getSenders().forEach(function (s) { + if (s.track === track) { + if (sender) { + err = true; + } else { + sender = s; + } + } + }); + this.getReceivers().forEach(function (r) { + if (r.track === track) { + if (receiver) { + err = true; + } else { + receiver = r; + } + } + + return r.track === track; + }); + + if (err || sender && receiver) { + return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); + } else if (sender) { + return sender.getStats(); + } else if (receiver) { + return receiver.getStats(); + } + + return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); + } + + return origGetStats.apply(this, arguments); + }; +} + +function shimAddTrackRemoveTrackWithNative(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function () { + var _this7 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function (streamId) { + return _this7._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + + window.RTCPeerConnection.prototype.addTrack = function (track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + var sender = origAddTrack.apply(this, arguments); + + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + + window.RTCPeerConnection.prototype.addStream = function (stream) { + var _this8 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + stream.getTracks().forEach(function (track) { + var alreadyExists = _this8.getSenders().find(function (s) { + return s.track === track; + }); + + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + }); + var existingSenders = this.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = this.getSenders().filter(function (newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + + window.RTCPeerConnection.prototype.removeStream = function (stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + + window.RTCPeerConnection.prototype.removeTrack = function (sender) { + var _this9 = this; + + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { + var idx = _this9._shimmedLocalStreams[streamId].indexOf(sender); + + if (idx !== -1) { + _this9._shimmedLocalStreams[streamId].splice(idx, 1); + } + + if (_this9._shimmedLocalStreams[streamId].length === 1) { + delete _this9._shimmedLocalStreams[streamId]; + } + }); + } + + return origRemoveTrack.apply(this, arguments); + }; +} + +function shimAddTrackRemoveTrack(window) { + if (!window.RTCPeerConnection) { + return; + } + + var browserDetails = utils.detectBrowser(window); // shim addTrack and removeTrack. + + if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { + return shimAddTrackRemoveTrackWithNative(window); + } // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + + + var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; + + window.RTCPeerConnection.prototype.getLocalStreams = function () { + var _this10 = this; + + var nativeStreams = origGetLocalStreams.apply(this); + this._reverseStreams = this._reverseStreams || {}; + return nativeStreams.map(function (stream) { + return _this10._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + + window.RTCPeerConnection.prototype.addStream = function (stream) { + var _this11 = this; + + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + stream.getTracks().forEach(function (track) { + var alreadyExists = _this11.getSenders().find(function (s) { + return s.track === track; + }); + + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + }); // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + + if (!this._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + this._streams[stream.id] = newStream; + this._reverseStreams[newStream.id] = stream; + stream = newStream; + } + + origAddStream.apply(this, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + + window.RTCPeerConnection.prototype.removeStream = function (stream) { + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + origRemoveStream.apply(this, [this._streams[stream.id] || stream]); + delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; + delete this._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function (track, stream) { + var _this12 = this; + + if (this.signalingState === 'closed') { + throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); + } + + var streams = [].slice.call(arguments, 1); + + if (streams.length !== 1 || !streams[0].getTracks().find(function (t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); + } + + var alreadyExists = this.getSenders().find(function (s) { + return s.track === track; + }); + + if (alreadyExists) { + throw new DOMException('Track already exists.', 'InvalidAccessError'); + } + + this._streams = this._streams || {}; + this._reverseStreams = this._reverseStreams || {}; + var oldStream = this._streams[stream.id]; + + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); // Trigger ONN async. + + Promise.resolve().then(function () { + _this12.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + this._streams[stream.id] = newStream; + this._reverseStreams[newStream.id] = stream; + this.addStream(newStream); + } + + return this.getSenders().find(function (s) { + return s.track === track; + }); + }; // replace the internal stream id with the external one and + // vice versa. + + + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function (internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function (internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + + ['createOffer', 'createAnswer'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + + window.RTCPeerConnection.prototype[method] = function () { + var _this13 = this; + + var args = arguments; + var isLegacyCall = arguments.length && typeof arguments[0] === 'function'; + + if (isLegacyCall) { + return nativeMethod.apply(this, [function (description) { + var desc = replaceInternalStreamId(_this13, description); + args[0].apply(null, [desc]); + }, function (err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2]]); + } + + return nativeMethod.apply(this, arguments).then(function (description) { + return replaceInternalStreamId(_this13, description); + }); + }; + }); + var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; + + window.RTCPeerConnection.prototype.setLocalDescription = function () { + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(this, arguments); + } + + arguments[0] = replaceExternalStreamId(this, arguments[0]); + return origSetLocalDescription.apply(this, arguments); + }; // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + + var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { + get: function get() { + var description = origLocalDescription.get.apply(this); + + if (description.type === '') { + return description; + } + + return replaceInternalStreamId(this, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function (sender) { + var _this14 = this; + + if (this.signalingState === 'closed') { + throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); + } // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + + + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + + var isLocal = sender._pc === this; + + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); + } // Search for the native stream the senders track belongs to. + + + this._streams = this._streams || {}; + var stream; + Object.keys(this._streams).forEach(function (streamid) { + var hasTrack = _this14._streams[streamid].getTracks().find(function (track) { + return sender.track === track; + }); + + if (hasTrack) { + stream = _this14._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + this.removeStream(this._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + + this.dispatchEvent(new Event('negotiationneeded')); + } + }; +} + +function shimPeerConnection(window) { + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + // very basic support for old versions. + window.RTCPeerConnection = window.webkitRTCPeerConnection; + } + + if (!window.RTCPeerConnection) { + return; + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + + window.RTCPeerConnection.prototype.getStats = function (selector, successCallback, errorCallback) { + var _this15 = this; + + var args = arguments; // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + + + if (origGetStats.length === 0 && (arguments.length === 0 || typeof arguments[0] !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function fixChromeStats_(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function (report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function (name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + return standardReport; + }; // shim getStats with maplike support + + + var makeMapStats = function makeMapStats(stats) { + return new Map(Object.keys(stats).map(function (key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function successCallbackWrapper_(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, arguments[0]]); + } // promise-support + + + return new Promise(function (resolve, reject) { + origGetStats.apply(_this15, [function (response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(successCallback, errorCallback); + }; // shim implicit creation of RTCSessionDescription/RTCIceCandidate + + + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + + window.RTCPeerConnection.prototype[method] = function () { + arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); // support for addIceCandidate(null or undefined) + + var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; + + window.RTCPeerConnection.prototype.addIceCandidate = function () { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + + return Promise.resolve(); + } + + return nativeAddIceCandidate.apply(this, arguments); + }; +} + +function fixNegotiationNeeded(window) { + utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { + var pc = e.target; + + if (pc.signalingState !== 'stable') { + return; + } + + return e; + }); +} + +},{"../utils.js":29,"./getdisplaymedia":18,"./getusermedia":19}],18:[function(require,module,exports){ +/* + * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetDisplayMedia = shimGetDisplayMedia; + +function shimGetDisplayMedia(window, getSourceId) { + if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + + if (!window.navigator.mediaDevices) { + return; + } // getSourceId is a function that returns a promise resolving with + // the sourceId of the screen/window/tab to be shared. + + + if (typeof getSourceId !== 'function') { + console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); + return; + } + + window.navigator.mediaDevices.getDisplayMedia = function (constraints) { + return getSourceId(constraints).then(function (sourceId) { + var widthSpecified = constraints.video && constraints.video.width; + var heightSpecified = constraints.video && constraints.video.height; + var frameRateSpecified = constraints.video && constraints.video.frameRate; + constraints.video = { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sourceId, + maxFrameRate: frameRateSpecified || 3 + } + }; + + if (widthSpecified) { + constraints.video.mandatory.maxWidth = widthSpecified; + } + + if (heightSpecified) { + constraints.video.mandatory.maxHeight = heightSpecified; + } + + return window.navigator.mediaDevices.getUserMedia(constraints); + }); + }; +} + +},{}],19:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetUserMedia = shimGetUserMedia; + +var utils = _interopRequireWildcard(require("../utils.js")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +var logging = utils.log; + +function shimGetUserMedia(window) { + var navigator = window && window.navigator; + + if (!navigator.mediaDevices) { + return; + } + + var browserDetails = utils.detectBrowser(window); + + var constraintsToChrome_ = function constraintsToChrome_(c) { + if (_typeof(c) !== 'object' || c.mandatory || c.optional) { + return c; + } + + var cc = {}; + Object.keys(c).forEach(function (key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + + var r = _typeof(c[key]) === 'object' ? c[key] : { + ideal: c[key] + }; + + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + + var oldname_ = function oldname_(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + + return name === 'deviceId' ? 'sourceId' : name; + }; + + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function (mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + + return cc; + }; + + var shimConstraints_ = function shimConstraints_(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + + constraints = JSON.parse(JSON.stringify(constraints)); + + if (constraints && _typeof(constraints.audio) === 'object') { + var remap = function remap(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + + if (constraints && _typeof(constraints.video) === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && (_typeof(face) === 'object' ? face : { + ideal: face + }); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches; + + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices().then(function (devices) { + devices = devices.filter(function (d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function (d) { + return matches.some(function (match) { + return d.label.toLowerCase().includes(match); + }); + }); + + if (!dev && devices.length && matches.includes('back')) { + dev = devices[devices.length - 1]; // more likely the back cam + } + + if (dev) { + constraints.video.deviceId = face.exact ? { + exact: dev.deviceId + } : { + ideal: dev.deviceId + }; + } + + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + + constraints.video = constraintsToChrome_(constraints.video); + } + + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function shimError_(e) { + if (browserDetails.version >= 64) { + return e; + } + + return { + name: { + PermissionDeniedError: 'NotAllowedError', + PermissionDismissedError: 'NotAllowedError', + InvalidStateError: 'NotAllowedError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotAllowedError', + MediaDeviceKillSwitchOn: 'NotAllowedError', + TabCaptureError: 'AbortError', + ScreenCaptureError: 'AbortError', + DeviceCaptureError: 'AbortError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraint || e.constraintName, + toString: function toString() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { + shimConstraints_(constraints, function (c) { + navigator.webkitGetUserMedia(c, onSuccess, function (e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + + navigator.getUserMedia = getUserMedia_.bind(navigator); // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + + var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); + + navigator.mediaDevices.getUserMedia = function (cs) { + return shimConstraints_(cs, function (c) { + return origGetUserMedia(c).then(function (stream) { + if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + + return stream; + }, function (e) { + return Promise.reject(shimError_(e)); + }); + }); + }; +} + +},{"../utils.js":29}],20:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimRTCIceCandidate = shimRTCIceCandidate; +exports.shimMaxMessageSize = shimMaxMessageSize; +exports.shimSendThrowTypeError = shimSendThrowTypeError; +exports.shimConnectionState = shimConnectionState; +exports.removeAllowExtmapMixed = removeAllowExtmapMixed; + +var _sdp = _interopRequireDefault(require("sdp")); + +var utils = _interopRequireWildcard(require("./utils")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function shimRTCIceCandidate(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + + window.RTCIceCandidate = function (args) { + // Remove the a= which shouldn't be part of the candidate string. + if (_typeof(args) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + if (args.candidate && args.candidate.length) { + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + + var parsedCandidate = _sdp.default.parseCandidate(args.candidate); + + var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); // Add a serializer that does not serialize the extra attributes. + + augmentedCandidate.toJSON = function () { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment + }; + }; + + return augmentedCandidate; + } + + return new NativeRTCIceCandidate(args); + }; + + window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + + utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + + return e; + }); +} + +function shimMaxMessageSize(window) { + if (window.RTCSctpTransport || !window.RTCPeerConnection) { + return; + } + + var browserDetails = utils.detectBrowser(window); + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function get() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function sctpInDescription(description) { + var sections = _sdp.default.splitSections(description.sdp); + + sections.shift(); + return sections.some(function (mediaSection) { + var mLine = _sdp.default.parseMLine(mediaSection); + + return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + + if (match === null || match.length < 2) { + return -1; + } + + var version = parseInt(match[1], 10); // Test for NaN (yes, this is ugly) + + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else if (browserDetails.version < 60) { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; + } else { + // FF >= 60 supports sending ~2 GiB + canSendMaxMessageSize = 2147483637; + } + } + + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + + if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = _sdp.default.matchPrefix(description.sdp, 'a=max-message-size:'); + + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + + return maxMessageSize; + }; + + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + + window.RTCPeerConnection.prototype.setRemoteDescription = function () { + this._sctp = null; + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); // Get the maximum message size the local peer is capable of sending + + var canSendMMS = getCanSendMaxMessageSize(isFirefox); // Get the maximum message size of the remote peer. + + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); // Determine final maximum message size + + var maxMessageSize; + + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + + + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function get() { + return maxMessageSize; + } + }); + this._sctp = sctp; + } + + return origSetRemoteDescription.apply(this, arguments); + }; +} + +function shimSendThrowTypeError(window) { + if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { + return; + } // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + + function wrapDcSend(dc, pc) { + var origDataChannelSend = dc.send; + + dc.send = function () { + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + + if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { + throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); + } + + return origDataChannelSend.apply(dc, arguments); + }; + } + + var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; + + window.RTCPeerConnection.prototype.createDataChannel = function () { + var dataChannel = origCreateDataChannel.apply(this, arguments); + wrapDcSend(dataChannel, this); + return dataChannel; + }; + + utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) { + wrapDcSend(e.channel, e.target); + return e; + }); +} +/* shims RTCConnectionState by pretending it is the same as iceConnectionState. + * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 + * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect + * since DTLS failures would be hidden. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 + * for the Firefox tracking bug. + */ + + +function shimConnectionState(window) { + if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { + return; + } + + var proto = window.RTCPeerConnection.prototype; + Object.defineProperty(proto, 'connectionState', { + get: function get() { + return { + completed: 'connected', + checking: 'connecting' + }[this.iceConnectionState] || this.iceConnectionState; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(proto, 'onconnectionstatechange', { + get: function get() { + return this._onconnectionstatechange || null; + }, + set: function set(cb) { + if (this._onconnectionstatechange) { + this.removeEventListener('connectionstatechange', this._onconnectionstatechange); + delete this._onconnectionstatechange; + } + + if (cb) { + this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); + } + }, + enumerable: true, + configurable: true + }); + ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { + var origMethod = proto[method]; + + proto[method] = function () { + if (!this._connectionstatechangepoly) { + this._connectionstatechangepoly = function (e) { + var pc = e.target; + + if (pc._lastConnectionState !== pc.connectionState) { + pc._lastConnectionState = pc.connectionState; + var newEvent = new Event('connectionstatechange', e); + pc.dispatchEvent(newEvent); + } + + return e; + }; + + this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); + } + + return origMethod.apply(this, arguments); + }; + }); +} + +function removeAllowExtmapMixed(window) { + /* remove a=extmap-allow-mixed for Chrome < M71 */ + if (!window.RTCPeerConnection) { + return; + } + + var browserDetails = utils.detectBrowser(window); + + if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { + return; + } + + var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; + + window.RTCPeerConnection.prototype.setRemoteDescription = function (desc) { + if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { + desc.sdp = desc.sdp.split('\n').filter(function (line) { + return line.trim() !== 'a=extmap-allow-mixed'; + }).join('\n'); + } + + return nativeSRD.apply(this, arguments); + }; +} + +},{"./utils":29,"sdp":14}],21:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimPeerConnection = shimPeerConnection; +exports.shimReplaceTrack = shimReplaceTrack; +Object.defineProperty(exports, "shimGetUserMedia", { + enumerable: true, + get: function get() { + return _getusermedia.shimGetUserMedia; + } +}); +Object.defineProperty(exports, "shimGetDisplayMedia", { + enumerable: true, + get: function get() { + return _getdisplaymedia.shimGetDisplayMedia; + } +}); + +var utils = _interopRequireWildcard(require("../utils")); + +var _filtericeservers = require("./filtericeservers"); + +var _rtcpeerconnectionShim = _interopRequireDefault(require("rtcpeerconnection-shim")); + +var _getusermedia = require("./getusermedia"); + +var _getdisplaymedia = require("./getdisplaymedia"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function shimPeerConnection(window) { + var browserDetails = utils.detectBrowser(window); + + if (window.RTCIceGatherer) { + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function (args) { + return args; + }; + } + + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function (args) { + return args; + }; + } // this adds an additional event listener to MediaStrackTrack that signals + // when a tracks enabled property was changed. Workaround for a bug in + // addStream, see below. No longer required in 15025+ + + + if (browserDetails.version < 15025) { + var origMSTEnabled = Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype, 'enabled'); + Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { + set: function set(value) { + origMSTEnabled.set.call(this, value); + var ev = new Event('enabled'); + ev.enabled = value; + this.dispatchEvent(ev); + } + }); + } + } // ORTC defines the DTMF sender a bit different. + // https://github.com/w3c/ortc/issues/714 + + + if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function get() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = new window.RTCDtmfSender(this); + } else if (this.track.kind === 'video') { + this._dtmf = null; + } + } + + return this._dtmf; + } + }); + } // Edge currently only implements the RTCDtmfSender, not the + // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* + + + if (window.RTCDtmfSender && !window.RTCDTMFSender) { + window.RTCDTMFSender = window.RTCDtmfSender; + } + + var RTCPeerConnectionShim = (0, _rtcpeerconnectionShim.default)(window, browserDetails.version); + + window.RTCPeerConnection = function (config) { + if (config && config.iceServers) { + config.iceServers = (0, _filtericeservers.filterIceServers)(config.iceServers, browserDetails.version); + utils.log('ICE servers after filtering:', config.iceServers); + } + + return new RTCPeerConnectionShim(config); + }; + + window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; +} + +function shimReplaceTrack(window) { + // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 + if (window.RTCRtpSender && !('replaceTrack' in window.RTCRtpSender.prototype)) { + window.RTCRtpSender.prototype.replaceTrack = window.RTCRtpSender.prototype.setTrack; + } +} + +},{"../utils":29,"./filtericeservers":22,"./getdisplaymedia":23,"./getusermedia":24,"rtcpeerconnection-shim":13}],22:[function(require,module,exports){ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.filterIceServers = filterIceServers; + +var utils = _interopRequireWildcard(require("../utils")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +// Edge does not like +// 1) stun: filtered after 14393 unless ?transport=udp is present +// 2) turn: that does not have all of turn:host:port?transport=udp +// 3) turn: with ipv6 addresses +// 4) turn: occurring muliple times +function filterIceServers(iceServers, edgeVersion) { + var hasTurn = false; + iceServers = JSON.parse(JSON.stringify(iceServers)); + return iceServers.filter(function (server) { + if (server && (server.urls || server.url)) { + var urls = server.urls || server.url; + + if (server.url && !server.urls) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + } + + var isString = typeof urls === 'string'; + + if (isString) { + urls = [urls]; + } + + urls = urls.filter(function (url) { + // filter STUN unconditionally. + if (url.indexOf('stun:') === 0) { + return false; + } + + var validTurn = url.startsWith('turn') && !url.startsWith('turn:[') && url.includes('transport=udp'); + + if (validTurn && !hasTurn) { + hasTurn = true; + return true; + } + + return validTurn && !hasTurn; + }); + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} + +},{"../utils":29}],23:[function(require,module,exports){ +/* + * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetDisplayMedia = shimGetDisplayMedia; + +function shimGetDisplayMedia(window) { + if (!('getDisplayMedia' in window.navigator)) { + return; + } + + if (!window.navigator.mediaDevices) { + return; + } + + if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + + window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(window.navigator); +} + +},{}],24:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetUserMedia = shimGetUserMedia; + +function shimGetUserMedia(window) { + var navigator = window && window.navigator; + + var shimError_ = function shimError_(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function toString() { + return this.name; + } + }; + }; // getUserMedia error shim. + + + var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); + + navigator.mediaDevices.getUserMedia = function (c) { + return origGetUserMedia(c).catch(function (e) { + return Promise.reject(shimError_(e)); + }); + }; +} + +},{}],25:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimOnTrack = shimOnTrack; +exports.shimPeerConnection = shimPeerConnection; +exports.shimSenderGetStats = shimSenderGetStats; +exports.shimReceiverGetStats = shimReceiverGetStats; +exports.shimRemoveStream = shimRemoveStream; +exports.shimRTCDataChannel = shimRTCDataChannel; +Object.defineProperty(exports, "shimGetUserMedia", { + enumerable: true, + get: function get() { + return _getusermedia.shimGetUserMedia; + } +}); +Object.defineProperty(exports, "shimGetDisplayMedia", { + enumerable: true, + get: function get() { + return _getdisplaymedia.shimGetDisplayMedia; + } +}); + +var utils = _interopRequireWildcard(require("../utils")); + +var _getusermedia = require("./getusermedia"); + +var _getdisplaymedia = require("./getdisplaymedia"); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function shimOnTrack(window) { + if (_typeof(window) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function get() { + return { + receiver: this.receiver + }; + } + }); + } +} + +function shimPeerConnection(window) { + var browserDetails = utils.detectBrowser(window); + + if (_typeof(window) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + + if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { + // very basic support for old versions. + window.RTCPeerConnection = window.mozRTCPeerConnection; + } // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + + + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + + window.RTCPeerConnection.prototype[method] = function () { + arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); // support for addIceCandidate(null or undefined) + + var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; + + window.RTCPeerConnection.prototype.addIceCandidate = function () { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + + return Promise.resolve(); + } + + return nativeAddIceCandidate.apply(this, arguments); + }; + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + + window.RTCPeerConnection.prototype.getStats = function (selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]).then(function (stats) { + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function (stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + + + stats.forEach(function (stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + + return stats; + }).then(onSucc, onErr); + }; +} + +function shimSenderGetStats(window) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + return; + } + + if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { + return; + } + + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function () { + var _this = this; + + var senders = origGetSenders.apply(this, []); + senders.forEach(function (sender) { + return sender._pc = _this; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function () { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + + window.RTCRtpSender.prototype.getStats = function () { + return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); + }; +} + +function shimReceiverGetStats(window) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + return; + } + + if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { + return; + } + + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function () { + var _this2 = this; + + var receivers = origGetReceivers.apply(this, []); + receivers.forEach(function (receiver) { + return receiver._pc = _this2; + }); + return receivers; + }; + } + + utils.wrapPeerConnectionEvent(window, 'track', function (e) { + e.receiver._pc = e.srcElement; + return e; + }); + + window.RTCRtpReceiver.prototype.getStats = function () { + return this._pc.getStats(this.track); + }; +} + +function shimRemoveStream(window) { + if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + + window.RTCPeerConnection.prototype.removeStream = function (stream) { + var _this3 = this; + + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function (sender) { + if (sender.track && stream.getTracks().includes(sender.track)) { + _this3.removeTrack(sender); + } + }); + }; +} + +function shimRTCDataChannel(window) { + // rename DataChannel to RTCDataChannel (native fix in FF60): + // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 + if (window.DataChannel && !window.RTCDataChannel) { + window.RTCDataChannel = window.DataChannel; + } +} + +},{"../utils":29,"./getdisplaymedia":26,"./getusermedia":27}],26:[function(require,module,exports){ +/* + * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetDisplayMedia = shimGetDisplayMedia; + +function shimGetDisplayMedia(window, preferredMediaSource) { + if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + + if (!window.navigator.mediaDevices) { + return; + } + + window.navigator.mediaDevices.getDisplayMedia = function (constraints) { + if (!(constraints && constraints.video)) { + var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); + err.name = 'NotFoundError'; // from https://heycam.github.io/webidl/#idl-DOMException-error-names + + err.code = 8; + return Promise.reject(err); + } + + if (constraints.video === true) { + constraints.video = { + mediaSource: preferredMediaSource + }; + } else { + constraints.video.mediaSource = preferredMediaSource; + } + + return window.navigator.mediaDevices.getUserMedia(constraints); + }; +} + +},{}],27:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimGetUserMedia = shimGetUserMedia; + +var utils = _interopRequireWildcard(require("../utils")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function shimGetUserMedia(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + navigator.getUserMedia = function (constraints, onSuccess, onError) { + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; + + if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function remap(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); + + navigator.mediaDevices.getUserMedia = function (c) { + if (_typeof(c) === 'object' && _typeof(c.audio) === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + + MediaStreamTrack.prototype.getSettings = function () { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + + MediaStreamTrack.prototype.applyConstraints = function (c) { + if (this.kind === 'audio' && _typeof(c) === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + + return nativeApplyConstraints.apply(this, [c]); + }; + } + } +} + +},{"../utils":29}],28:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shimLocalStreamsAPI = shimLocalStreamsAPI; +exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; +exports.shimCallbacksAPI = shimCallbacksAPI; +exports.shimGetUserMedia = shimGetUserMedia; +exports.shimConstraints = shimConstraints; +exports.shimRTCIceServerUrls = shimRTCIceServerUrls; +exports.shimTrackEventTransceiver = shimTrackEventTransceiver; +exports.shimCreateOfferLegacy = shimCreateOfferLegacy; + +var utils = _interopRequireWildcard(require("../utils")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function shimLocalStreamsAPI(window) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { + return; + } + + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function () { + if (!this._localStreams) { + this._localStreams = []; + } + + return this._localStreams; + }; + } + + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + + window.RTCPeerConnection.prototype.addStream = function (stream) { + var _this = this; + + if (!this._localStreams) { + this._localStreams = []; + } + + if (!this._localStreams.includes(stream)) { + this._localStreams.push(stream); + } + + stream.getTracks().forEach(function (track) { + return _addTrack.call(_this, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function (track, stream) { + if (stream) { + if (!this._localStreams) { + this._localStreams = [stream]; + } else if (!this._localStreams.includes(stream)) { + this._localStreams.push(stream); + } + } + + return _addTrack.call(this, track, stream); + }; + } + + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function (stream) { + var _this2 = this; + + if (!this._localStreams) { + this._localStreams = []; + } + + var index = this._localStreams.indexOf(stream); + + if (index === -1) { + return; + } + + this._localStreams.splice(index, 1); + + var tracks = stream.getTracks(); + this.getSenders().forEach(function (sender) { + if (tracks.includes(sender.track)) { + _this2.removeTrack(sender); + } + }); + }; + } +} + +function shimRemoteStreamsAPI(window) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { + return; + } + + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function () { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function get() { + return this._onaddstream; + }, + set: function set(f) { + var _this3 = this; + + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); + } + + this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function (e) { + e.streams.forEach(function (stream) { + if (!_this3._remoteStreams) { + _this3._remoteStreams = []; + } + + if (_this3._remoteStreams.includes(stream)) { + return; + } + + _this3._remoteStreams.push(stream); + + var event = new Event('addstream'); + event.stream = stream; + + _this3.dispatchEvent(event); + }); + }); + } + }); + var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; + + window.RTCPeerConnection.prototype.setRemoteDescription = function () { + var pc = this; + + if (!this._onaddstreampoly) { + this.addEventListener('track', this._onaddstreampoly = function (e) { + e.streams.forEach(function (stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + + pc._remoteStreams.push(stream); + + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + + return origSetRemoteDescription.apply(pc, arguments); + }; + } +} + +function shimCallbacksAPI(window) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { + return; + } + + var prototype = window.RTCPeerConnection.prototype; + var createOffer = prototype.createOffer; + var createAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function (successCallback, failureCallback) { + var options = arguments.length >= 2 ? arguments[2] : arguments[0]; + var promise = createOffer.apply(this, [options]); + + if (!failureCallback) { + return promise; + } + + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function (successCallback, failureCallback) { + var options = arguments.length >= 2 ? arguments[2] : arguments[0]; + var promise = createAnswer.apply(this, [options]); + + if (!failureCallback) { + return promise; + } + + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function withCallback(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + + if (!failureCallback) { + return promise; + } + + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.setLocalDescription = withCallback; + + withCallback = function withCallback(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + + if (!failureCallback) { + return promise; + } + + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.setRemoteDescription = withCallback; + + withCallback = function withCallback(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + + if (!failureCallback) { + return promise; + } + + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.addIceCandidate = withCallback; +} + +function shimGetUserMedia(window) { + var navigator = window && window.navigator; + + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + // shim not needed in Safari 12.1 + var mediaDevices = navigator.mediaDevices; + + var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); + + navigator.mediaDevices.getUserMedia = function (constraints) { + return _getUserMedia(shimConstraints(constraints)); + }; + } + + if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function (constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); + }.bind(navigator); + } +} + +function shimConstraints(constraints) { + if (constraints && constraints.video !== undefined) { + return Object.assign({}, constraints, { + video: utils.compactObject(constraints.video) + }); + } + + return constraints; +} + +function shimRTCIceServerUrls(window) { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + + window.RTCPeerConnection = function (pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + + if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + + pcConfig.iceServers = newIceServers; + } + + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; // wrap static methods. Currently just generateCertificate. + + if ('generateCertificate' in window.RTCPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function get() { + return OrigPeerConnection.generateCertificate; + } + }); + } +} + +function shimTrackEventTransceiver(window) { + // Add event.transceiver member over deprecated event.receiver + if (_typeof(window) === 'object' && window.RTCPeerConnection && 'receiver' in window.RTCTrackEvent.prototype && // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is + // defined for some reason even when window.RTCTransceiver is not. + !window.RTCTransceiver) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function get() { + return { + receiver: this.receiver + }; + } + }); + } +} + +function shimCreateOfferLegacy(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + + window.RTCPeerConnection.prototype.createOffer = function (offerOptions) { + if (offerOptions) { + if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { + // support bit values + offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; + } + + var audioTransceiver = this.getTransceivers().find(function (transceiver) { + return transceiver.sender.track && transceiver.sender.track.kind === 'audio'; + }); + + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { + this.addTransceiver('audio'); + } + + if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { + // support bit values + offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; + } + + var videoTransceiver = this.getTransceivers().find(function (transceiver) { + return transceiver.sender.track && transceiver.sender.track.kind === 'video'; + }); + + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + if (videoTransceiver.setDirection) { + videoTransceiver.setDirection('sendonly'); + } else { + videoTransceiver.direction = 'sendonly'; + } + } else if (videoTransceiver.direction === 'recvonly') { + if (videoTransceiver.setDirection) { + videoTransceiver.setDirection('inactive'); + } else { + videoTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { + this.addTransceiver('video'); + } + } + + return origCreateOffer.apply(this, arguments); + }; +} + +},{"../utils":29}],29:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* eslint-env node */ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.extractVersion = extractVersion; +exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; +exports.disableLog = disableLog; +exports.disableWarnings = disableWarnings; +exports.log = log; +exports.deprecated = deprecated; +exports.detectBrowser = detectBrowser; +exports.compactObject = compactObject; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +var logDisabled_ = true; +var deprecationWarnings_ = true; +/** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + +function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); +} // Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object (or false to prevent +// the event). + + +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + + proto.addEventListener = function (nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + + var wrappedCallback = function wrappedCallback(e) { + var modifiedEvent = wrapper(e); + + if (modifiedEvent) { + cb(modifiedEvent); + } + }; + + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + + proto.removeEventListener = function (nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function get() { + return this['_on' + eventNameToWrap]; + }, + set: function set(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + + if (cb) { + this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); + } + }, + enumerable: true, + configurable: true + }); +} + +function disableLog(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.'); + } + + logDisabled_ = bool; + return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; +} +/** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + + +function disableWarnings(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.'); + } + + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); +} + +function log() { + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') { + if (logDisabled_) { + return; + } + + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } +} +/** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + + +function deprecated(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); +} +/** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + + +function detectBrowser(window) { + var navigator = window.navigator; // Returned result object. + + var result = { + browser: null, + version: null + }; // Fail early if it's not a browser + + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + if (navigator.mozGetUserMedia) { + // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + // Edge. + result.browser = 'edge'; + result.version = extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); + } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { + // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); + } else { + // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; +} +/** + * Remove all empty objects and undefined values + * from a nested object -- an enhanced and vanilla version + * of Lodash's `compact`. + */ + + +function compactObject(data) { + if (_typeof(data) !== 'object') { + return data; + } + + return Object.keys(data).reduce(function (accumulator, key) { + var isObject = _typeof(data[key]) === 'object'; + var value = isObject ? compactObject(data[key]) : data[key]; + var isEmptyObject = isObject && !Object.keys(value).length; + + if (value === undefined || isEmptyObject) { + return accumulator; + } + + return Object.assign(accumulator, _defineProperty({}, key, value)); + }, {}); +} + +},{}],30:[function(require,module,exports){ +"use strict"; + +// created by @HenrikJoreteg +var prefix; +var version; + +if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { + prefix = 'moz'; + version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); +} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { + prefix = 'webkit'; + version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); +} + +var PC = window.RTCPeerConnection || 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:' && (prefix === 'webkit' && version >= 26 || prefix === 'moz' && version >= 33); +var AudioContext = window.AudioContext || window.webkitAudioContext; +var videoEl = document.createElement('video'); +var supportVp8 = videoEl && videoEl.canPlayType && videoEl.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, + browserVersion: version, + support: !!PC && !!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, + // constructors + AudioContext: AudioContext, + PeerConnection: PC, + SessionDescription: SessionDescription, + IceCandidate: IceCandidate, + MediaStream: MediaStream, + getUserMedia: getUserMedia +}; + +},{}],31:[function(require,module,exports){ +"use strict"; + +/*
+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() {} + +WildEmitter.mixin = function (constructor) { + var prototype = constructor.prototype || constructor; + prototype.isWildEmitter = true; // Listen on the given `event` with `fn`. Store a group name if present. + + prototype.on = function (event, groupName, fn) { + this.callbacks = this.callbacks || {}; + 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. + + + 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 + + + prototype.releaseGroup = function (groupName) { + this.callbacks = this.callbacks || {}; + 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. + + + prototype.off = function (event, fn) { + this.callbacks = this.callbacks || {}; + 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); + + if (callbacks.length === 0) { + delete this.callbacks[event]; + } + + return this; + }; /// Emit `event` with the given args. + // also calls any `*` handlers + + + prototype.emit = function (event) { + this.callbacks = this.callbacks || {}; + 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]) { + break; + } + + listeners[i].apply(this, args); + } + } + + if (specialCallbacks) { + len = specialCallbacks.length; + listeners = specialCallbacks.slice(); + + for (i = 0, len = listeners.length; i < len; ++i) { + if (!listeners[i]) { + break; + } + + listeners[i].apply(this, [event].concat(args)); + } + } + + return this; + }; // Helper for for finding special wildcard event handlers that match the event + + + prototype.getWildcardCallbacks = function (eventName) { + this.callbacks = this.callbacks || {}; + 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; + }; +}; + +WildEmitter.mixin(WildEmitter); + +},{}]},{},[4])(4) +}); diff --git a/js/simplewebrtc/getscreenmedia.js b/js/simplewebrtc/getscreenmedia.js new file mode 100644 index 000000000..bbd44b9b7 --- /dev/null +++ b/js/simplewebrtc/getscreenmedia.js @@ -0,0 +1,182 @@ +// getScreenMedia helper by @HenrikJoreteg +var getUserMedia = function(constraints, callback) { + window.navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { + callback(null, stream); + }).catch(function(error) { + callback(error, null); + }); +}; + +// cache for constraints and callback +var cache = {}; + +module.exports = function (mode, constraints, cb) { + var hasConstraints = arguments.length === 3; + 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 (navigator.webkitGetUserMedia) { + var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(\d+)\./)[1], 10); + var maxver = 33; + // Chrome 71 dropped support for "window.chrome.webstore;". + var isCef = (chromever < 71) && !window.chrome.webstore; + // "known" crash in chrome 34 and 35 on linux + if (window.navigator.userAgent.match('Linux')) maxver = 35; + + // check that the extension is installed by looking for a + // sessionStorage variable that contains the extension id + // this has to be set after installation unless the contest + // script does that + if (sessionStorage.getScreenMediaJSExtensionId) { + chrome.runtime.sendMessage(sessionStorage.getScreenMediaJSExtensionId, + {type:'getScreen', id: 1}, null, + function (data) { + if (!data || data.sourceId === '') { // user canceled + var error = new Error('NavigatorUserMediaError'); + error.name = 'PERMISSION_DENIED'; + callback(error); + } else { + constraints = (hasConstraints && constraints) || {audio: false, video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + } + }}; + constraints.video.mandatory.chromeMediaSourceId = data.sourceId; + getUserMedia(constraints, callback); + } + } + ); + } else if (window.cefGetScreenMedia) { + //window.cefGetScreenMedia is experimental - may be removed without notice + window.cefGetScreenMedia(function(sourceId) { + if (!sourceId) { + var error = new Error('cefGetScreenMediaError'); + error.name = 'CEF_GETSCREENMEDIA_CANCELED'; + callback(error); + } else { + constraints = (hasConstraints && 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 = sourceId; + getUserMedia(constraints, callback); + } + }); + } else 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 ? constraints : 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 >= 52) { + mode = mode || 'window'; + constraints = (hasConstraints && constraints) || { + video: { + mozMediaSource: mode, + mediaSource: mode + } + }; + getUserMedia(constraints, function (err, stream) { + callback(err, stream); + if (err) { + return; + } + // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 + 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 = 'FF52_REQUIRED'; + return callback(error); + } + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + navigator.mediaDevices.getDisplayMedia({video: true}).then(function(stream) { + callback(null, stream); + }).catch(function(error) { + callback(error, null); + }); + } +}; + +typeof window !== 'undefined' && window.addEventListener('message', function (event) { + if (event.origin != window.location.origin && !event.isTrusted) { + 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); + } +}); diff --git a/js/simplewebrtc/localmedia.js b/js/simplewebrtc/localmedia.js new file mode 100644 index 000000000..0c749fd2f --- /dev/null +++ b/js/simplewebrtc/localmedia.js @@ -0,0 +1,337 @@ +var util = require('util'); +var hark = require('hark'); +var getScreenMedia = require('./getscreenmedia'); +var WildEmitter = require('wildemitter'); +var mockconsole = require('mockconsole'); + +function isAllTracksEnded(stream) { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + return isAllTracksEnded; +} + +function LocalMedia(opts) { + WildEmitter.call(this); + + var config = this.config = { + detectSpeakingEvents: false, + audioFallback: false, + media: { + audio: true, + video: true + }, + harkOptions: null, + logger: mockconsole + }; + + var item; + for (item in opts) { + if (opts.hasOwnProperty(item)) { + 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.localStreams = []; + this.localScreens = []; + + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + this._logerror('Your browser does not support local media capture.'); + } + + this._audioMonitors = []; + this.on('localStreamStopped', this._stopAudioMonitor.bind(this)); + this.on('localScreenStopped', this._stopAudioMonitor.bind(this)); +} + +util.inherits(LocalMedia, WildEmitter); + + +LocalMedia.prototype.start = function (mediaConstraints, cb) { + var self = this; + var constraints = mediaConstraints || this.config.media; + + this.emit('localStreamRequested', constraints); + + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + // Although the promise should be resolved only if all the constraints + // are met Edge resolves it if both audio and video are requested but + // only audio is available. + if (constraints.video && stream.getVideoTracks().length === 0) { + constraints.video = false; + self.start(constraints, cb); + return; + } + + if (constraints.audio && self.config.detectSpeakingEvents) { + self._setupAudioMonitor(stream, self.config.harkOptions); + } + self.localStreams.push(stream); + + stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + if (isAllTracksEnded(stream)) { + self._removeStream(stream); + } + }); + }); + + self.emit('localStream', stream); + + if (cb) { + return cb(null, stream); + } + }).catch(function (err) { + // Fallback for users without a camera + if (self.config.audioFallback && err.name === 'NotFoundError' && constraints.video !== false) { + constraints.video = false; + self.start(constraints, cb); + return; + } + + self.emit('localStreamRequestFailed', constraints); + + if (cb) { + return cb(err, null); + } + }); +}; + +LocalMedia.prototype.stop = function (stream) { + this.stopStream(stream); + this.stopScreenShare(stream); +}; + +LocalMedia.prototype.stopStream = function (stream) { + var self = this; + + if (stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + stream.getTracks().forEach(function (track) { track.stop(); }); + this._removeStream(stream); + } + } else { + this.localStreams.forEach(function (stream) { + stream.getTracks().forEach(function (track) { track.stop(); }); + self._removeStream(stream); + }); + } +}; + +LocalMedia.prototype.startScreenShare = function (mode, constraints, cb) { + var self = this; + + this.emit('localScreenRequested'); + + if (typeof constraints === 'function' && !cb) { + cb = constraints; + constraints = null; + } + + getScreenMedia(mode, constraints, function (err, stream) { + if (!err) { + self.localScreens.push(stream); + + stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + + if (isAllTracksEnded) { + self._removeStream(stream); + } + }); + }); + + self.emit('localScreen', stream); + } else { + self.emit('localScreenRequestFailed'); + } + + // enable the callback + if (cb) { + return cb(err, stream); + } + }); +}; + +LocalMedia.prototype.stopScreenShare = function (stream) { + var self = this; + + if (stream) { + var idx = this.localScreens.indexOf(stream); + if (idx > -1) { + stream.getTracks().forEach(function (track) { track.stop(); }); + this._removeStream(stream); + } + } else { + this.localScreens.forEach(function (stream) { + stream.getTracks().forEach(function (track) { track.stop(); }); + self._removeStream(stream); + }); + } +}; + +// Audio controls +LocalMedia.prototype.mute = function () { + this._audioEnabled(false); + this.emit('audioOff'); +}; + +LocalMedia.prototype.unmute = function () { + this._audioEnabled(true); + this.emit('audioOn'); +}; + +// 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) { + 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; + var hasAudioTracks = false; + this.localStreams.forEach(function (stream) { + var audioTracks = stream.getAudioTracks(); + if (audioTracks.length > 0) { + hasAudioTracks = true; + audioTracks.forEach(function (track) { + enabled = enabled && track.enabled; + }); + } + }); + + // If no audioTracks were found, that means there is no microphone device. + // In that case, isAudioEnabled should return false. + if (!hasAudioTracks) { + return false; + } + + return enabled; +}; + +// check if all video streams are enabled +LocalMedia.prototype.isVideoEnabled = function () { + var enabled = true; + var hasVideoTracks = false; + this.localStreams.forEach(function (stream) { + var videoTracks = stream.getVideoTracks(); + if (videoTracks.length > 0) { + hasVideoTracks = true; + videoTracks.forEach(function (track) { + enabled = enabled && track.enabled; + }); + } + }); + + // If no videoTracks were found, that means there is no camera device. + // In that case, isVideoEnabled should return false. + if (!hasVideoTracks) { + return false; + } + + return enabled; +}; + +LocalMedia.prototype._removeStream = function (stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this.emit('localStreamStopped', stream); + } else { + idx = this.localScreens.indexOf(stream); + if (idx > -1) { + this.localScreens.splice(idx, 1); + this.emit('localScreenStopped', stream); + } + } +}; + +LocalMedia.prototype._setupAudioMonitor = function (stream, harkOptions) { + this._log('Setup audio'); + var audio = hark(stream, harkOptions); + var self = this; + var timeout; + + audio.on('speaking', function () { + self.emit('speaking'); + }); + + audio.on('stopped_speaking', function () { + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(function () { + self.emit('stoppedSpeaking'); + }, 1000); + }); + audio.on('volume_change', function (volume, threshold) { + self.emit('volumeChange', volume, threshold); + }); + + this._audioMonitors.push({audio: audio, stream: stream}); +}; + +LocalMedia.prototype._stopAudioMonitor = function (stream) { + var idx = -1; + this._audioMonitors.forEach(function (monitors, i) { + if (monitors.stream === stream) { + idx = i; + } + }); + + if (idx > -1) { + this._audioMonitors[idx].audio.stop(); + this._audioMonitors.splice(idx, 1); + } +}; + +// fallback for old .localScreen behaviour +Object.defineProperty(LocalMedia.prototype, 'localScreen', { + get: function () { + return this.localScreens.length > 0 ? this.localScreens[0] : null; + } +}); + +module.exports = LocalMedia; diff --git a/js/simplewebrtc/peer.js b/js/simplewebrtc/peer.js new file mode 100644 index 000000000..3ddad179f --- /dev/null +++ b/js/simplewebrtc/peer.js @@ -0,0 +1,306 @@ +var util = require('util'); +var webrtcSupport = require('webrtcsupport'); +var WildEmitter = require('wildemitter'); + +function isAllTracksEnded(stream) { + var isAllTracksEnded = true; + stream.getTracks().forEach(function (t) { + isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded; + }); + return isAllTracksEnded; +} + +function Peer(options) { + var self = this; + + // call emitter constructor + WildEmitter.call(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.pendingDCMessages = []; // key (datachannel label) -> value (array[pending messages]) + this.sid = options.sid || Date.now().toString(); + this.pc = new RTCPeerConnection(this.parent.config.peerConnectionConfig); + this.pc.addEventListener('icecandidate', this.onIceCandidate.bind(this)); + this.pc.addEventListener('endofcandidates', function (event) { + self.send('endOfCandidates', event); + }); + this.pc.addEventListener('addstream', this.handleRemoteStreamAdded.bind(this)); + this.pc.addEventListener('datachannel', this.handleDataChannelAdded.bind(this)); + this.pc.addEventListener('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.addEventListener('negotiationneeded', this.emit.bind(this, 'negotiationNeeded')); + this.pc.addEventListener('iceconnectionstatechange', this.emit.bind(this, 'iceConnectionStateChange')); + this.pc.addEventListener('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.localDescription.type === 'offer') { + self.parent.emit('iceFailed', self); + self.send('connectivityError'); + } + break; + } + }); + this.pc.addEventListener('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); + }); + } + + // proxy events to parent + this.on('*', function () { + self.parent.emit.apply(self.parent, arguments); + }); +} + +util.inherits(Peer, WildEmitter); + +Peer.prototype.offer = function(options) { + this.pc.createOffer(options).then(function(offer) { + this.pc.setLocalDescription(offer).then(function() { + if (this.parent.config.nick) offer.nick = this.parent.config.nick; + this.send('offer', offer); + }.bind(this)).catch(function(error) { + console.warn("setLocalDescription for offer failed: ", error); + }.bind(this)); + }.bind(this)).catch(function(error) { + console.warn("createOffer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.handleOffer = function (offer) { + this.pc.setRemoteDescription(offer).then(function() { + this.answer(); + }.bind(this)).catch(function(error) { + console.warn("setRemoteDescription for offer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.answer = function() { + this.pc.createAnswer().then(function(answer) { + this.pc.setLocalDescription(answer).then(function() { + if (this.parent.config.nick) answer.nick = this.parent.config.nick; + this.send('answer', answer); + }.bind(this)).catch(function(error) { + console.warn("setLocalDescription for answer failed: ", error); + }.bind(this)); + }.bind(this)).catch(function(error) { + console.warn("createAnswer failed: ", error); + }.bind(this)); +}; + +Peer.prototype.handleAnswer = function (answer) { + this.pc.setRemoteDescription(answer).catch(function(error) { + console.warn("setRemoteDescription for answer failed: ", error); + }.bind(this)); +}; + +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') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.handleOffer(message.payload); + } else if (message.type === 'answer') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.handleAnswer(message.payload); + } else if (message.type === 'candidate') { + this.pc.addIceCandidate(message.payload.candidate); + } 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}); + } else if (message.type === 'endOfCandidates') { + this.pc.addIceCandidate(''); + } else if (message.type === 'unshareScreen') { + this.parent.emit('unshareScreen', {id: message.from}); + this.end(); + } +}; + +// 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: webrtcSupport.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') { + if (!this.pendingDCMessages.hasOwnProperty(channel)) { + this.pendingDCMessages[channel] = []; + } + this.pendingDCMessages[channel].push(message); + 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 = function () { + self.emit('channelOpen', channel); + // Check if there are messages that could not be send + if (self.pendingDCMessages.hasOwnProperty(channel.label)) { + var pendingMessages = self.pendingDCMessages[channel.label]; + for (var i = 0; i < pendingMessages.length; i++) { + self.sendDirectly(channel.label, pendingMessages[i].type, pendingMessages[i].payload); + } + self.pendingDCMessages[channel.label] = []; + } + }; +}; + +// Fetch or create a data channel by the given name +Peer.prototype.getDataChannel = function (name, opts) { + if (!webrtcSupport.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 (event) { + var candidate = event.candidate; + if (this.closed) return; + if (candidate) { + var pcConfig = this.parent.config.peerConnectionConfig; + if (webrtcSupport.prefix === 'moz' && pcConfig && pcConfig.iceTransports && + candidate.candidate && candidate.candidate.candidate && + candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) { + this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports); + } else { + // Retain legacy data structure for compatibility with + // mobile clients. + var expandedCandidate = { + candidate: { + candidate: candidate.candidate, + sdpMid: candidate.sdpMid, + sdpMLineIndex: candidate.sdpMLineIndex + } + }; + this.send('candidate', expandedCandidate); + } + } 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.offer(this.receiveMedia); +}; + +Peer.prototype.icerestart = function () { + var constraints = this.receiveMedia; + constraints.iceRestart = true; + this.offer(constraints); +}; + +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; + + this.stream.getTracks().forEach(function (track) { + track.addEventListener('ended', function () { + if (isAllTracksEnded(self.stream)) { + self.end(); + } + }); + }); + + this.parent.emit('peerStreamAdded', this); + } +}; + +Peer.prototype.handleStreamRemoved = function () { + var peerIndex = this.parent.peers.indexOf(this); + if (peerIndex > -1) { + this.parent.peers.splice(peerIndex, 1); + this.closed = true; + this.parent.emit('peerStreamRemoved', this); + } +}; + +Peer.prototype.handleDataChannelAdded = function (event) { + var channel = event.channel; + this.channels[channel.label] = channel; + this._observeDataChannel(channel); +}; + +module.exports = Peer; diff --git a/js/simplewebrtc/simplewebrtc.js b/js/simplewebrtc/simplewebrtc.js new file mode 100644 index 000000000..191fe4ea9 --- /dev/null +++ b/js/simplewebrtc/simplewebrtc.js @@ -0,0 +1,422 @@ +var WebRTC = require('./webrtc'); +var WildEmitter = require('wildemitter'); +var webrtcSupport = require('webrtcsupport'); +var attachMediaStream = require('attachmediastream'); +var mockconsole = require('mockconsole'); + +function SimpleWebRTC(opts) { + var self = this; + var options = opts || {}; + var config = this.config = { + socketio: {/* 'force new connection':true*/}, + connection: null, + debug: false, + localVideoEl: '', + remoteVideosEl: '', + enableDataChannels: true, + autoRequestMedia: false, + autoRemoveVideos: true, + adjustPeerVolume: false, + peerVolumeWhenSpeaking: 0.25, + media: { + video: true, + audio: true + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + localVideo: { + autoplay: true, + mirror: true, + muted: true + } + }; + var item, connection; + + // 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 our config from options + for (item in options) { + if (options.hasOwnProperty(item)) { + this.config[item] = options[item]; + } + } + + // attach detected support for convenience + this.capabilities = webrtcSupport; + + // call WildEmitter constructor + WildEmitter.call(this); + + if (this.config.connection === null) { + throw 'no connection object given in the configuration'; + } else { + connection = this.connection = this.config.connection; + } + + connection.on('connect', function () { + self.emit('connectionReady', connection.getSessionid()); + self.sessionReady = true; + self.testReadiness(); + }); + + connection.on('message', function (message) { + var peers = self.webrtc.getPeers(message.from, message.roomType); + var peer; + + if (message.type === 'offer') { + if (peers.length) { + peers.forEach(function (p) { + if (p.sid == message.sid) peer = p; + }); + //if (!peer) peer = peers[0]; // fallback for old protocol versions + } + if (!peer) { + peer = self.webrtc.createPeer({ + id: message.from, + sid: message.sid, + type: message.roomType, + enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen', + sharemyscreen: message.roomType === 'screen' && !message.broadcaster, + broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null + }); + self.emit('createdPeer', peer); + } + peer.handleMessage(message); + } else if (peers.length) { + peers.forEach(function (peer) { + if (message.sid) { + if (peer.sid === message.sid) { + peer.handleMessage(message); + } + } else { + peer.handleMessage(message); + } + }); + } + }); + + connection.on('remove', function (room) { + if (room.id !== self.connection.getSessionid()) { + self.webrtc.removePeers(room.id, room.type); + } + }); + + // instantiate our main WebRTC helper + // using same logger from logic here + opts.logger = this.logger; + opts.debug = false; + this.webrtc = new WebRTC(opts); + + // attach a few methods from underlying lib to simple. + ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers', 'createPeer', 'removePeers'].forEach(function (method) { + self[method] = self.webrtc[method].bind(self.webrtc); + }); + + // proxy events from WebRTC + this.webrtc.on('*', function () { + self.emit.apply(self, arguments); + }); + + // log all events in debug mode + if (config.debug) { + this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:')); + } + + // check for readiness + this.webrtc.on('localStream', function () { + self.testReadiness(); + }); + + this.webrtc.on('message', function (payload) { + self.connection.emit('message', payload); + }); + + this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this)); + this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this)); + + // echo cancellation attempts + if (this.config.adjustPeerVolume) { + this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking)); + this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1)); + } + + connection.on('stunservers', function (args) { + // resets/overrides the config + self.webrtc.config.peerConnectionConfig.iceServers = args; + self.emit('stunservers', args); + }); + connection.on('turnservers', function (args) { + // appends to the config + self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args); + self.emit('turnservers', args); + }); + + this.webrtc.on('iceFailed', function (peer) { + // local ice failure + }); + this.webrtc.on('connectivityError', function (peer) { + // remote ice failure + }); + + + // sending mute/unmute to all peers + this.webrtc.on('audioOn', function () { + self.webrtc.sendToAll('unmute', {name: 'audio'}); + }); + this.webrtc.on('audioOff', function () { + self.webrtc.sendToAll('mute', {name: 'audio'}); + }); + this.webrtc.on('videoOn', function () { + self.webrtc.sendToAll('unmute', {name: 'video'}); + }); + this.webrtc.on('videoOff', function () { + self.webrtc.sendToAll('mute', {name: 'video'}); + }); + + // screensharing events + this.webrtc.on('localScreen', function (stream) { + var item, + el = document.createElement('video'), + container = self.getRemoteVideoContainer(); + + el.oncontextmenu = function () { return false; }; + el.id = 'localScreen'; + attachMediaStream(stream, el); + if (container) { + container.appendChild(el); + } + + self.emit('localScreenAdded', el); + self.connection.emit('shareScreen'); + + // NOTE: we don't create screen peers for existing video peers here, + // this is done by the application code in "webrtc.js". + }); + this.webrtc.on('localScreenStopped', function (stream) { + if (self.getLocalScreen()) { + self.stopScreenShare(); + } + /* + self.connection.emit('unshareScreen'); + self.webrtc.peers.forEach(function (peer) { + if (peer.sharemyscreen) { + peer.end(); + } + }); + */ + }); + + this.webrtc.on('channelMessage', function (peer, label, data) { + if (data.type == 'volume') { + self.emit('remoteVolumeChange', peer, data.volume); + } + }); +} + + +SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, { + constructor: { + value: SimpleWebRTC + } +}); + +SimpleWebRTC.prototype.leaveCall = function () { + if (this.roomName) { + while (this.webrtc.peers.length) { + this.webrtc.peers[0].end(); + } + if (this.getLocalScreen()) { + this.stopScreenShare(); + } + this.emit('leftRoom', this.roomName); + this.stopLocalVideo(); + this.roomName = undefined; + } +}; + +SimpleWebRTC.prototype.disconnect = function () { + this.connection.disconnect(); + delete this.connection; +}; + +SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) { + var self = this; + var container = this.getRemoteVideoContainer(); + var video = attachMediaStream(peer.stream); + + // At least Firefox, Opera and Edge move the video to a wrong position + // instead of keeping it unchanged when "transform: scaleX(1)" is used + // ("transform: scaleX(-1)" is fine); as it should have no effect the + // transform is removed. + if (video.style.transform === 'scaleX(1)') { + video.style.transform = ''; + } + + // store video element as part of peer for easy removal + peer.videoEl = video; + video.id = this.getDomId(peer); + + if (container) container.appendChild(video); + + this.emit('videoAdded', video, peer); + + // send our mute status to new peer if we're muted + // currently called with a small delay because it arrives before + // the video element is created otherwise (which happens after + // the async setRemoteDescription-createAnswer) + window.setTimeout(function () { + if (!self.webrtc.isAudioEnabled()) { + peer.send('mute', {name: 'audio'}); + } + if (!self.webrtc.isVideoEnabled()) { + peer.send('mute', {name: 'video'}); + } + }, 250); +}; + +SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) { + var container = this.getRemoteVideoContainer(); + var videoEl = peer.videoEl; + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } + if (videoEl) this.emit('videoRemoved', videoEl, peer); +}; + +SimpleWebRTC.prototype.getDomId = function (peer) { + return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_'); +}; + +// set volume on video tag for all peers takse a value between 0 and 1 +SimpleWebRTC.prototype.setVolumeForAll = function (volume) { + this.webrtc.peers.forEach(function (peer) { + if (peer.videoEl) peer.videoEl.volume = volume; + }); +}; + +SimpleWebRTC.prototype.joinCall = function (name) { + if (this.config.autoRequestMedia) this.startLocalVideo(); + this.roomName = name; + this.emit('joinedRoom', name); +}; + +SimpleWebRTC.prototype.getEl = function (idOrEl) { + if (typeof idOrEl === 'string') { + return document.getElementById(idOrEl); + } else { + return idOrEl; + } +}; + +SimpleWebRTC.prototype.startLocalVideo = function () { + var self = this; + this.webrtc.start(this.config.media, function (err, stream) { + if (err) { + self.emit('localMediaError', err); + } else { + self.emit('localMediaStarted', self.config.media); + attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo); + } + }); +}; + +SimpleWebRTC.prototype.stopLocalVideo = function () { + this.webrtc.stop(); +}; + +// this accepts either element ID or element +// and either the video tag itself or a container +// that will be used to put the video tag into. +SimpleWebRTC.prototype.getLocalVideoContainer = function () { + var el = this.getEl(this.config.localVideoEl); + if (el && el.tagName === 'VIDEO') { + el.oncontextmenu = function () { return false; }; + return el; + } else if (el) { + var video = document.createElement('video'); + video.oncontextmenu = function () { return false; }; + el.appendChild(video); + return video; + } else { + return; + } +}; + +SimpleWebRTC.prototype.getRemoteVideoContainer = function () { + return this.getEl(this.config.remoteVideosEl); +}; + +SimpleWebRTC.prototype.shareScreen = function (mode, cb) { + this.webrtc.startScreenShare(mode, cb); +}; + +SimpleWebRTC.prototype.getLocalScreen = function () { + return this.webrtc.localScreen; +}; + +SimpleWebRTC.prototype.stopScreenShare = function () { + this.connection.emit('unshareScreen'); + var videoEl = document.getElementById('localScreen'); + var container = this.getRemoteVideoContainer(); + + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } + + // a hack to emit the event the removes the video + // element that we want + if (videoEl) { + this.emit('videoRemoved', videoEl); + } + if (this.getLocalScreen()) { + this.webrtc.stopScreenShare(); + } + // Notify peers were sending to. + this.webrtc.peers.forEach(function (peer) { + if (peer.type === 'screen' && peer.sharemyscreen) { + peer.send('unshareScreen'); + } + if (peer.broadcaster) { + peer.end(); + } + }); +}; + +SimpleWebRTC.prototype.testReadiness = function () { + var self = this; + if (this.sessionReady) { + if (!this.config.media.video && !this.config.media.audio) { + self.emit('readyToCall', self.connection.getSessionid()); + } else if (this.webrtc.localStreams.length > 0) { + self.emit('readyToCall', self.connection.getSessionid()); + } + } +}; + +SimpleWebRTC.prototype.createRoom = function (name, cb) { + this.roomName = name; + if (arguments.length === 2) { + this.connection.emit('create', name, cb); + } else { + this.connection.emit('create', name); + } +}; + +module.exports = SimpleWebRTC; diff --git a/js/simplewebrtc/webrtc.js b/js/simplewebrtc/webrtc.js new file mode 100644 index 000000000..f3be2e552 --- /dev/null +++ b/js/simplewebrtc/webrtc.js @@ -0,0 +1,164 @@ +var util = require('util'); +var webrtcSupport = require('webrtcsupport'); +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: [] + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + enableDataChannels: true + }; + var item; + + // 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) { + if (options.hasOwnProperty(item)) { + this.config[item] = options[item]; + } + } + + // check for support + if (!webrtcSupport.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 })); + } + }); + } + }); + + this.on('unshareScreen', function(message) { + // End peers we were receiving the screensharing stream from. + var peers = self.getPeers(message.from, 'screen'); + peers.forEach(function(peer) { + if (!peer.sharemyscreen) { + peer.end(); + } + }); + }); + + // 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; diff --git a/js/webrtc.js b/js/webrtc.js index aa5b75e74..c8e25aaed 100644 --- a/js/webrtc.js +++ b/js/webrtc.js @@ -467,7 +467,7 @@ var spreedPeerConnectionTable = []; // Initialize ice restart counter for peer spreedPeerConnectionTable[peer.id] = 0; - peer.pc.on('iceConnectionStateChange', function () { + peer.pc.addEventListener('iceconnectionstatechange', function () { var userId = spreedMappingTable[peer.id]; switch (peer.pc.iceConnectionState) { @@ -528,8 +528,8 @@ var spreedPeerConnectionTable = []; videoView.setConnectionStatus(OCA.Talk.Views.VideoView.ConnectionStatus.DISCONNECTED_LONG); if (spreedPeerConnectionTable[peer.id] < 5) { - if (peer.pc.pc.peerconnection.localDescription.type === 'offer' && - peer.pc.pc.peerconnection.signalingState === 'stable') { + if (peer.pc.localDescription.type === 'offer' && + peer.pc.signalingState === 'stable') { spreedPeerConnectionTable[peer.id] ++; console.log('ICE restart.'); peer.icerestart(); @@ -548,8 +548,8 @@ var spreedPeerConnectionTable = []; if (spreedPeerConnectionTable[peer.id] < 5) { videoView.setConnectionStatus(OCA.Talk.Views.VideoView.ConnectionStatus.FAILED); - if (peer.pc.pc.peerconnection.localDescription.type === 'offer' && - peer.pc.pc.peerconnection.signalingState === 'stable') { + if (peer.pc.localDescription.type === 'offer' && + peer.pc.signalingState === 'stable') { spreedPeerConnectionTable[peer.id] ++; console.log('ICE restart.'); peer.icerestart(); @@ -570,10 +570,6 @@ var spreedPeerConnectionTable = []; OCA.SpreedMe.speakers.updateVideoContainerDummyIfLatestSpeaker(peer.id); }); - - peer.pc.on('PeerConnectionTrace', function (event) { - console.log('trace', event); - }); }, // The nick name below the avatar is distributed through the // DataChannel of the PeerConnection and only sent once during @@ -855,15 +851,14 @@ var spreedPeerConnectionTable = []; function checkPeerMedia(peer, track, mediaType) { var defer = $.Deferred(); - peer.pc.pc.getStats(track, function(stats) { + peer.pc.getStats(track).then(function(stats) { var result = false; - Object.keys(stats).forEach(function(key) { - var value = stats[key]; - if (!result && !value || value.mediaType !== mediaType || !value.hasOwnProperty('bytesReceived')) { + stats.forEach(function(statsReport) { + if (!result && statsReport.mediaType !== mediaType || !statsReport.hasOwnProperty('bytesReceived')) { return; } - if (value.bytesReceived > 0) { + if (statsReport.bytesReceived > 0) { OCA.SpreedMe.webrtc.emit('unmute', { id: peer.id, name: mediaType diff --git a/package.json b/package.json index 35e629af0..77059e708 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,13 @@ "author": "", "license": "AGPL-3.0", "devDependencies": { + "@babel/core": "^7.3.4", + "@babel/preset-env": "^7.3.4", + "attachmediastream": "^2.1.0", + "babelify": "^10.0.0", + "browserify": "^16.2.3", "handlebars": "^4.0.12", + "hark": "^1.2.3", "jasmine": "^2.5.2", "jasmine-ajax": "^3.2.0", "jasmine-core": "^2.5.2", @@ -22,6 +28,11 @@ "karma-jasmine": "^1.0.2", "karma-jasmine-ajax": "^0.1.13", "karma-phantomjs-launcher": "^1.0.2", - "phantomjs-prebuilt": "^2.1.13" + "mockconsole": "0.0.1", + "phantomjs-prebuilt": "^2.1.13", + "util": "^0.11.1", + "webrtc-adapter": "^7.2.1", + "webrtcsupport": "^2.2.0", + "wildemitter": "^1.2.0" } } |