diff --git a/bower_components/kurento-utils/.bower.json b/bower_components/kurento-utils/.bower.json new file mode 100755 index 0000000..e0d3f06 --- /dev/null +++ b/bower_components/kurento-utils/.bower.json @@ -0,0 +1,43 @@ +{ + "name": "kurento-utils", + "description": "Kurento Utilities", + "license": "Apache-2.0", + "keywords": [ + "kurento", + "webrtc", + "rtp", + "srtp", + "audio", + "video", + "videoconference", + "broadcast", + "mediaserver", + "recording", + "stream", + "streaming", + "mcu", + "sfu", + "h264", + "vp8", + "stun", + "turn" + ], + "homepage": "https://www.kurento.org", + "repository": { + "type": "git", + "url": "github:Kurento/kurento-utils-js" + }, + "authors": [ + "Kurento (https://www.kurento.org)", + "Kurento Community (https://www.kurento.org)" + ], + "_release": "0d1627438e", + "_resolution": { + "type": "branch", + "branch": "master", + "commit": "0d1627438ec0388524dd137f135d4670b76a2797" + }, + "_source": "https://github.com/Kurento/kurento-utils-bower.git", + "_target": "master", + "_originalSource": "kurento-utils" +} \ No newline at end of file diff --git a/bower_components/kurento-utils/LICENSE b/bower_components/kurento-utils/LICENSE new file mode 100755 index 0000000..7a4a3ea --- /dev/null +++ b/bower_components/kurento-utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/bower_components/kurento-utils/README.md b/bower_components/kurento-utils/README.md new file mode 100755 index 0000000..22bc09c --- /dev/null +++ b/bower_components/kurento-utils/README.md @@ -0,0 +1,147 @@ +[![][KurentoImage]][Kurento] + +Copyright © 2013-2016 [Kurento]. Licensed under [Apache 2.0 License]. + +Kurento Utils for Bower +======================= + +The Kurento Utils project contains a set of reusable components that have been +found useful during the development of the WebRTC applications with Kurento. + +The source code of this project can be cloned from the [GitHub repository]. + +Installation instructions +------------------------- + +Be sure to have installed [Node.js] and [Bower] in your system: + +```bash +curl -sL https://deb.nodesource.com/setup_4.x | sudo bash - +sudo apt-get install -y nodejs +sudo npm install -g bower +``` + +To install the library, it's recommended to do that from the [Bower repository] : + +```bash +bower install kurento-utils +``` + +Alternatively, you can download the code using git and install manually its +dependencies: + +```bash +git clone https://github.com/Kurento/kurento-utils-bower +cd kurento-utils-bower +npm install +``` + +Kurento +======= + +What is Kurento +--------------- + +Kurento is an open source software project providing a platform suitable +for creating modular applications with advanced real-time communication +capabilities. For knowing more about Kurento, please visit the Kurento +project website: http://www.kurento.org. + +Kurento is part of [FIWARE]. For further information on the relationship of +FIWARE and Kurento check the [Kurento FIWARE Catalog Entry] + +Kurento is part of the [NUBOMEDIA] research initiative. + +Documentation +------------- + +The Kurento project provides detailed [documentation] including tutorials, +installation and development guides. A simplified version of the documentation +can be found on [readthedocs.org]. The [Open API specification] a.k.a. Kurento +Protocol is also available on [apiary.io]. + +Source +------ + +Code for other Kurento projects can be found in the [GitHub Kurento Group]. + +News and Website +---------------- + +Check the [Kurento blog] +Follow us on Twitter @[kurentoms]. + +Issue tracker +------------- + +Issues and bug reports should be posted to the [GitHub Kurento bugtracker] + +Licensing and distribution +-------------------------- + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Contribution policy +------------------- + +You can contribute to the Kurento community through bug-reports, bug-fixes, new +code or new documentation. For contributing to the Kurento community, drop a +post to the [Kurento Public Mailing List] providing full information about your +contribution and its value. In your contributions, you must comply with the +following guidelines + +* You must specify the specific contents of your contribution either through a + detailed bug description, through a pull-request or through a patch. +* You must specify the licensing restrictions of the code you contribute. +* For newly created code to be incorporated in the Kurento code-base, you must + accept Kurento to own the code copyright, so that its open source nature is + guaranteed. +* You must justify appropriately the need and value of your contribution. The + Kurento project has no obligations in relation to accepting contributions + from third parties. +* The Kurento project leaders have the right of asking for further + explanations, tests or validations of any code contributed to the community + before it being incorporated into the Kurento code-base. You must be ready to + addressing all these kind of concerns before having your code approved. + +Support +------- + +The Kurento project provides community support through the [Kurento Public +Mailing List] and through [StackOverflow] using the tags *kurento* and +*fiware-kurento*. + +Before asking for support, please read first the [Kurento Netiquette Guidelines] + +[documentation]: http://www.kurento.org/documentation +[FIWARE]: http://www.fiware.org +[GitHub Kurento bugtracker]: https://github.com/Kurento/bugtracker/issues +[GitHub Kurento Group]: https://github.com/kurento +[kurentoms]: http://twitter.com/kurentoms +[Kurento]: http://kurento.org +[Kurento Blog]: http://www.kurento.org/blog +[Kurento FIWARE Catalog Entry]: http://catalogue.fiware.org/enablers/stream-oriented-kurento +[Kurento Netiquette Guidelines]: http://www.kurento.org/blog/kurento-netiquette-guidelines +[Kurento Public Mailing list]: https://groups.google.com/forum/#!forum/kurento +[KurentoImage]: https://secure.gravatar.com/avatar/21a2a12c56b2a91c8918d5779f1778bf?s=120 +[Apache 2.0 License]: http://www.apache.org/licenses/LICENSE-2.0 +[NUBOMEDIA]: http://www.nubomedia.eu +[StackOverflow]: http://stackoverflow.com/search?q=kurento +[GitHub repository]: https://github.com/kurento/kurento-utils-js +[grunt]: http://gruntjs.com/ +[Node.js]: http://nodejs.org/ +[NPM repository]: https://www.npmjs.org/package/kurento-utils +[Read-the-docs]: http://read-the-docs.readthedocs.org/ +[readthedocs.org]: http://kurento.readthedocs.org/ +[Open API specification]: http://kurento.github.io/doc-kurento/ +[apiary.io]: http://docs.streamoriented.apiary.io/ diff --git a/bower_components/kurento-utils/bower.json b/bower_components/kurento-utils/bower.json new file mode 100755 index 0000000..f2ff634 --- /dev/null +++ b/bower_components/kurento-utils/bower.json @@ -0,0 +1,34 @@ +{ + "name": "kurento-utils", + "description": "Kurento Utilities", + "license": "Apache-2.0", + "keywords": [ + "kurento", + "webrtc", + "rtp", + "srtp", + "audio", + "video", + "videoconference", + "broadcast", + "mediaserver", + "recording", + "stream", + "streaming", + "mcu", + "sfu", + "h264", + "vp8", + "stun", + "turn" + ], + "homepage": "https://www.kurento.org", + "repository": { + "type": "git", + "url": "github:Kurento/kurento-utils-js" + }, + "authors": [ + "Kurento (https://www.kurento.org)", + "Kurento Community (https://www.kurento.org)" + ] +} \ No newline at end of file diff --git a/bower_components/kurento-utils/js/kurento-utils.js b/bower_components/kurento-utils/js/kurento-utils.js new file mode 100755 index 0000000..2193d61 --- /dev/null +++ b/bower_components/kurento-utils/js/kurento-utils.js @@ -0,0 +1,4362 @@ +(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.kurentoUtils = f()}})(function(){var define,module,exports;return (function(){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 0) { + return sdp.slice(0, n); + } else { + return sdp; + } +} +function getSimulcastInfo(videoStream) { + var videoTracks = videoStream.getVideoTracks(); + if (!videoTracks.length) { + logger.warn('No video tracks available in the video stream'); + return ''; + } + var lines = [ + 'a=x-google-flag:conference', + 'a=ssrc-group:SIM 1 2 3', + 'a=ssrc:1 cname:localVideo', + 'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id, + 'a=ssrc:1 mslabel:' + videoStream.id, + 'a=ssrc:1 label:' + videoTracks[0].id, + 'a=ssrc:2 cname:localVideo', + 'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id, + 'a=ssrc:2 mslabel:' + videoStream.id, + 'a=ssrc:2 label:' + videoTracks[0].id, + 'a=ssrc:3 cname:localVideo', + 'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id, + 'a=ssrc:3 mslabel:' + videoStream.id, + 'a=ssrc:3 label:' + videoTracks[0].id + ]; + lines.push(''); + return lines.join('\n'); +} +function WebRtcPeer(mode, options, callback) { + if (!(this instanceof WebRtcPeer)) { + return new WebRtcPeer(mode, options, callback); + } + WebRtcPeer.super_.call(this); + if (options instanceof Function) { + callback = options; + options = undefined; + } + options = options || {}; + callback = (callback || noop).bind(this); + var self = this; + var localVideo = options.localVideo; + var remoteVideo = options.remoteVideo; + var videoStream = options.videoStream; + var audioStream = options.audioStream; + var mediaConstraints = options.mediaConstraints; + var connectionConstraints = options.connectionConstraints; + var pc = options.peerConnection; + var sendSource = options.sendSource || 'webcam'; + var dataChannelConfig = options.dataChannelConfig; + var useDataChannels = options.dataChannels || false; + var dataChannel; + var guid = uuid.v4(); + var configuration = recursive({ iceServers: freeice() }, options.configuration); + var onicecandidate = options.onicecandidate; + if (onicecandidate) + this.on('icecandidate', onicecandidate); + var oncandidategatheringdone = options.oncandidategatheringdone; + if (oncandidategatheringdone) { + this.on('candidategatheringdone', oncandidategatheringdone); + } + var simulcast = options.simulcast; + var multistream = options.multistream; + var interop = new sdpTranslator.Interop(); + var candidatesQueueOut = []; + var candidategatheringdone = false; + Object.defineProperties(this, { + 'peerConnection': { + get: function () { + return pc; + } + }, + 'id': { + value: options.id || guid, + writable: false + }, + 'remoteVideo': { + get: function () { + return remoteVideo; + } + }, + 'localVideo': { + get: function () { + return localVideo; + } + }, + 'dataChannel': { + get: function () { + return dataChannel; + } + }, + 'currentFrame': { + get: function () { + if (!remoteVideo) + return; + if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA) + throw new Error('No video stream data available'); + var canvas = document.createElement('canvas'); + canvas.width = remoteVideo.videoWidth; + canvas.height = remoteVideo.videoHeight; + canvas.getContext('2d').drawImage(remoteVideo, 0, 0); + return canvas; + } + } + }); + if (!pc) { + pc = new RTCPeerConnection(configuration); + if (useDataChannels && !dataChannel) { + var dcId = 'WebRtcPeer-' + self.id; + var dcOptions = undefined; + if (dataChannelConfig) { + dcId = dataChannelConfig.id || dcId; + dcOptions = dataChannelConfig.options; + } + dataChannel = pc.createDataChannel(dcId, dcOptions); + if (dataChannelConfig) { + dataChannel.onopen = dataChannelConfig.onopen; + dataChannel.onclose = dataChannelConfig.onclose; + dataChannel.onmessage = dataChannelConfig.onmessage; + dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow; + dataChannel.onerror = dataChannelConfig.onerror || noop; + } + } + } + pc.addEventListener('icecandidate', function (event) { + var candidate = event.candidate; + if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) { + if (candidate) { + var cand; + if (multistream && usePlanB) { + cand = interop.candidateToUnifiedPlan(candidate); + } else { + cand = candidate; + } + self.emit('icecandidate', cand); + candidategatheringdone = false; + } else if (!candidategatheringdone) { + self.emit('candidategatheringdone'); + candidategatheringdone = true; + } + } else if (!candidategatheringdone) { + candidatesQueueOut.push(candidate); + if (!candidate) + candidategatheringdone = true; + } + }); + pc.onaddstream = options.onaddstream; + pc.onnegotiationneeded = options.onnegotiationneeded; + this.on('newListener', function (event, listener) { + if (event === 'icecandidate' || event === 'candidategatheringdone') { + while (candidatesQueueOut.length) { + var candidate = candidatesQueueOut.shift(); + if (!candidate === (event === 'candidategatheringdone')) { + listener(candidate); + } + } + } + }); + var addIceCandidate = bufferizeCandidates(pc); + this.addIceCandidate = function (iceCandidate, callback) { + var candidate; + if (multistream && usePlanB) { + candidate = interop.candidateToPlanB(iceCandidate); + } else { + candidate = new RTCIceCandidate(iceCandidate); + } + logger.debug('Remote ICE candidate received', iceCandidate); + callback = (callback || noop).bind(this); + addIceCandidate(candidate, callback); + }; + this.generateOffer = function (callback) { + callback = callback.bind(this); + var offerAudio = true; + var offerVideo = true; + if (mediaConstraints) { + offerAudio = typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true; + offerVideo = typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true; + } + var browserDependantConstraints = { + offerToReceiveAudio: mode !== 'sendonly' && offerAudio, + offerToReceiveVideo: mode !== 'sendonly' && offerVideo + }; + var constraints = browserDependantConstraints; + logger.debug('constraints: ' + JSON.stringify(constraints)); + pc.createOffer(constraints).then(function (offer) { + logger.debug('Created SDP offer'); + offer = mangleSdpToAddSimulcast(offer); + return pc.setLocalDescription(offer); + }).then(function () { + var localDescription = pc.localDescription; + logger.debug('Local description set', localDescription.sdp); + if (multistream && usePlanB) { + localDescription = interop.toUnifiedPlan(localDescription); + logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription)); + } + callback(null, localDescription.sdp, self.processAnswer.bind(self)); + }).catch(callback); + }; + this.getLocalSessionDescriptor = function () { + return pc.localDescription; + }; + this.getRemoteSessionDescriptor = function () { + return pc.remoteDescription; + }; + function setRemoteVideo() { + if (remoteVideo) { + remoteVideo.pause(); + var stream = pc.getRemoteStreams()[0]; + remoteVideo.srcObject = stream; + logger.debug('Remote stream:', stream); + remoteVideo.load(); + } + } + this.showLocalVideo = function () { + localVideo.srcObject = videoStream; + localVideo.muted = true; + }; + this.send = function (data) { + if (dataChannel && dataChannel.readyState === 'open') { + dataChannel.send(data); + } else { + logger.warn('Trying to send data over a non-existing or closed data channel'); + } + }; + this.processAnswer = function (sdpAnswer, callback) { + callback = (callback || noop).bind(this); + var answer = new RTCSessionDescription({ + type: 'answer', + sdp: sdpAnswer + }); + if (multistream && usePlanB) { + var planBAnswer = interop.toPlanB(answer); + logger.debug('asnwer::planB', dumpSDP(planBAnswer)); + answer = planBAnswer; + } + logger.debug('SDP answer received, setting remote description'); + if (pc.signalingState === 'closed') { + return callback('PeerConnection is closed'); + } + pc.setRemoteDescription(answer, function () { + setRemoteVideo(); + callback(); + }, callback); + }; + this.processOffer = function (sdpOffer, callback) { + callback = callback.bind(this); + var offer = new RTCSessionDescription({ + type: 'offer', + sdp: sdpOffer + }); + if (multistream && usePlanB) { + var planBOffer = interop.toPlanB(offer); + logger.debug('offer::planB', dumpSDP(planBOffer)); + offer = planBOffer; + } + logger.debug('SDP offer received, setting remote description'); + if (pc.signalingState === 'closed') { + return callback('PeerConnection is closed'); + } + pc.setRemoteDescription(offer).then(function () { + return setRemoteVideo(); + }).then(function () { + return pc.createAnswer(); + }).then(function (answer) { + answer = mangleSdpToAddSimulcast(answer); + logger.debug('Created SDP answer'); + return pc.setLocalDescription(answer); + }).then(function () { + var localDescription = pc.localDescription; + if (multistream && usePlanB) { + localDescription = interop.toUnifiedPlan(localDescription); + logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription)); + } + logger.debug('Local description set', localDescription.sdp); + callback(null, localDescription.sdp); + }).catch(callback); + }; + function mangleSdpToAddSimulcast(answer) { + if (simulcast) { + if (browser.name === 'Chrome' || browser.name === 'Chromium') { + logger.debug('Adding multicast info'); + answer = new RTCSessionDescription({ + 'type': answer.type, + 'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream) + }); + } else { + logger.warn('Simulcast is only available in Chrome browser.'); + } + } + return answer; + } + function start() { + if (pc.signalingState === 'closed') { + callback('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'); + } + if (videoStream && localVideo) { + self.showLocalVideo(); + } + if (videoStream) { + pc.addStream(videoStream); + } + if (audioStream) { + pc.addStream(audioStream); + } + var browser = parser.getBrowser(); + if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) { + mode = 'sendrecv'; + } + callback(); + } + if (mode !== 'recvonly' && !videoStream && !audioStream) { + function getMedia(constraints) { + if (constraints === undefined) { + constraints = MEDIA_CONSTRAINTS; + } + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + videoStream = stream; + start(); + }).catch(callback); + } + if (sendSource === 'webcam') { + getMedia(mediaConstraints); + } else { + getScreenConstraints(sendSource, function (error, constraints_) { + if (error) + return callback(error); + constraints = [mediaConstraints]; + constraints.unshift(constraints_); + getMedia(recursive.apply(undefined, constraints)); + }, guid); + } + } else { + setTimeout(start, 0); + } + this.on('_dispose', function () { + if (localVideo) { + localVideo.pause(); + localVideo.srcObject = null; + localVideo.load(); + localVideo.muted = false; + } + if (remoteVideo) { + remoteVideo.pause(); + remoteVideo.srcObject = null; + remoteVideo.load(); + } + self.removeAllListeners(); + if (window.cancelChooseDesktopMedia !== undefined) { + window.cancelChooseDesktopMedia(guid); + } + }); +} +inherits(WebRtcPeer, EventEmitter); +function createEnableDescriptor(type) { + var method = 'get' + type + 'Tracks'; + return { + enumerable: true, + get: function () { + if (!this.peerConnection) + return; + var streams = this.peerConnection.getLocalStreams(); + if (!streams.length) + return; + for (var i = 0, stream; stream = streams[i]; i++) { + var tracks = stream[method](); + for (var j = 0, track; track = tracks[j]; j++) + if (!track.enabled) + return false; + } + return true; + }, + set: function (value) { + function trackSetEnable(track) { + track.enabled = value; + } + this.peerConnection.getLocalStreams().forEach(function (stream) { + stream[method]().forEach(trackSetEnable); + }); + } + }; +} +Object.defineProperties(WebRtcPeer.prototype, { + 'enabled': { + enumerable: true, + get: function () { + return this.audioEnabled && this.videoEnabled; + }, + set: function (value) { + this.audioEnabled = this.videoEnabled = value; + } + }, + 'audioEnabled': createEnableDescriptor('Audio'), + 'videoEnabled': createEnableDescriptor('Video') +}); +WebRtcPeer.prototype.getLocalStream = function (index) { + if (this.peerConnection) { + return this.peerConnection.getLocalStreams()[index || 0]; + } +}; +WebRtcPeer.prototype.getRemoteStream = function (index) { + if (this.peerConnection) { + return this.peerConnection.getRemoteStreams()[index || 0]; + } +}; +WebRtcPeer.prototype.dispose = function () { + logger.debug('Disposing WebRtcPeer'); + var pc = this.peerConnection; + var dc = this.dataChannel; + try { + if (dc) { + if (dc.signalingState === 'closed') + return; + dc.close(); + } + if (pc) { + if (pc.signalingState === 'closed') + return; + pc.getLocalStreams().forEach(streamStop); + pc.close(); + } + } catch (err) { + logger.warn('Exception disposing webrtc peer ' + err); + } + this.emit('_dispose'); +}; +function WebRtcPeerRecvonly(options, callback) { + if (!(this instanceof WebRtcPeerRecvonly)) { + return new WebRtcPeerRecvonly(options, callback); + } + WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback); +} +inherits(WebRtcPeerRecvonly, WebRtcPeer); +function WebRtcPeerSendonly(options, callback) { + if (!(this instanceof WebRtcPeerSendonly)) { + return new WebRtcPeerSendonly(options, callback); + } + WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback); +} +inherits(WebRtcPeerSendonly, WebRtcPeer); +function WebRtcPeerSendrecv(options, callback) { + if (!(this instanceof WebRtcPeerSendrecv)) { + return new WebRtcPeerSendrecv(options, callback); + } + WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback); +} +inherits(WebRtcPeerSendrecv, WebRtcPeer); +function harkUtils(stream, options) { + return hark(stream, options); +} +exports.bufferizeCandidates = bufferizeCandidates; +exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly; +exports.WebRtcPeerSendonly = WebRtcPeerSendonly; +exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv; +exports.hark = harkUtils; +},{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid":23}],2:[function(require,module,exports){ +if (window.addEventListener) + module.exports = require('./index'); +},{"./index":3}],3:[function(require,module,exports){ +var WebRtcPeer = require('./WebRtcPeer'); +exports.WebRtcPeer = WebRtcPeer; +},{"./WebRtcPeer":1}],4:[function(require,module,exports){ +// 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. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); + err.context = er; + throw err; + } + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.prototype.listenerCount = function(type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) + return 1; + else if (evlistener) + return evlistener.length; + } + return 0; +}; + +EventEmitter.listenerCount = function(emitter, type) { + return emitter.listenerCount(type); +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],5:[function(require,module,exports){ +/* jshint node: true */ +'use strict'; + +var normalice = require('normalice'); + +/** + # freeice + + The `freeice` module is a simple way of getting random STUN or TURN server + for your WebRTC application. The list of servers (just STUN at this stage) + were sourced from this [gist](https://gist.github.com/zziuni/3741933). + + ## Example Use + + The following demonstrates how you can use `freeice` with + [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect): + + <<< examples/quickconnect.js + + As the `freeice` module generates ice servers in a list compliant with the + WebRTC spec you will be able to use it with raw `RTCPeerConnection` + constructors and other WebRTC libraries. + + ## Hey, don't use my STUN/TURN server! + + If for some reason your free STUN or TURN server ends up in the + list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or + [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json)) + that is used in this module, you can feel + free to open an issue on this repository and those servers will be removed + within 24 hours (or sooner). This is the quickest and probably the most + polite way to have something removed (and provides us some visibility + if someone opens a pull request requesting that a server is added). + + ## Please add my server! + + If you have a server that you wish to add to the list, that's awesome! I'm + sure I speak on behalf of a whole pile of WebRTC developers who say thanks. + To get it into the list, feel free to either open a pull request or if you + find that process a bit daunting then just create an issue requesting + the addition of the server (make sure you provide all the details, and if + you have a Terms of Service then including that in the PR/issue would be + awesome). + + ## I know of a free server, can I add it? + + Sure, if you do your homework and make sure it is ok to use (I'm currently + in the process of reviewing the terms of those STUN servers included from + the original list). If it's ok to go, then please see the previous entry + for how to add it. + + ## Current List of Servers + + * current as at the time of last `README.md` file generation + + ### STUN + + <<< stun.json + + ### TURN + + <<< turn.json + +**/ + +var freeice = module.exports = function(opts) { + // if a list of servers has been provided, then use it instead of defaults + var servers = { + stun: (opts || {}).stun || require('./stun.json'), + turn: (opts || {}).turn || require('./turn.json') + }; + + var stunCount = (opts || {}).stunCount || 2; + var turnCount = (opts || {}).turnCount || 0; + var selected; + + function getServers(type, count) { + var out = []; + var input = [].concat(servers[type]); + var idx; + + while (input.length && out.length < count) { + idx = (Math.random() * input.length) | 0; + out = out.concat(input.splice(idx, 1)); + } + + return out.map(function(url) { + //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up + if ((typeof url !== 'string') && (! (url instanceof String))) { + return url; + } else { + return normalice(type + ':' + url); + } + }); + } + + // add stun servers + selected = [].concat(getServers('stun', stunCount)); + + if (turnCount) { + selected = selected.concat(getServers('turn', turnCount)); + } + + return selected; +}; + +},{"./stun.json":6,"./turn.json":7,"normalice":12}],6:[function(require,module,exports){ +module.exports=[ + "stun.l.google.com:19302", + "stun1.l.google.com:19302", + "stun2.l.google.com:19302", + "stun3.l.google.com:19302", + "stun4.l.google.com:19302", + "stun.ekiga.net", + "stun.ideasip.com", + "stun.schlund.de", + "stun.stunprotocol.org:3478", + "stun.voiparound.com", + "stun.voipbuster.com", + "stun.voipstunt.com", + "stun.voxgratia.org", + "stun.services.mozilla.com" +] + +},{}],7:[function(require,module,exports){ +module.exports=[] + +},{}],8:[function(require,module,exports){ +var WildEmitter = require('wildemitter'); + +function getMaxVolume (analyser, fftBins) { + var maxVolume = -Infinity; + analyser.getFloatFrequencyData(fftBins); + + for(var i=4, ii=fftBins.length; i < ii; i++) { + if (fftBins[i] > maxVolume && fftBins[i] < 0) { + maxVolume = fftBins[i]; + } + }; + + return maxVolume; +} + + +var audioContextType = window.AudioContext || window.webkitAudioContext; +// use a single audio context due to hardware limits +var audioContext = null; +module.exports = function(stream, options) { + var harker = new WildEmitter(); + + + // make it not break in non-supported browsers + if (!audioContextType) return harker; + + //Config + var options = options || {}, + smoothing = (options.smoothing || 0.1), + interval = (options.interval || 50), + threshold = options.threshold, + play = options.play, + history = options.history || 10, + running = true; + + //Setup Audio Context + if (!audioContext) { + audioContext = new audioContextType(); + } + var sourceNode, fftBins, analyser; + + analyser = audioContext.createAnalyser(); + analyser.fftSize = 512; + analyser.smoothingTimeConstant = smoothing; + fftBins = new Float32Array(analyser.fftSize); + + if (stream.jquery) stream = stream[0]; + if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) { + //Audio Tag + sourceNode = audioContext.createMediaElementSource(stream); + if (typeof play === 'undefined') play = true; + threshold = threshold || -50; + } else { + //WebRTC Stream + sourceNode = audioContext.createMediaStreamSource(stream); + threshold = threshold || -50; + } + + sourceNode.connect(analyser); + if (play) analyser.connect(audioContext.destination); + + harker.speaking = false; + + harker.setThreshold = function(t) { + threshold = t; + }; + + harker.setInterval = function(i) { + interval = i; + }; + + harker.stop = function() { + running = false; + harker.emit('volume_change', -100, threshold); + if (harker.speaking) { + harker.speaking = false; + harker.emit('stopped_speaking'); + } + }; + harker.speakingHistory = []; + for (var i = 0; i < history; i++) { + harker.speakingHistory.push(0); + } + + // Poll the analyser node to determine if speaking + // and emit events if changed + var looper = function() { + setTimeout(function() { + + //check if stop has been called + if(!running) { + return; + } + + var currentVolume = getMaxVolume(analyser, fftBins); + + harker.emit('volume_change', currentVolume, threshold); + + var history = 0; + if (currentVolume > threshold && !harker.speaking) { + // trigger quickly, short history + for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) { + history += harker.speakingHistory[i]; + } + if (history >= 2) { + harker.speaking = true; + harker.emit('speaking'); + } + } else if (currentVolume < threshold && harker.speaking) { + for (var i = 0; i < harker.speakingHistory.length; i++) { + history += harker.speakingHistory[i]; + } + if (history == 0) { + harker.speaking = false; + harker.emit('stopped_speaking'); + } + } + harker.speakingHistory.shift(); + harker.speakingHistory.push(0 + (currentVolume > threshold)); + + looper(); + }, interval); + }; + looper(); + + + return harker; +} + +},{"wildemitter":24}],9:[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 + } +} + +},{}],10:[function(require,module,exports){ +// Does nothing at all. + +},{}],11:[function(require,module,exports){ +/*! + * @name JavaScript/NodeJS Merge v1.2.0 + * @author yeikos + * @repository https://github.com/yeikos/js.merge + + * Copyright 2014 yeikos - MIT license + * https://raw.github.com/yeikos/js.merge/master/LICENSE + */ + +;(function(isNode) { + + /** + * Merge one or more objects + * @param bool? clone + * @param mixed,... arguments + * @return object + */ + + var Public = function(clone) { + + return merge(clone === true, false, arguments); + + }, publicName = 'merge'; + + /** + * Merge two or more objects recursively + * @param bool? clone + * @param mixed,... arguments + * @return object + */ + + Public.recursive = function(clone) { + + return merge(clone === true, true, arguments); + + }; + + /** + * Clone the input removing any reference + * @param mixed input + * @return mixed + */ + + Public.clone = function(input) { + + var output = input, + type = typeOf(input), + index, size; + + if (type === 'array') { + + output = []; + size = input.length; + + for (index=0;index 1) { + url = parts[1]; + parts = parts[0].split(':'); + + // add the output credential and username + output.username = parts[0]; + output.credential = (input || {}).credential || parts[1] || ''; + } + + output.url = protocol + url; + output.urls = [ output.url ]; + + return output; +}; + +},{}],13:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ //o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: "%s %s %d %s IP%d %s" + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + //k: [{}], // outdated thing ignored + t: [{ //t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: "%d %d" + }], + c: [{ //c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: "IN IP%d %s" + }], + b: [{ //b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: "%s:%s" + }], + m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: "%s %d %s %s" + }], + a: [ + { //a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) ? + "rtpmap:%d %s/%s/%s": + o.rate ? + "rtpmap:%d %s/%s": + "rtpmap:%d %s"; + } + }, + { + //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + //a=fmtp:111 minptime=10; useinbandfec=1 + push: 'fmtp', + reg: /^fmtp:(\d*) ([\S| ]*)/, + names: ['payload', 'config'], + format: "fmtp:%d %s" + }, + { //a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: "control:%s" + }, + { //a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) ? + "rtcp:%d %s IP%d %s": + "rtcp:%d"; + } + }, + { //a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: "rtcp-fb:%d trr-int %d" + }, + { //a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) ? + "rtcp-fb:%s %s %s": + "rtcp-fb:%s %s"; + } + }, + { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + //a=extmap:1/recvonly URI-gps-string + push: 'ext', + reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, + names: ['value', 'uri', 'config'], // value may include "/direction" suffix + format: function (o) { + return (o.config != null) ? + "extmap:%s %s %s": + "extmap:%s %s"; + } + }, + { + //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) ? + "crypto:%d %s %s %s": + "crypto:%d %s %s"; + } + }, + { //a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: "setup:%s" + }, + { //a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: "mid:%s" + }, + { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: "msid:%s" + }, + { //a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*)/, + format: "ptime:%d" + }, + { //a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*)/, + format: "maxptime:%d" + }, + { //a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { //a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { //a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: "ice-ufrag:%s" + }, + { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: "ice-pwd:%s" + }, + { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: "fingerprint:%s %s" + }, + { + //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 + //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 + //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 + //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'], + format: function (o) { + var str = "candidate:%s %d %s %d %s %d typ %s"; + + str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; + + // NB: candidate has three optional chunks, so %void middles one if it's missing + str += (o.tcptype != null) ? " tcptype %s" : "%v"; + + if (o.generation != null) { + str += " generation %d"; + } + return str; + } + }, + { //a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: "remote-candidates:%s" + }, + { //a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: "ice-options:%s" + }, + { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: "ssrcs", + reg: /^ssrc:(\d*) ([\w_]*):(.*)/, + names: ['id', 'attribute', 'value'], + format: "ssrc:%d %s:%s" + }, + { //a=ssrc-group:FEC 1 2 + push: "ssrcGroups", + reg: /^ssrc-group:(\w*) (.*)/, + names: ['semantics', 'ssrcs'], + format: "ssrc-group:%s %s" + }, + { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: "msidSemantic", + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: "msid-semantic: %s %s" // space after ":" is not accidental + }, + { //a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: "group:%s %s" + }, + { //a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { //a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { // any a= that we don't understand is kepts verbatim on media.invalid + push: 'invalid', + names: ["value"] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = "%s"; + } + }); +}); + +},{}],14:[function(require,module,exports){ +var parser = require('./parser'); +var writer = require('./writer'); + +exports.write = writer; +exports.parse = parser.parse; +exports.parseFmtpConfig = parser.parseFmtpConfig; +exports.parsePayloads = parser.parsePayloads; +exports.parseRemoteCandidates = parser.parseRemoteCandidates; + +},{"./parser":15,"./writer":16}],15:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var fmtpReducer = function (acc, expr) { + var s = expr.split('='); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } + return acc; +}; + +exports.parseFmtpConfig = function (str) { + return str.split(/\;\s?/).reduce(fmtpReducer, {}); +}; + +exports.parsePayloads = function (str) { + return str.split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +},{"./grammar":13}],16:[function(require,module,exports){ +var grammar = require('./grammar'); + +// customized util.format - discards excess arguments and can void middle ones +var formatRegExp = /%[sdv%]/g; +var format = function (formatStr) { + var i = 1; + var args = arguments; + var len = args.length; + return formatStr.replace(formatRegExp, function (x) { + if (i >= len) { + return x; // missing argument + } + var arg = args[i]; + i += 1; + switch (x) { + case '%%': + return '%'; + case '%s': + return String(arg); + case '%d': + return Number(arg); + case '%v': + return ''; + } + }); + // NB: we discard excess arguments - they are typically undefined from makeLine +}; + +var makeLine = function (type, obj, location) { + var str = obj.format instanceof Function ? + (obj.format(obj.push ? location : location[obj.name])) : + obj.format; + + var args = [type + '=' + str]; + if (obj.names) { + for (var i = 0; i < obj.names.length; i += 1) { + var n = obj.names[i]; + if (obj.name) { + args.push(location[obj.name][n]); + } + else { // for mLine and push attributes + args.push(location[obj.names[i]]); + } + } + } + else { + args.push(location[obj.name]); + } + return format.apply(null, args); +}; + +// RFC specified order +// TODO: extend this with all the rest +var defaultOuterOrder = [ + 'v', 'o', 's', 'i', + 'u', 'e', 'p', 'c', + 'b', 't', 'r', 'z', 'a' +]; +var defaultInnerOrder = ['i', 'c', 'b', 'a']; + + +module.exports = function (session, opts) { + opts = opts || {}; + // ensure certain properties exist + if (session.version == null) { + session.version = 0; // "v=0" must be there (only defined version atm) + } + if (session.name == null) { + session.name = " "; // "s= " must be there if no meaningful name set + } + session.media.forEach(function (mLine) { + if (mLine.payloads == null) { + mLine.payloads = ""; + } + }); + + var outerOrder = opts.outerOrder || defaultOuterOrder; + var innerOrder = opts.innerOrder || defaultInnerOrder; + var sdp = []; + + // loop through outerOrder for matching properties on session + outerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in session && session[obj.name] != null) { + sdp.push(makeLine(type, obj, session)); + } + else if (obj.push in session && session[obj.push] != null) { + session[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + + // then for each media line, follow the innerOrder + session.media.forEach(function (mLine) { + sdp.push(makeLine('m', grammar.m[0], mLine)); + + innerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in mLine && mLine[obj.name] != null) { + sdp.push(makeLine(type, obj, mLine)); + } + else if (obj.push in mLine && mLine[obj.push] != null) { + mLine[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + }); + + return sdp.join('\r\n') + '\r\n'; +}; + +},{"./grammar":13}],17:[function(require,module,exports){ +/* Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = function arrayEquals(array) { + // if the other array is a falsy value, return + if (!array) + return false; + + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + + for (var i = 0, l = this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!arrayEquals.apply(this[i], [array[i]])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: + // {x:20} != {x:20} + return false; + } + } + return true; +}; + + +},{}],18:[function(require,module,exports){ +/* Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +exports.Interop = require('./interop'); + +},{"./interop":19}],19:[function(require,module,exports){ +/* Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* global RTCSessionDescription */ +/* global RTCIceCandidate */ +/* jshint -W097 */ +"use strict"; + +var transform = require('./transform'); +var arrayEquals = require('./array-equals'); + +function Interop() { + + /** + * This map holds the most recent Unified Plan offer/answer SDP that was + * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and + * the SDP string as values. + * + * @type {{}} + */ + this.cache = { + mlB2UMap : {}, + mlU2BMap : {} + }; +} + +module.exports = Interop; + +/** + * Changes the candidate args to match with the related Unified Plan + */ +Interop.prototype.candidateToUnifiedPlan = function(candidate) { + var cand = new RTCIceCandidate(candidate); + + cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex]; + /* TODO: change sdpMid to (audio|video)-SSRC */ + + return cand; +}; + +/** + * Changes the candidate args to match with the related Plan B + */ +Interop.prototype.candidateToPlanB = function(candidate) { + var cand = new RTCIceCandidate(candidate); + + if (cand.sdpMid.indexOf('audio') === 0) { + cand.sdpMid = 'audio'; + } else if (cand.sdpMid.indexOf('video') === 0) { + cand.sdpMid = 'video'; + } else { + throw new Error('candidate with ' + cand.sdpMid + ' not allowed'); + } + + cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex]; + + return cand; +}; + +/** + * Returns the index of the first m-line with the given media type and with a + * direction which allows sending, in the last Unified Plan description with + * type "answer" converted to Plan B. Returns {null} if there is no saved + * answer, or if none of its m-lines with the given type allow sending. + * @param type the media type ("audio" or "video"). + * @returns {*} + */ +Interop.prototype.getFirstSendingIndexFromAnswer = function(type) { + if (!this.cache.answer) { + return null; + } + + var session = transform.parse(this.cache.answer); + if (session && session.media && Array.isArray(session.media)){ + for (var i = 0; i < session.media.length; i++) { + if (session.media[i].type == type && + (!session.media[i].direction /* default to sendrecv */ || + session.media[i].direction === 'sendrecv' || + session.media[i].direction === 'sendonly')){ + return i; + } + } + } + + return null; +}; + +/** + * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A + * PeerConnection wrapper transforms the SDP to Plan B before passing it to the + * application. + * + * @param desc + * @returns {*} + */ +Interop.prototype.toPlanB = function(desc) { + var self = this; + //#region Preliminary input validation. + + if (typeof desc !== 'object' || desc === null || + typeof desc.sdp !== 'string') { + console.warn('An empty description was passed as an argument.'); + return desc; + } + + // Objectify the SDP for easier manipulation. + var session = transform.parse(desc.sdp); + + // If the SDP contains no media, there's nothing to transform. + if (typeof session.media === 'undefined' || + !Array.isArray(session.media) || session.media.length === 0) { + console.warn('The description has no media.'); + return desc; + } + + // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B + // SDP has a video, an audio and a data "channel" at most. + if (session.media.length <= 3 && session.media.every(function(m) { + return ['video', 'audio', 'data'].indexOf(m.mid) !== -1; + })) { + console.warn('This description does not look like Unified Plan.'); + return desc; + } + + //#endregion + + // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443 + var sdp = desc.sdp; + var rewrite = false; + for (var i = 0; i < session.media.length; i++) { + var uLine = session.media[i]; + uLine.rtp.forEach(function(rtp) { + if (rtp.codec === 'NULL') + { + rewrite = true; + var offer = transform.parse(self.cache.offer); + rtp.codec = offer.media[i].rtp[0].codec; + } + }); + } + if (rewrite) { + sdp = transform.write(session); + } + + // Unified Plan SDP is our "precious". Cache it for later use in the Plan B + // -> Unified Plan transformation. + this.cache[desc.type] = sdp; + + //#region Convert from Unified Plan to Plan B. + + // We rebuild the session.media array. + var media = session.media; + session.media = []; + + // Associative array that maps channel types to channel objects for fast + // access to channel objects by their type, e.g. type2bl['audio']->channel + // obj. + var type2bl = {}; + + // Used to build the group:BUNDLE value after the channels construction + // loop. + var types = []; + + media.forEach(function(uLine) { + // rtcp-mux is required in the Plan B SDP. + if ((typeof uLine.rtcpMux !== 'string' || + uLine.rtcpMux !== 'rtcp-mux') && + uLine.direction !== 'inactive') { + throw new Error('Cannot convert to Plan B because m-lines ' + + 'without the rtcp-mux attribute were found.'); + } + + // If we don't have a channel for this uLine.type OR the selected is + // inactive, then select this uLine as the channel basis. + if (typeof type2bl[uLine.type] === 'undefined' || + type2bl[uLine.type].direction === 'inactive') { + type2bl[uLine.type] = uLine; + } + + if (uLine.protocol != type2bl[uLine.type].protocol) { + throw new Error('Cannot convert to Plan B because m-lines ' + + 'have different protocols and this library does not have ' + + 'support for that'); + } + + if (uLine.payloads != type2bl[uLine.type].payloads) { + throw new Error('Cannot convert to Plan B because m-lines ' + + 'have different payloads and this library does not have ' + + 'support for that'); + } + + }); + + // Implode the Unified Plan m-lines/tracks into Plan B channels. + media.forEach(function(uLine) { + if (uLine.type === 'application') { + session.media.push(uLine); + types.push(uLine.mid); + return; + } + + // Add sources to the channel and handle a=msid. + if (typeof uLine.sources === 'object') { + Object.keys(uLine.sources).forEach(function(ssrc) { + if (typeof type2bl[uLine.type].sources !== 'object') + type2bl[uLine.type].sources = {}; + + // Assign the sources to the channel. + type2bl[uLine.type].sources[ssrc] = + uLine.sources[ssrc]; + + if (typeof uLine.msid !== 'undefined') { + // In Plan B the msid is an SSRC attribute. Also, we don't + // care about the obsolete label and mslabel attributes. + // + // Note that it is not guaranteed that the uLine will + // have an msid. recvonly channels in particular don't have + // one. + type2bl[uLine.type].sources[ssrc].msid = + uLine.msid; + } + // NOTE ssrcs in ssrc groups will share msids, as + // draft-uberti-rtcweb-plan-00 mandates. + }); + } + + // Add ssrc groups to the channel. + if (typeof uLine.ssrcGroups !== 'undefined' && + Array.isArray(uLine.ssrcGroups)) { + + // Create the ssrcGroups array, if it's not defined. + if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' || + !Array.isArray(type2bl[uLine.type].ssrcGroups)) { + type2bl[uLine.type].ssrcGroups = []; + } + + type2bl[uLine.type].ssrcGroups = + type2bl[uLine.type].ssrcGroups.concat( + uLine.ssrcGroups); + } + + if (type2bl[uLine.type] === uLine) { + // Plan B mids are in ['audio', 'video', 'data'] + uLine.mid = uLine.type; + + // Plan B doesn't support/need the bundle-only attribute. + delete uLine.bundleOnly; + + // In Plan B the msid is an SSRC attribute. + delete uLine.msid; + + if (uLine.type == media[0].type) { + types.unshift(uLine.type); + // Add the channel to the new media array. + session.media.unshift(uLine); + } else { + types.push(uLine.type); + // Add the channel to the new media array. + session.media.push(uLine); + } + } + }); + + if (typeof session.groups !== 'undefined') { + // We regenerate the BUNDLE group with the new mids. + session.groups.some(function(group) { + if (group.type === 'BUNDLE') { + group.mids = types.join(' '); + return true; + } + }); + } + + // msid semantic + session.msidSemantic = { + semantic: 'WMS', + token: '*' + }; + + var resStr = transform.write(session); + + return new RTCSessionDescription({ + type: desc.type, + sdp: resStr + }); + + //#endregion +}; + +/* follow rules defined in RFC4145 */ +function addSetupAttr(uLine) { + if (typeof uLine.setup === 'undefined') { + return; + } + + if (uLine.setup === "active") { + uLine.setup = "passive"; + } else if (uLine.setup === "passive") { + uLine.setup = "active"; + } +} + +/** + * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A + * PeerConnection wrapper transforms the SDP to Unified Plan before passing it + * to FF. + * + * @param desc + * @returns {*} + */ +Interop.prototype.toUnifiedPlan = function(desc) { + var self = this; + //#region Preliminary input validation. + + if (typeof desc !== 'object' || desc === null || + typeof desc.sdp !== 'string') { + console.warn('An empty description was passed as an argument.'); + return desc; + } + + var session = transform.parse(desc.sdp); + + // If the SDP contains no media, there's nothing to transform. + if (typeof session.media === 'undefined' || + !Array.isArray(session.media) || session.media.length === 0) { + console.warn('The description has no media.'); + return desc; + } + + // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has + // a video, an audio and a data "channel" at most. + if (session.media.length > 3 || !session.media.every(function(m) { + return ['video', 'audio', 'data'].indexOf(m.mid) !== -1; + })) { + console.warn('This description does not look like Plan B.'); + return desc; + } + + // Make sure this Plan B SDP can be converted to a Unified Plan SDP. + var mids = []; + session.media.forEach(function(m) { + mids.push(m.mid); + }); + + var hasBundle = false; + if (typeof session.groups !== 'undefined' && + Array.isArray(session.groups)) { + hasBundle = session.groups.every(function(g) { + return g.type !== 'BUNDLE' || + arrayEquals.apply(g.mids.sort(), [mids.sort()]); + }); + } + + if (!hasBundle) { + var mustBeBundle = false; + + session.media.forEach(function(m) { + if (m.direction !== 'inactive') { + mustBeBundle = true; + } + }); + + if (mustBeBundle) { + throw new Error("Cannot convert to Unified Plan because m-lines that" + + " are not bundled were found."); + } + } + + //#endregion + + + //#region Convert from Plan B to Unified Plan. + + // Unfortunately, a Plan B offer/answer doesn't have enough information to + // rebuild an equivalent Unified Plan offer/answer. + // + // For example, if this is a local answer (in Unified Plan style) that we + // convert to Plan B prior to handing it over to the application (the + // PeerConnection wrapper called us, for instance, after a successful + // createAnswer), we want to remember the m-line at which we've seen the + // (local) SSRC. That's because when the application wants to do call the + // SLD method, forcing us to do the inverse transformation (from Plan B to + // Unified Plan), we need to know to which m-line to assign the (local) + // SSRC. We also need to know all the other m-lines that the original + // answer had and include them in the transformed answer as well. + // + // Another example is if this is a remote offer that we convert to Plan B + // prior to giving it to the application, we want to remember the mid at + // which we've seen the (remote) SSRC. + // + // In the iteration that follows, we use the cached Unified Plan (if it + // exists) to assign mids to ssrcs. + + var type; + if (desc.type === 'answer') { + type = 'offer'; + } else if (desc.type === 'offer') { + type = 'answer'; + } else { + throw new Error("Type '" + desc.type + "' not supported."); + } + + var cached; + if (typeof this.cache[type] !== 'undefined') { + cached = transform.parse(this.cache[type]); + } + + var recvonlySsrcs = { + audio: {}, + video: {} + }; + + // A helper map that sends mids to m-line objects. We use it later to + // rebuild the Unified Plan style session.media array. + var mid2ul = {}; + var bIdx = 0; + var uIdx = 0; + + var sources2ul = {}; + + var candidates; + var iceUfrag; + var icePwd; + var fingerprint; + var payloads = {}; + var rtcpFb = {}; + var rtp = {}; + + session.media.forEach(function(bLine) { + if ((typeof bLine.rtcpMux !== 'string' || + bLine.rtcpMux !== 'rtcp-mux') && + bLine.direction !== 'inactive') { + throw new Error("Cannot convert to Unified Plan because m-lines " + + "without the rtcp-mux attribute were found."); + } + + if (bLine.type === 'application') { + mid2ul[bLine.mid] = bLine; + return; + } + + // With rtcp-mux and bundle all the channels should have the same ICE + // stuff. + var sources = bLine.sources; + var ssrcGroups = bLine.ssrcGroups; + var port = bLine.port; + + /* Chrome adds different candidates even using bundle, so we concat the candidates list */ + if (typeof bLine.candidates != 'undefined') { + if (typeof candidates != 'undefined') { + candidates = candidates.concat(bLine.candidates); + } else { + candidates = bLine.candidates; + } + } + + if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) { + throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" + + "\tLast iceUfrag: " + iceUfrag + "\n" + + "\tNew iceUfrag: " + bLine.iceUfrag + ); + } + + if (typeof bLine.iceUfrag != 'undefined') { + iceUfrag = bLine.iceUfrag; + } + + if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) { + throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n" + + "\tLast icePwd: " + icePwd + "\n" + + "\tNew icePwd: " + bLine.icePwd + ); + } + + if (typeof bLine.icePwd != 'undefined') { + icePwd = bLine.icePwd; + } + + if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') && + (fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) { + throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" + + "\tLast fingerprint: " + JSON.stringify(fingerprint) + "\n" + + "\tNew fingerprint: " + JSON.stringify(bLine.fingerprint) + ); + } + + if (typeof bLine.fingerprint != 'undefined') { + fingerprint = bLine.fingerprint; + } + + payloads[bLine.type] = bLine.payloads; + rtcpFb[bLine.type] = bLine.rtcpFb; + rtp[bLine.type] = bLine.rtp; + + // inverted ssrc group map + var ssrc2group = {}; + if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) { + ssrcGroups.forEach(function (ssrcGroup) { + // XXX This might brake if an SSRC is in more than one group + // for some reason. + if (typeof ssrcGroup.ssrcs !== 'undefined' && + Array.isArray(ssrcGroup.ssrcs)) { + ssrcGroup.ssrcs.forEach(function (ssrc) { + if (typeof ssrc2group[ssrc] === 'undefined') { + ssrc2group[ssrc] = []; + } + + ssrc2group[ssrc].push(ssrcGroup); + }); + } + }); + } + + // ssrc to m-line index. + var ssrc2ml = {}; + + if (typeof sources === 'object') { + + // We'll use the "bLine" object as a prototype for each new "mLine" + // that we create, but first we need to clean it up a bit. + delete bLine.sources; + delete bLine.ssrcGroups; + delete bLine.candidates; + delete bLine.iceUfrag; + delete bLine.icePwd; + delete bLine.fingerprint; + delete bLine.port; + delete bLine.mid; + + // Explode the Plan B channel sources with one m-line per source. + Object.keys(sources).forEach(function(ssrc) { + + // The (unified) m-line for this SSRC. We either create it from + // scratch or, if it's a grouped SSRC, we re-use a related + // mline. In other words, if the source is grouped with another + // source, put the two together in the same m-line. + var uLine; + + // We assume here that we are the answerer in the O/A, so any + // offers which we translate come from the remote side, while + // answers are local. So the check below is to make that we + // handle receive-only SSRCs in a special way only if they come + // from the remote side. + if (desc.type==='offer') { + // We want to detect SSRCs which are used by a remote peer + // in an m-line with direction=recvonly (i.e. they are + // being used for RTCP only). + // This information would have gotten lost if the remote + // peer used Unified Plan and their local description was + // translated to Plan B. So we use the lack of an MSID + // attribute to deduce a "receive only" SSRC. + if (!sources[ssrc].msid) { + recvonlySsrcs[bLine.type][ssrc] = sources[ssrc]; + // Receive-only SSRCs must not create new m-lines. We + // will assign them to an existing m-line later. + return; + } + } + + if (typeof ssrc2group[ssrc] !== 'undefined' && + Array.isArray(ssrc2group[ssrc])) { + ssrc2group[ssrc].some(function (ssrcGroup) { + // ssrcGroup.ssrcs *is* an Array, no need to check + // again here. + return ssrcGroup.ssrcs.some(function (related) { + if (typeof ssrc2ml[related] === 'object') { + uLine = ssrc2ml[related]; + return true; + } + }); + }); + } + + if (typeof uLine === 'object') { + // the m-line already exists. Just add the source. + uLine.sources[ssrc] = sources[ssrc]; + delete sources[ssrc].msid; + } else { + // Use the "bLine" as a prototype for the "uLine". + uLine = Object.create(bLine); + ssrc2ml[ssrc] = uLine; + + if (typeof sources[ssrc].msid !== 'undefined') { + // Assign the msid of the source to the m-line. Note + // that it is not guaranteed that the source will have + // msid. In particular "recvonly" sources don't have an + // msid. Note that "recvonly" is a term only defined + // for m-lines. + uLine.msid = sources[ssrc].msid; + delete sources[ssrc].msid; + } + + // We assign one SSRC per media line. + uLine.sources = {}; + uLine.sources[ssrc] = sources[ssrc]; + uLine.ssrcGroups = ssrc2group[ssrc]; + + // Use the cached Unified Plan SDP (if it exists) to assign + // SSRCs to mids. + if (typeof cached !== 'undefined' && + typeof cached.media !== 'undefined' && + Array.isArray(cached.media)) { + + cached.media.forEach(function (m) { + if (typeof m.sources === 'object') { + Object.keys(m.sources).forEach(function (s) { + if (s === ssrc) { + uLine.mid = m.mid; + } + }); + } + }); + } + + if (typeof uLine.mid === 'undefined') { + + // If this is an SSRC that we see for the first time + // assign it a new mid. This is typically the case when + // this method is called to transform a remote + // description for the first time or when there is a + // new SSRC in the remote description because a new + // peer has joined the conference. Local SSRCs should + // have already been added to the map in the toPlanB + // method. + // + // Because FF generates answers in Unified Plan style, + // we MUST already have a cached answer with all the + // local SSRCs mapped to some m-line/mid. + + uLine.mid = [bLine.type, '-', ssrc].join(''); + } + + // Include the candidates in the 1st media line. + uLine.candidates = candidates; + uLine.iceUfrag = iceUfrag; + uLine.icePwd = icePwd; + uLine.fingerprint = fingerprint; + uLine.port = port; + + mid2ul[uLine.mid] = uLine; + sources2ul[uIdx] = uLine.sources; + + self.cache.mlU2BMap[uIdx] = bIdx; + if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') { + self.cache.mlB2UMap[bIdx] = uIdx; + } + uIdx++; + } + }); + } else { + var uLine = bLine; + + uLine.candidates = candidates; + uLine.iceUfrag = iceUfrag; + uLine.icePwd = icePwd; + uLine.fingerprint = fingerprint; + uLine.port = port; + + mid2ul[uLine.mid] = uLine; + + self.cache.mlU2BMap[uIdx] = bIdx; + if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') { + self.cache.mlB2UMap[bIdx] = uIdx; + } + } + + bIdx++; + }); + + // Rebuild the media array in the right order and add the missing mLines + // (missing from the Plan B SDP). + session.media = []; + mids = []; // reuse + + if (desc.type === 'answer') { + + // The media lines in the answer must match the media lines in the + // offer. The order is important too. Here we assume that Firefox is + // the answerer, so we merely have to use the reconstructed (unified) + // answer to update the cached (unified) answer accordingly. + // + // In the general case, one would have to use the cached (unified) + // offer to find the m-lines that are missing from the reconstructed + // answer, potentially grabbing them from the cached (unified) answer. + // One has to be careful with this approach because inactive m-lines do + // not always have an mid, making it tricky (impossible?) to find where + // exactly and which m-lines are missing from the reconstructed answer. + + for (var i = 0; i < cached.media.length; i++) { + var uLine = cached.media[i]; + + delete uLine.msid; + delete uLine.sources; + delete uLine.ssrcGroups; + + if (typeof sources2ul[i] === 'undefined') { + if (!uLine.direction + || uLine.direction === 'sendrecv') + uLine.direction = 'recvonly'; + else if (uLine.direction === 'sendonly') + uLine.direction = 'inactive'; + } else { + if (!uLine.direction + || uLine.direction === 'sendrecv') + uLine.direction = 'sendrecv'; + else if (uLine.direction === 'recvonly') + uLine.direction = 'sendonly'; + } + + uLine.sources = sources2ul[i]; + uLine.candidates = candidates; + uLine.iceUfrag = iceUfrag; + uLine.icePwd = icePwd; + uLine.fingerprint = fingerprint; + + uLine.rtp = rtp[uLine.type]; + uLine.payloads = payloads[uLine.type]; + uLine.rtcpFb = rtcpFb[uLine.type]; + + session.media.push(uLine); + + if (typeof uLine.mid === 'string') { + // inactive lines don't/may not have an mid. + mids.push(uLine.mid); + } + } + } else { + + // SDP offer/answer (and the JSEP spec) forbids removing an m-section + // under any circumstances. If we are no longer interested in sending a + // track, we just remove the msid and ssrc attributes and set it to + // either a=recvonly (as the reofferer, we must use recvonly if the + // other side was previously sending on the m-section, but we can also + // leave the possibility open if it wasn't previously in use), or + // a=inactive. + + if (typeof cached !== 'undefined' && + typeof cached.media !== 'undefined' && + Array.isArray(cached.media)) { + cached.media.forEach(function(uLine) { + mids.push(uLine.mid); + if (typeof mid2ul[uLine.mid] !== 'undefined') { + session.media.push(mid2ul[uLine.mid]); + } else { + delete uLine.msid; + delete uLine.sources; + delete uLine.ssrcGroups; + + if (!uLine.direction + || uLine.direction === 'sendrecv') { + uLine.direction = 'sendonly'; + } + if (!uLine.direction + || uLine.direction === 'recvonly') { + uLine.direction = 'inactive'; + } + + addSetupAttr (uLine); + session.media.push(uLine); + } + }); + } + + // Add all the remaining (new) m-lines of the transformed SDP. + Object.keys(mid2ul).forEach(function(mid) { + if (mids.indexOf(mid) === -1) { + mids.push(mid); + if (mid2ul[mid].direction === 'recvonly') { + // This is a remote recvonly channel. Add its SSRC to the + // appropriate sendrecv or sendonly channel. + // TODO(gp) what if we don't have sendrecv/sendonly + // channel? + + var done = false; + + session.media.some(function (uLine) { + if ((uLine.direction === 'sendrecv' || + uLine.direction === 'sendonly') && + uLine.type === mid2ul[mid].type) { + // mid2ul[mid] shouldn't have any ssrc-groups + Object.keys(mid2ul[mid].sources).forEach( + function (ssrc) { + uLine.sources[ssrc] = + mid2ul[mid].sources[ssrc]; + }); + + done = true; + return true; + } + }); + + if (!done) { + session.media.push(mid2ul[mid]); + } + } else { + session.media.push(mid2ul[mid]); + } + } + }); + } + + // After we have constructed the Plan Unified m-lines we can figure out + // where (in which m-line) to place the 'recvonly SSRCs'. + // Note: we assume here that we are the answerer in the O/A, so any offers + // which we translate come from the remote side, while answers are local + // (and so our last local description is cached as an 'answer'). + ["audio", "video"].forEach(function (type) { + if (!session || !session.media || !Array.isArray(session.media)) + return; + + var idx = null; + if (Object.keys(recvonlySsrcs[type]).length > 0) { + idx = self.getFirstSendingIndexFromAnswer(type); + if (idx === null){ + // If this is the first offer we receive, we don't have a + // cached answer. Assume that we will be sending media using + // the first m-line for each media type. + + for (var i = 0; i < session.media.length; i++) { + if (session.media[i].type === type) { + idx = i; + break; + } + } + } + } + + if (idx && session.media.length > idx) { + var mLine = session.media[idx]; + Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) { + if (mLine.sources && mLine.sources[ssrc]) { + console.warn("Replacing an existing SSRC."); + } + if (!mLine.sources) { + mLine.sources = {}; + } + + mLine.sources[ssrc] = recvonlySsrcs[type][ssrc]; + }); + } + }); + + if (typeof session.groups !== 'undefined') { + // We regenerate the BUNDLE group (since we regenerated the mids) + session.groups.some(function(group) { + if (group.type === 'BUNDLE') { + group.mids = mids.join(' '); + return true; + } + }); + } + + // msid semantic + session.msidSemantic = { + semantic: 'WMS', + token: '*' + }; + + var resStr = transform.write(session); + + // Cache the transformed SDP (Unified Plan) for later re-use in this + // function. + this.cache[desc.type] = resStr; + + return new RTCSessionDescription({ + type: desc.type, + sdp: resStr + }); + + //#endregion +}; + +},{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){ +/* Copyright @ 2015 Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var transform = require('sdp-transform'); + +exports.write = function(session, opts) { + + if (typeof session !== 'undefined' && + typeof session.media !== 'undefined' && + Array.isArray(session.media)) { + + session.media.forEach(function (mLine) { + // expand sources to ssrcs + if (typeof mLine.sources !== 'undefined' && + Object.keys(mLine.sources).length !== 0) { + mLine.ssrcs = []; + Object.keys(mLine.sources).forEach(function (ssrc) { + var source = mLine.sources[ssrc]; + Object.keys(source).forEach(function (attribute) { + mLine.ssrcs.push({ + id: ssrc, + attribute: attribute, + value: source[attribute] + }); + }); + }); + delete mLine.sources; + } + + // join ssrcs in ssrc groups + if (typeof mLine.ssrcGroups !== 'undefined' && + Array.isArray(mLine.ssrcGroups)) { + mLine.ssrcGroups.forEach(function (ssrcGroup) { + if (typeof ssrcGroup.ssrcs !== 'undefined' && + Array.isArray(ssrcGroup.ssrcs)) { + ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' '); + } + }); + } + }); + } + + // join group mids + if (typeof session !== 'undefined' && + typeof session.groups !== 'undefined' && Array.isArray(session.groups)) { + + session.groups.forEach(function (g) { + if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) { + g.mids = g.mids.join(' '); + } + }); + } + + return transform.write(session, opts); +}; + +exports.parse = function(sdp) { + var session = transform.parse(sdp); + + if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && + Array.isArray(session.media)) { + + session.media.forEach(function (mLine) { + // group sources attributes by ssrc + if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) { + mLine.sources = {}; + mLine.ssrcs.forEach(function (ssrc) { + if (!mLine.sources[ssrc.id]) + mLine.sources[ssrc.id] = {}; + mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value; + }); + + delete mLine.ssrcs; + } + + // split ssrcs in ssrc groups + if (typeof mLine.ssrcGroups !== 'undefined' && + Array.isArray(mLine.ssrcGroups)) { + mLine.ssrcGroups.forEach(function (ssrcGroup) { + if (typeof ssrcGroup.ssrcs === 'string') { + ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' '); + } + }); + } + }); + } + // split group mids + if (typeof session !== 'undefined' && + typeof session.groups !== 'undefined' && Array.isArray(session.groups)) { + + session.groups.forEach(function (g) { + if (typeof g.mids === 'string') { + g.mids = g.mids.split(' '); + } + }); + } + + return session; +}; + + +},{"sdp-transform":14}],21:[function(require,module,exports){ +/** + * UAParser.js v0.7.17 + * Lightweight JavaScript-based User-Agent string parser + * https://github.com/faisalman/ua-parser-js + * + * Copyright © 2012-2016 Faisal Salman + * Dual licensed under GPLv2 & MIT + */ + +(function (window, undefined) { + + 'use strict'; + + ////////////// + // Constants + ///////////// + + + var LIBVERSION = '0.7.17', + EMPTY = '', + UNKNOWN = '?', + FUNC_TYPE = 'function', + UNDEF_TYPE = 'undefined', + OBJ_TYPE = 'object', + STR_TYPE = 'string', + MAJOR = 'major', // deprecated + MODEL = 'model', + NAME = 'name', + TYPE = 'type', + VENDOR = 'vendor', + VERSION = 'version', + ARCHITECTURE= 'architecture', + CONSOLE = 'console', + MOBILE = 'mobile', + TABLET = 'tablet', + SMARTTV = 'smarttv', + WEARABLE = 'wearable', + EMBEDDED = 'embedded'; + + + /////////// + // Helper + ////////// + + + var util = { + extend : function (regexes, extensions) { + var margedRegexes = {}; + for (var i in regexes) { + if (extensions[i] && extensions[i].length % 2 === 0) { + margedRegexes[i] = extensions[i].concat(regexes[i]); + } else { + margedRegexes[i] = regexes[i]; + } + } + return margedRegexes; + }, + has : function (str1, str2) { + if (typeof str1 === "string") { + return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1; + } else { + return false; + } + }, + lowerize : function (str) { + return str.toLowerCase(); + }, + major : function (version) { + return typeof(version) === STR_TYPE ? version.replace(/[^\d\.]/g,'').split(".")[0] : undefined; + }, + trim : function (str) { + return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + } + }; + + + /////////////// + // Map helper + ////////////// + + + var mapper = { + + rgx : function (ua, arrays) { + + //var result = {}, + var i = 0, j, k, p, q, matches, match;//, args = arguments; + + /*// construct object barebones + for (p = 0; p < args[1].length; p++) { + q = args[1][p]; + result[typeof q === OBJ_TYPE ? q[0] : q] = undefined; + }*/ + + // loop through all regexes maps + while (i < arrays.length && !matches) { + + var regex = arrays[i], // even sequence (0,2,4,..) + props = arrays[i + 1]; // odd sequence (1,3,5,..) + j = k = 0; + + // try matching uastring with regexes + while (j < regex.length && !matches) { + + matches = regex[j++].exec(ua); + + if (!!matches) { + for (p = 0; p < props.length; p++) { + match = matches[++k]; + q = props[p]; + // check if given property is actually array + if (typeof q === OBJ_TYPE && q.length > 0) { + if (q.length == 2) { + if (typeof q[1] == FUNC_TYPE) { + // assign modified match + this[q[0]] = q[1].call(this, match); + } else { + // assign given value, ignore regex match + this[q[0]] = q[1]; + } + } else if (q.length == 3) { + // check whether function or regex + if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) { + // call function (usually string mapper) + this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined; + } else { + // sanitize match using given regex + this[q[0]] = match ? match.replace(q[1], q[2]) : undefined; + } + } else if (q.length == 4) { + this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined; + } + } else { + this[q] = match ? match : undefined; + } + } + } + } + i += 2; + } + // console.log(this); + //return this; + }, + + str : function (str, map) { + + for (var i in map) { + // check if array + if (typeof map[i] === OBJ_TYPE && map[i].length > 0) { + for (var j = 0; j < map[i].length; j++) { + if (util.has(map[i][j], str)) { + return (i === UNKNOWN) ? undefined : i; + } + } + } else if (util.has(map[i], str)) { + return (i === UNKNOWN) ? undefined : i; + } + } + return str; + } + }; + + + /////////////// + // String map + ////////////// + + + var maps = { + + browser : { + oldsafari : { + version : { + '1.0' : '/8', + '1.2' : '/1', + '1.3' : '/3', + '2.0' : '/412', + '2.0.2' : '/416', + '2.0.3' : '/417', + '2.0.4' : '/419', + '?' : '/' + } + } + }, + + device : { + amazon : { + model : { + 'Fire Phone' : ['SD', 'KF'] + } + }, + sprint : { + model : { + 'Evo Shift 4G' : '7373KT' + }, + vendor : { + 'HTC' : 'APA', + 'Sprint' : 'Sprint' + } + } + }, + + os : { + windows : { + version : { + 'ME' : '4.90', + 'NT 3.11' : 'NT3.51', + 'NT 4.0' : 'NT4.0', + '2000' : 'NT 5.0', + 'XP' : ['NT 5.1', 'NT 5.2'], + 'Vista' : 'NT 6.0', + '7' : 'NT 6.1', + '8' : 'NT 6.2', + '8.1' : 'NT 6.3', + '10' : ['NT 6.4', 'NT 10.0'], + 'RT' : 'ARM' + } + } + } + }; + + + ////////////// + // Regex map + ///////////// + + + var regexes = { + + browser : [[ + + // Presto based + /(opera\smini)\/([\w\.-]+)/i, // Opera Mini + /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet + /(opera).+version\/([\w\.]+)/i, // Opera > 9.80 + /(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80 + ], [NAME, VERSION], [ + + /(opios)[\/\s]+([\w\.]+)/i // Opera mini on iphone >= 8.0 + ], [[NAME, 'Opera Mini'], VERSION], [ + + /\s(opr)\/([\w\.]+)/i // Opera Webkit + ], [[NAME, 'Opera'], VERSION], [ + + // Mixed + /(kindle)\/([\w\.]+)/i, // Kindle + /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i, + // Lunascape/Maxthon/Netfront/Jasmine/Blazer + + // Trident based + /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i, + // Avant/IEMobile/SlimBrowser/Baidu + /(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer + + // Webkit/KHTML based + /(rekonq)\/([\w\.]+)*/i, // Rekonq + /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser)\/([\w\.-]+)/i + // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser + ], [NAME, VERSION], [ + + /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11 + ], [[NAME, 'IE'], VERSION], [ + + /(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge + ], [NAME, VERSION], [ + + /(yabrowser)\/([\w\.]+)/i // Yandex + ], [[NAME, 'Yandex'], VERSION], [ + + /(puffin)\/([\w\.]+)/i // Puffin + ], [[NAME, 'Puffin'], VERSION], [ + + /((?:[\s\/])uc?\s?browser|(?:juc.+)ucweb)[\/\s]?([\w\.]+)/i + // UCBrowser + ], [[NAME, 'UCBrowser'], VERSION], [ + + /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon + ], [[NAME, /_/g, ' '], VERSION], [ + + /(micromessenger)\/([\w\.]+)/i // WeChat + ], [[NAME, 'WeChat'], VERSION], [ + + /(QQ)\/([\d\.]+)/i // QQ, aka ShouQ + ], [NAME, VERSION], [ + + /m?(qqbrowser)[\/\s]?([\w\.]+)/i // QQBrowser + ], [NAME, VERSION], [ + + /xiaomi\/miuibrowser\/([\w\.]+)/i // MIUI Browser + ], [VERSION, [NAME, 'MIUI Browser']], [ + + /;fbav\/([\w\.]+);/i // Facebook App for iOS & Android + ], [VERSION, [NAME, 'Facebook']], [ + + /headlesschrome(?:\/([\w\.]+)|\s)/i // Chrome Headless + ], [VERSION, [NAME, 'Chrome Headless']], [ + + /\swv\).+(chrome)\/([\w\.]+)/i // Chrome WebView + ], [[NAME, /(.+)/, '$1 WebView'], VERSION], [ + + /((?:oculus|samsung)browser)\/([\w\.]+)/i + ], [[NAME, /(.+(?:g|us))(.+)/, '$1 $2'], VERSION], [ // Oculus / Samsung Browser + + /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i // Android Browser + ], [VERSION, [NAME, 'Android Browser']], [ + + /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i + // Chrome/OmniWeb/Arora/Tizen/Nokia + ], [NAME, VERSION], [ + + /(dolfin)\/([\w\.]+)/i // Dolphin + ], [[NAME, 'Dolphin'], VERSION], [ + + /((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS + ], [[NAME, 'Chrome'], VERSION], [ + + /(coast)\/([\w\.]+)/i // Opera Coast + ], [[NAME, 'Opera Coast'], VERSION], [ + + /fxios\/([\w\.-]+)/i // Firefox for iOS + ], [VERSION, [NAME, 'Firefox']], [ + + /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari + ], [VERSION, [NAME, 'Mobile Safari']], [ + + /version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile + ], [VERSION, NAME], [ + + /webkit.+?(gsa)\/([\w\.]+).+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Google Search Appliance on iOS + ], [[NAME, 'GSA'], VERSION], [ + + /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0 + ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [ + + /(konqueror)\/([\w\.]+)/i, // Konqueror + /(webkit|khtml)\/([\w\.]+)/i + ], [NAME, VERSION], [ + + // Gecko based + /(navigator|netscape)\/([\w\.-]+)/i // Netscape + ], [[NAME, 'Netscape'], VERSION], [ + /(swiftfox)/i, // Swiftfox + /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i, + // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror + /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i, + // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix + /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla + + // Other + /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i, + // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir + /(links)\s\(([\w\.]+)/i, // Links + /(gobrowser)\/?([\w\.]+)*/i, // GoBrowser + /(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser + /(mosaic)[\/\s]([\w\.]+)/i // Mosaic + ], [NAME, VERSION] + + /* ///////////////////// + // Media players BEGIN + //////////////////////// + + , [ + + /(apple(?:coremedia|))\/((\d+)[\w\._]+)/i, // Generic Apple CoreMedia + /(coremedia) v((\d+)[\w\._]+)/i + ], [NAME, VERSION], [ + + /(aqualung|lyssna|bsplayer)\/((\d+)?[\w\.-]+)/i // Aqualung/Lyssna/BSPlayer + ], [NAME, VERSION], [ + + /(ares|ossproxy)\s((\d+)[\w\.-]+)/i // Ares/OSSProxy + ], [NAME, VERSION], [ + + /(audacious|audimusicstream|amarok|bass|core|dalvik|gnomemplayer|music on console|nsplayer|psp-internetradioplayer|videos)\/((\d+)[\w\.-]+)/i, + // Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/Dalvik/GnomeMplayer/MoC + // NSPlayer/PSP-InternetRadioPlayer/Videos + /(clementine|music player daemon)\s((\d+)[\w\.-]+)/i, // Clementine/MPD + /(lg player|nexplayer)\s((\d+)[\d\.]+)/i, + /player\/(nexplayer|lg player)\s((\d+)[\w\.-]+)/i // NexPlayer/LG Player + ], [NAME, VERSION], [ + /(nexplayer)\s((\d+)[\w\.-]+)/i // Nexplayer + ], [NAME, VERSION], [ + + /(flrp)\/((\d+)[\w\.-]+)/i // Flip Player + ], [[NAME, 'Flip Player'], VERSION], [ + + /(fstream|nativehost|queryseekspider|ia-archiver|facebookexternalhit)/i + // FStream/NativeHost/QuerySeekSpider/IA Archiver/facebookexternalhit + ], [NAME], [ + + /(gstreamer) souphttpsrc (?:\([^\)]+\)){0,1} libsoup\/((\d+)[\w\.-]+)/i + // Gstreamer + ], [NAME, VERSION], [ + + /(htc streaming player)\s[\w_]+\s\/\s((\d+)[\d\.]+)/i, // HTC Streaming Player + /(java|python-urllib|python-requests|wget|libcurl)\/((\d+)[\w\.-_]+)/i, + // Java/urllib/requests/wget/cURL + /(lavf)((\d+)[\d\.]+)/i // Lavf (FFMPEG) + ], [NAME, VERSION], [ + + /(htc_one_s)\/((\d+)[\d\.]+)/i // HTC One S + ], [[NAME, /_/g, ' '], VERSION], [ + + /(mplayer)(?:\s|\/)(?:(?:sherpya-){0,1}svn)(?:-|\s)(r\d+(?:-\d+[\w\.-]+){0,1})/i + // MPlayer SVN + ], [NAME, VERSION], [ + + /(mplayer)(?:\s|\/|[unkow-]+)((\d+)[\w\.-]+)/i // MPlayer + ], [NAME, VERSION], [ + + /(mplayer)/i, // MPlayer (no other info) + /(yourmuze)/i, // YourMuze + /(media player classic|nero showtime)/i // Media Player Classic/Nero ShowTime + ], [NAME], [ + + /(nero (?:home|scout))\/((\d+)[\w\.-]+)/i // Nero Home/Nero Scout + ], [NAME, VERSION], [ + + /(nokia\d+)\/((\d+)[\w\.-]+)/i // Nokia + ], [NAME, VERSION], [ + + /\s(songbird)\/((\d+)[\w\.-]+)/i // Songbird/Philips-Songbird + ], [NAME, VERSION], [ + + /(winamp)3 version ((\d+)[\w\.-]+)/i, // Winamp + /(winamp)\s((\d+)[\w\.-]+)/i, + /(winamp)mpeg\/((\d+)[\w\.-]+)/i + ], [NAME, VERSION], [ + + /(ocms-bot|tapinradio|tunein radio|unknown|winamp|inlight radio)/i // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info) + // inlight radio + ], [NAME], [ + + /(quicktime|rma|radioapp|radioclientapplication|soundtap|totem|stagefright|streamium)\/((\d+)[\w\.-]+)/i + // QuickTime/RealMedia/RadioApp/RadioClientApplication/ + // SoundTap/Totem/Stagefright/Streamium + ], [NAME, VERSION], [ + + /(smp)((\d+)[\d\.]+)/i // SMP + ], [NAME, VERSION], [ + + /(vlc) media player - version ((\d+)[\w\.]+)/i, // VLC Videolan + /(vlc)\/((\d+)[\w\.-]+)/i, + /(xbmc|gvfs|xine|xmms|irapp)\/((\d+)[\w\.-]+)/i, // XBMC/gvfs/Xine/XMMS/irapp + /(foobar2000)\/((\d+)[\d\.]+)/i, // Foobar2000 + /(itunes)\/((\d+)[\d\.]+)/i // iTunes + ], [NAME, VERSION], [ + + /(wmplayer)\/((\d+)[\w\.-]+)/i, // Windows Media Player + /(windows-media-player)\/((\d+)[\w\.-]+)/i + ], [[NAME, /-/g, ' '], VERSION], [ + + /windows\/((\d+)[\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ (home media server)/i + // Windows Media Server + ], [VERSION, [NAME, 'Windows']], [ + + /(com\.riseupradioalarm)\/((\d+)[\d\.]*)/i // RiseUP Radio Alarm + ], [NAME, VERSION], [ + + /(rad.io)\s((\d+)[\d\.]+)/i, // Rad.io + /(radio.(?:de|at|fr))\s((\d+)[\d\.]+)/i + ], [[NAME, 'rad.io'], VERSION] + + ////////////////////// + // Media players END + ////////////////////*/ + + ], + + cpu : [[ + + /(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i // AMD64 + ], [[ARCHITECTURE, 'amd64']], [ + + /(ia32(?=;))/i // IA32 (quicktime) + ], [[ARCHITECTURE, util.lowerize]], [ + + /((?:i[346]|x)86)[;\)]/i // IA32 + ], [[ARCHITECTURE, 'ia32']], [ + + // PocketPC mistakenly identified as PowerPC + /windows\s(ce|mobile);\sppc;/i + ], [[ARCHITECTURE, 'arm']], [ + + /((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i // PowerPC + ], [[ARCHITECTURE, /ower/, '', util.lowerize]], [ + + /(sun4\w)[;\)]/i // SPARC + ], [[ARCHITECTURE, 'sparc']], [ + + /((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+;))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i + // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC + ], [[ARCHITECTURE, util.lowerize]] + ], + + device : [[ + + /\((ipad|playbook);[\w\s\);-]+(rim|apple)/i // iPad/PlayBook + ], [MODEL, VENDOR, [TYPE, TABLET]], [ + + /applecoremedia\/[\w\.]+ \((ipad)/ // iPad + ], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [ + + /(apple\s{0,1}tv)/i // Apple TV + ], [[MODEL, 'Apple TV'], [VENDOR, 'Apple']], [ + + /(archos)\s(gamepad2?)/i, // Archos + /(hp).+(touchpad)/i, // HP TouchPad + /(hp).+(tablet)/i, // HP Tablet + /(kindle)\/([\w\.]+)/i, // Kindle + /\s(nook)[\w\s]+build\/(\w+)/i, // Nook + /(dell)\s(strea[kpr\s\d]*[\dko])/i // Dell Streak + ], [VENDOR, MODEL, [TYPE, TABLET]], [ + + /(kf[A-z]+)\sbuild\/[\w\.]+.*silk\//i // Kindle Fire HD + ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [ + /(sd|kf)[0349hijorstuw]+\sbuild\/[\w\.]+.*silk\//i // Fire Phone + ], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [ + + /\((ip[honed|\s\w*]+);.+(apple)/i // iPod/iPhone + ], [MODEL, VENDOR, [TYPE, MOBILE]], [ + /\((ip[honed|\s\w*]+);/i // iPod/iPhone + ], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [ + + /(blackberry)[\s-]?(\w+)/i, // BlackBerry + /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[\s_-]?([\w-]+)*/i, + // BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron + /(hp)\s([\w\s]+\w)/i, // HP iPAQ + /(asus)-?(\w+)/i // Asus + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + /\(bb10;\s(\w+)/i // BlackBerry 10 + ], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [ + // Asus Tablets + /android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone)/i + ], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [ + + /(sony)\s(tablet\s[ps])\sbuild\//i, // Sony + /(sony)?(?:sgp.+)\sbuild\//i + ], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [ + /android.+\s([c-g]\d{4}|so[-l]\w+)\sbuild\//i + ], [MODEL, [VENDOR, 'Sony'], [TYPE, MOBILE]], [ + + /\s(ouya)\s/i, // Ouya + /(nintendo)\s([wids3u]+)/i // Nintendo + ], [VENDOR, MODEL, [TYPE, CONSOLE]], [ + + /android.+;\s(shield)\sbuild/i // Nvidia + ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [ + + /(playstation\s[34portablevi]+)/i // Playstation + ], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [ + + /(sprint\s(\w+))/i // Sprint Phones + ], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [ + + /(lenovo)\s?(S(?:5000|6000)+(?:[-][\w+]))/i // Lenovo tablets + ], [VENDOR, MODEL, [TYPE, TABLET]], [ + + /(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i, // HTC + /(zte)-(\w+)*/i, // ZTE + /(alcatel|geeksphone|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i + // Alcatel/GeeksPhone/Lenovo/Nexian/Panasonic/Sony + ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [ + + /(nexus\s9)/i // HTC Nexus 9 + ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [ + + /d\/huawei([\w\s-]+)[;\)]/i, + /(nexus\s6p)/i // Huawei + ], [MODEL, [VENDOR, 'Huawei'], [TYPE, MOBILE]], [ + + /(microsoft);\s(lumia[\s\w]+)/i // Microsoft Lumia + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + + /[\s\(;](xbox(?:\sone)?)[\s\);]/i // Microsoft Xbox + ], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [ + /(kin\.[onetw]{3})/i // Microsoft Kin + ], [[MODEL, /\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [ + + // Motorola + /\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?(:?\s4g)?)[\w\s]+build\//i, + /mot[\s-]?(\w+)*/i, + /(XT\d{3,4}) build\//i, + /(nexus\s6)/i + ], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [ + /android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i + ], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [ + + /hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i // HbbTV devices + ], [[VENDOR, util.trim], [MODEL, util.trim], [TYPE, SMARTTV]], [ + + /hbbtv.+maple;(\d+)/i + ], [[MODEL, /^/, 'SmartTV'], [VENDOR, 'Samsung'], [TYPE, SMARTTV]], [ + + /\(dtv[\);].+(aquos)/i // Sharp + ], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [ + + /android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i, + /((SM-T\w+))/i + ], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [ // Samsung + /smart-tv.+(samsung)/i + ], [VENDOR, [TYPE, SMARTTV], MODEL], [ + /((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i, + /(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i, + /sec-((sgh\w+))/i + ], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [ + + /sie-(\w+)*/i // Siemens + ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [ + + /(maemo|nokia).*(n900|lumia\s\d+)/i, // Nokia + /(nokia)[\s_-]?([\w-]+)*/i + ], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [ + + /android\s3\.[\s\w;-]{10}(a\d{3})/i // Acer + ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [ + + /android.+([vl]k\-?\d{3})\s+build/i // LG Tablet + ], [MODEL, [VENDOR, 'LG'], [TYPE, TABLET]], [ + /android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet + ], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [ + /(lg) netcast\.tv/i // LG SmartTV + ], [VENDOR, MODEL, [TYPE, SMARTTV]], [ + /(nexus\s[45])/i, // LG + /lg[e;\s\/-]+(\w+)*/i, + /android.+lg(\-?[\d\w]+)\s+build/i + ], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [ + + /android.+(ideatab[a-z0-9\-\s]+)/i // Lenovo + ], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [ + + /linux;.+((jolla));/i // Jolla + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + + /((pebble))app\/[\d\.]+\s/i // Pebble + ], [VENDOR, MODEL, [TYPE, WEARABLE]], [ + + /android.+;\s(oppo)\s?([\w\s]+)\sbuild/i // OPPO + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + + /crkey/i // Google Chromecast + ], [[MODEL, 'Chromecast'], [VENDOR, 'Google']], [ + + /android.+;\s(glass)\s\d/i // Google Glass + ], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [ + + /android.+;\s(pixel c)\s/i // Google Pixel C + ], [MODEL, [VENDOR, 'Google'], [TYPE, TABLET]], [ + + /android.+;\s(pixel xl|pixel)\s/i // Google Pixel + ], [MODEL, [VENDOR, 'Google'], [TYPE, MOBILE]], [ + + /android.+(\w+)\s+build\/hm\1/i, // Xiaomi Hongmi 'numeric' models + /android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i, // Xiaomi Hongmi + /android.+(mi[\s\-_]*(?:one|one[\s_]plus|note lte)?[\s_]*(?:\d\w)?)\s+build/i, // Xiaomi Mi + /android.+(redmi[\s\-_]*(?:note)?(?:[\s_]*[\w\s]+)?)\s+build/i // Redmi Phones + ], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [ + /android.+(mi[\s\-_]*(?:pad)?(?:[\s_]*[\w\s]+)?)\s+build/i // Mi Pad tablets + ],[[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, TABLET]], [ + /android.+;\s(m[1-5]\snote)\sbuild/i // Meizu Tablet + ], [MODEL, [VENDOR, 'Meizu'], [TYPE, TABLET]], [ + + /android.+a000(1)\s+build/i // OnePlus + ], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [ + + /android.+[;\/]\s*(RCT[\d\w]+)\s+build/i // RCA Tablets + ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Venue[\d\s]*)\s+build/i // Dell Venue Tablets + ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Q[T|M][\d\w]+)\s+build/i // Verizon Tablet + ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [ + + /android.+[;\/]\s+(Barnes[&\s]+Noble\s+|BN[RT])(V?.*)\s+build/i // Barnes & Noble Tablet + ], [[VENDOR, 'Barnes & Noble'], MODEL, [TYPE, TABLET]], [ + + /android.+[;\/]\s+(TM\d{3}.*\b)\s+build/i // Barnes & Noble Tablet + ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(zte)?.+(k\d{2})\s+build/i // ZTE K Series Tablet + ], [[VENDOR, 'ZTE'], MODEL, [TYPE, TABLET]], [ + + /android.+[;\/]\s*(gen\d{3})\s+build.*49h/i // Swiss GEN Mobile + ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [ + + /android.+[;\/]\s*(zur\d{3})\s+build/i // Swiss ZUR Tablet + ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*((Zeki)?TB.*\b)\s+build/i // Zeki Tablets + ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [ + + /(android).+[;\/]\s+([YR]\d{2}x?.*)\s+build/i, + /android.+[;\/]\s+(Dragon[\-\s]+Touch\s+|DT)(.+)\s+build/i // Dragon Touch Tablet + ], [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]], [ + + /android.+[;\/]\s*(NS-?.+)\s+build/i // Insignia Tablets + ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*((NX|Next)-?.+)\s+build/i // NextBook Tablets + ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Xtreme\_?)?(V(1[045]|2[015]|30|40|60|7[05]|90))\s+build/i + ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [ // Voice Xtreme Phones + + /android.+[;\/]\s*(LVTEL\-?)?(V1[12])\s+build/i // LvTel Phones + ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [ + + /android.+[;\/]\s*(V(100MD|700NA|7011|917G).*\b)\s+build/i // Envizen Tablets + ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Le[\s\-]+Pan)[\s\-]+(.*\b)\s+build/i // Le Pan Tablets + ], [VENDOR, MODEL, [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Trio[\s\-]*.*)\s+build/i // MachSpeed Tablets + ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [ + + /android.+[;\/]\s*(Trinity)[\-\s]*(T\d{3})\s+build/i // Trinity Tablets + ], [VENDOR, MODEL, [TYPE, TABLET]], [ + + /android.+[;\/]\s*TU_(1491)\s+build/i // Rotor Tablets + ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]], [ + + /android.+(KS(.+))\s+build/i // Amazon Kindle Tablets + ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [ + + /android.+(Gigaset)[\s\-]+(Q.+)\s+build/i // Gigaset Tablets + ], [VENDOR, MODEL, [TYPE, TABLET]], [ + + /\s(tablet|tab)[;\/]/i, // Unidentifiable Tablet + /\s(mobile)(?:[;\/]|\ssafari)/i // Unidentifiable Mobile + ], [[TYPE, util.lowerize], VENDOR, MODEL], [ + + /(android.+)[;\/].+build/i // Generic Android Device + ], [MODEL, [VENDOR, 'Generic']] + + + /*////////////////////////// + // TODO: move to string map + //////////////////////////// + + /(C6603)/i // Sony Xperia Z C6603 + ], [[MODEL, 'Xperia Z C6603'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [ + /(C6903)/i // Sony Xperia Z 1 + ], [[MODEL, 'Xperia Z 1'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [ + + /(SM-G900[F|H])/i // Samsung Galaxy S5 + ], [[MODEL, 'Galaxy S5'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [ + /(SM-G7102)/i // Samsung Galaxy Grand 2 + ], [[MODEL, 'Galaxy Grand 2'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [ + /(SM-G530H)/i // Samsung Galaxy Grand Prime + ], [[MODEL, 'Galaxy Grand Prime'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [ + /(SM-G313HZ)/i // Samsung Galaxy V + ], [[MODEL, 'Galaxy V'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [ + /(SM-T805)/i // Samsung Galaxy Tab S 10.5 + ], [[MODEL, 'Galaxy Tab S 10.5'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [ + /(SM-G800F)/i // Samsung Galaxy S5 Mini + ], [[MODEL, 'Galaxy S5 Mini'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [ + /(SM-T311)/i // Samsung Galaxy Tab 3 8.0 + ], [[MODEL, 'Galaxy Tab 3 8.0'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [ + + /(T3C)/i // Advan Vandroid T3C + ], [MODEL, [VENDOR, 'Advan'], [TYPE, TABLET]], [ + /(ADVAN T1J\+)/i // Advan Vandroid T1J+ + ], [[MODEL, 'Vandroid T1J+'], [VENDOR, 'Advan'], [TYPE, TABLET]], [ + /(ADVAN S4A)/i // Advan Vandroid S4A + ], [[MODEL, 'Vandroid S4A'], [VENDOR, 'Advan'], [TYPE, MOBILE]], [ + + /(V972M)/i // ZTE V972M + ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [ + + /(i-mobile)\s(IQ\s[\d\.]+)/i // i-mobile IQ + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + /(IQ6.3)/i // i-mobile IQ IQ 6.3 + ], [[MODEL, 'IQ 6.3'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [ + /(i-mobile)\s(i-style\s[\d\.]+)/i // i-mobile i-STYLE + ], [VENDOR, MODEL, [TYPE, MOBILE]], [ + /(i-STYLE2.1)/i // i-mobile i-STYLE 2.1 + ], [[MODEL, 'i-STYLE 2.1'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [ + + /(mobiistar touch LAI 512)/i // mobiistar touch LAI 512 + ], [[MODEL, 'Touch LAI 512'], [VENDOR, 'mobiistar'], [TYPE, MOBILE]], [ + + ///////////// + // END TODO + ///////////*/ + + ], + + engine : [[ + + /windows.+\sedge\/([\w\.]+)/i // EdgeHTML + ], [VERSION, [NAME, 'EdgeHTML']], [ + + /(presto)\/([\w\.]+)/i, // Presto + /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m + /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links + /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab + ], [NAME, VERSION], [ + + /rv\:([\w\.]+).*(gecko)/i // Gecko + ], [VERSION, NAME] + ], + + os : [[ + + // Windows based + /microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes) + ], [NAME, VERSION], [ + /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT + /(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s]+\w)*/i, // Windows Phone + /(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i + ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [ + /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i + ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [ + + // Mobile/Embedded OS + /\((bb)(10);/i // BlackBerry 10 + ], [[NAME, 'BlackBerry'], VERSION], [ + /(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry + /(tizen)[\/\s]([\w\.]+)/i, // Tizen + /(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i, + // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki + /linux;.+(sailfish);/i // Sailfish OS + ], [NAME, VERSION], [ + /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian + ], [[NAME, 'Symbian'], VERSION], [ + /\((series40);/i // Series 40 + ], [NAME], [ + /mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS + ], [[NAME, 'Firefox OS'], VERSION], [ + + // Console + /(nintendo|playstation)\s([wids34portablevu]+)/i, // Nintendo/Playstation + + // GNU/Linux based + /(mint)[\/\s\(]?(\w+)*/i, // Mint + /(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux + /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]+)*/i, + // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware + // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus + /(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux + /(gnu)\s?([\w\.]+)*/i // GNU + ], [NAME, VERSION], [ + + /(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS + ], [[NAME, 'Chromium OS'], VERSION],[ + + // Solaris + /(sunos)\s?([\w\.]+\d)*/i // Solaris + ], [[NAME, 'Solaris'], VERSION], [ + + // BSD based + /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly + ], [NAME, VERSION],[ + + /(haiku)\s(\w+)/i // Haiku + ], [NAME, VERSION],[ + + /cfnetwork\/.+darwin/i, + /ip[honead]+(?:.*os\s([\w]+)\slike\smac|;\sopera)/i // iOS + ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [ + + /(mac\sos\sx)\s?([\w\s\.]+\w)*/i, + /(macintosh|mac(?=_powerpc)\s)/i // Mac OS + ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [ + + // Other + /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris + /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX + /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i, + // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS + /(unix)\s?([\w\.]+)*/i // UNIX + ], [NAME, VERSION] + ] + }; + + + ///////////////// + // Constructor + //////////////// + /* + var Browser = function (name, version) { + this[NAME] = name; + this[VERSION] = version; + }; + var CPU = function (arch) { + this[ARCHITECTURE] = arch; + }; + var Device = function (vendor, model, type) { + this[VENDOR] = vendor; + this[MODEL] = model; + this[TYPE] = type; + }; + var Engine = Browser; + var OS = Browser; + */ + var UAParser = function (uastring, extensions) { + + if (typeof uastring === 'object') { + extensions = uastring; + uastring = undefined; + } + + if (!(this instanceof UAParser)) { + return new UAParser(uastring, extensions).getResult(); + } + + var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); + var rgxmap = extensions ? util.extend(regexes, extensions) : regexes; + //var browser = new Browser(); + //var cpu = new CPU(); + //var device = new Device(); + //var engine = new Engine(); + //var os = new OS(); + + this.getBrowser = function () { + var browser = { name: undefined, version: undefined }; + mapper.rgx.call(browser, ua, rgxmap.browser); + browser.major = util.major(browser.version); // deprecated + return browser; + }; + this.getCPU = function () { + var cpu = { architecture: undefined }; + mapper.rgx.call(cpu, ua, rgxmap.cpu); + return cpu; + }; + this.getDevice = function () { + var device = { vendor: undefined, model: undefined, type: undefined }; + mapper.rgx.call(device, ua, rgxmap.device); + return device; + }; + this.getEngine = function () { + var engine = { name: undefined, version: undefined }; + mapper.rgx.call(engine, ua, rgxmap.engine); + return engine; + }; + this.getOS = function () { + var os = { name: undefined, version: undefined }; + mapper.rgx.call(os, ua, rgxmap.os); + return os; + }; + this.getResult = function () { + return { + ua : this.getUA(), + browser : this.getBrowser(), + engine : this.getEngine(), + os : this.getOS(), + device : this.getDevice(), + cpu : this.getCPU() + }; + }; + this.getUA = function () { + return ua; + }; + this.setUA = function (uastring) { + ua = uastring; + //browser = new Browser(); + //cpu = new CPU(); + //device = new Device(); + //engine = new Engine(); + //os = new OS(); + return this; + }; + return this; + }; + + UAParser.VERSION = LIBVERSION; + UAParser.BROWSER = { + NAME : NAME, + MAJOR : MAJOR, // deprecated + VERSION : VERSION + }; + UAParser.CPU = { + ARCHITECTURE : ARCHITECTURE + }; + UAParser.DEVICE = { + MODEL : MODEL, + VENDOR : VENDOR, + TYPE : TYPE, + CONSOLE : CONSOLE, + MOBILE : MOBILE, + SMARTTV : SMARTTV, + TABLET : TABLET, + WEARABLE: WEARABLE, + EMBEDDED: EMBEDDED + }; + UAParser.ENGINE = { + NAME : NAME, + VERSION : VERSION + }; + UAParser.OS = { + NAME : NAME, + VERSION : VERSION + }; + //UAParser.Utils = util; + + /////////// + // Export + ////////// + + + // check js environment + if (typeof(exports) !== UNDEF_TYPE) { + // nodejs env + if (typeof module !== UNDEF_TYPE && module.exports) { + exports = module.exports = UAParser; + } + // TODO: test!!!!!!!! + /* + if (require && require.main === module && process) { + // cli + var jsonize = function (arr) { + var res = []; + for (var i in arr) { + res.push(new UAParser(arr[i]).getResult()); + } + process.stdout.write(JSON.stringify(res, null, 2) + '\n'); + }; + if (process.stdin.isTTY) { + // via args + jsonize(process.argv.slice(2)); + } else { + // via pipe + var str = ''; + process.stdin.on('readable', function() { + var read = process.stdin.read(); + if (read !== null) { + str += read; + } + }); + process.stdin.on('end', function () { + jsonize(str.replace(/\n$/, '').split('\n')); + }); + } + } + */ + exports.UAParser = UAParser; + } else { + // requirejs env (optional) + if (typeof(define) === FUNC_TYPE && define.amd) { + define(function () { + return UAParser; + }); + } else if (window) { + // browser env + window.UAParser = UAParser; + } + } + + // jQuery/Zepto specific (optional) + // Note: + // In AMD env the global scope should be kept clean, but jQuery is an exception. + // jQuery always exports to global scope, unless jQuery.noConflict(true) is used, + // and we should catch that. + var $ = window && (window.jQuery || window.Zepto); + if (typeof $ !== UNDEF_TYPE) { + var parser = new UAParser(); + $.ua = parser.getResult(); + $.ua.get = function () { + return parser.getUA(); + }; + $.ua.set = function (uastring) { + parser.setUA(uastring); + var result = parser.getResult(); + for (var prop in result) { + $.ua[prop] = result[prop]; + } + }; + } + +})(typeof window === 'object' ? window : this); + +},{}],22:[function(require,module,exports){ +(function (global){ + +var rng; + +var crypto = global.crypto || global.msCrypto; // for IE 11 +if (crypto && crypto.getRandomValues) { + // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto + // Moderately fast, high quality + var _rnds8 = new Uint8Array(16); + rng = function whatwgRNG() { + crypto.getRandomValues(_rnds8); + return _rnds8; + }; +} + +if (!rng) { + // Math.random()-based (RNG) + // + // If all else fails, use Math.random(). It's fast, but is of unspecified + // quality. + var _rnds = new Array(16); + rng = function() { + for (var i = 0, r; i < 16; i++) { + if ((i & 0x03) === 0) r = Math.random() * 0x100000000; + _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff; + } + + return _rnds; + }; +} + +module.exports = rng; + + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],23:[function(require,module,exports){ +// uuid.js +// +// Copyright (c) 2010-2012 Robert Kieffer +// MIT License - http://opensource.org/licenses/mit-license.php + +// Unique ID creation requires a high quality random # generator. We feature +// detect to determine the best RNG source, normalizing to a function that +// returns 128-bits of randomness, since that's what's usually required +var _rng = require('./rng'); + +// Maps for number <-> hex string conversion +var _byteToHex = []; +var _hexToByte = {}; +for (var i = 0; i < 256; i++) { + _byteToHex[i] = (i + 0x100).toString(16).substr(1); + _hexToByte[_byteToHex[i]] = i; +} + +// **`parse()` - Parse a UUID into it's component bytes** +function parse(s, buf, offset) { + var i = (buf && offset) || 0, ii = 0; + + buf = buf || []; + s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) { + if (ii < 16) { // Don't overflow! + buf[i + ii++] = _hexToByte[oct]; + } + }); + + // Zero out remaining bytes if string was short + while (ii < 16) { + buf[i + ii++] = 0; + } + + return buf; +} + +// **`unparse()` - Convert UUID byte array (ala parse()) into a string** +function unparse(buf, offset) { + var i = offset || 0, bth = _byteToHex; + return bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]]; +} + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html + +// random #'s we need to init node and clockseq +var _seedBytes = _rng(); + +// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) +var _nodeId = [ + _seedBytes[0] | 0x01, + _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5] +]; + +// Per 4.2.2, randomize (14 bit) clockseq +var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff; + +// Previous uuid creation time +var _lastMSecs = 0, _lastNSecs = 0; + +// See https://github.com/broofa/node-uuid for API details +function v1(options, buf, offset) { + var i = buf && offset || 0; + var b = buf || []; + + options = options || {}; + + var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; + + // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime(); + + // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; + + // Time since last uuid creation (in msecs) + var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000; + + // Per 4.2.1.2, Bump clockseq on clock regression + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } + + // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } + + // Per 4.2.1.2 Throw error if too many uuids are requested + if (nsecs >= 10000) { + throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec'); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; + + // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + msecs += 12219292800000; + + // `time_low` + var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; + + // `time_mid` + var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; + + // `time_high_and_version` + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + b[i++] = tmh >>> 16 & 0xff; + + // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + b[i++] = clockseq >>> 8 | 0x80; + + // `clock_seq_low` + b[i++] = clockseq & 0xff; + + // `node` + var node = options.node || _nodeId; + for (var n = 0; n < 6; n++) { + b[i + n] = node[n]; + } + + return buf ? buf : unparse(b); +} + +// **`v4()` - Generate random UUID** + +// See https://github.com/broofa/node-uuid for API details +function v4(options, buf, offset) { + // Deprecated - 'format' argument, as supported in v1.2 + var i = buf && offset || 0; + + if (typeof(options) == 'string') { + buf = options == 'binary' ? new Array(16) : null; + options = null; + } + options = options || {}; + + var rnds = options.random || (options.rng || _rng)(); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + // Copy bytes to buffer, if provided + if (buf) { + for (var ii = 0; ii < 16; ii++) { + buf[i + ii] = rnds[ii]; + } + } + + return buf || unparse(rnds); +} + +// Export public API +var uuid = v4; +uuid.v1 = v1; +uuid.v4 = v4; +uuid.parse = parse; +uuid.unparse = unparse; + +module.exports = uuid; + +},{"./rng":22}],24:[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); + +},{}]},{},[2])(2) +}); diff --git a/bower_components/kurento-utils/js/kurento-utils.map b/bower_components/kurento-utils/js/kurento-utils.map new file mode 100755 index 0000000..4b01c13 --- /dev/null +++ b/bower_components/kurento-utils/js/kurento-utils.map @@ -0,0 +1 @@ +{"version":3,"sources":["../node_modules/browser-pack/_prelude.js","../node_modules/freeice/stun.json","../node_modules/freeice/turn.json","../lib/WebRtcPeer.js","../lib/browser.js","../lib/index.js","../node_modules/events/events.js","../node_modules/freeice/index.js","../node_modules/hark/hark.js","../node_modules/inherits/inherits_browser.js","../node_modules/merge/merge.js","../node_modules/normalice/index.js","../node_modules/sdp-transform/lib/grammar.js","../node_modules/sdp-transform/lib/index.js","../node_modules/sdp-transform/lib/parser.js","../node_modules/sdp-transform/lib/writer.js","../node_modules/sdp-translator/lib/array-equals.js","../node_modules/sdp-translator/lib/index.js","../node_modules/sdp-translator/lib/interop.js","../node_modules/sdp-translator/lib/transform.js","../node_modules/ua-parser-js/src/ua-parser.js","../node_modules/uuid/rng-browser.js","../node_modules/uuid/uuid.js","../node_modules/wildemitter/wildemitter.js"],"names":["noop","error","logger","trackStop","track","stop","streamStop","stream","getTracks","forEach","bufferizeCandidates","pc","onerror","candidatesQueue","addEventListener","this","signalingState","length","entry","shift","addIceCandidate","candidate","callback","Error","remoteDescription","push","removeFIDFromOffer","sdp","n","indexOf","slice","getSimulcastInfo","videoStream","videoTracks","getVideoTracks","warn","lines","id","join","WebRtcPeer","mode","options","setRemoteVideo","remoteVideo","pause","getRemoteStreams","srcObject","debug","load","mangleSdpToAddSimulcast","answer","simulcast","browser","name","RTCSessionDescription","type","start","localVideo","self","showLocalVideo","addStream","audioStream","parser","getBrowser","major","getMedia","constraints","undefined","MEDIA_CONSTRAINTS","navigator","mediaDevices","getUserMedia","then","catch","super_","call","Function","bind","dataChannel","mediaConstraints","connectionConstraints","peerConnection","sendSource","dataChannelConfig","useDataChannels","dataChannels","guid","uuid","v4","configuration","recursive","iceServers","freeice","onicecandidate","on","oncandidategatheringdone","multistream","interop","sdpTranslator","Interop","candidatesQueueOut","candidategatheringdone","Object","defineProperties","get","value","writable","currentFrame","readyState","HAVE_CURRENT_DATA","canvas","document","createElement","width","videoWidth","height","videoHeight","getContext","drawImage","RTCPeerConnection","dcId","dcOptions","createDataChannel","onopen","onclose","onmessage","onbufferedamountlow","event","EventEmitter","listenerCount","cand","usePlanB","candidateToUnifiedPlan","emit","onaddstream","onnegotiationneeded","listener","iceCandidate","candidateToPlanB","RTCIceCandidate","generateOffer","offerAudio","offerVideo","audio","video","browserDependantConstraints","offerToReceiveAudio","offerToReceiveVideo","JSON","stringify","createOffer","offer","setLocalDescription","localDescription","toUnifiedPlan","dumpSDP","processAnswer","getLocalSessionDescriptor","getRemoteSessionDescriptor","muted","send","data","sdpAnswer","planBAnswer","toPlanB","setRemoteDescription","processOffer","sdpOffer","planBOffer","createAnswer","setTimeout","getScreenConstraints","constraints_","unshift","apply","removeAllListeners","window","cancelChooseDesktopMedia","createEnableDescriptor","method","enumerable","streams","getLocalStreams","i","tracks","j","enabled","set","trackSetEnable","WebRtcPeerRecvonly","WebRtcPeerSendonly","WebRtcPeerSendrecv","harkUtils","hark","require","inherits","UAParser","Logger","console","framerate","ua","userAgent","description","prototype","audioEnabled","videoEnabled","getLocalStream","index","getRemoteStream","dispose","dc","close","err","exports","module","_events","_maxListeners","isFunction","arg","isNumber","isObject","isUndefined","defaultMaxListeners","setMaxListeners","isNaN","TypeError","er","handler","len","args","listeners","arguments","context","Array","addListener","m","newListener","warned","trace","once","g","removeListener","fired","list","position","splice","key","evlistener","emitter","normalice","opts","getServers","count","idx","out","input","concat","servers","Math","random","map","url","String","selected","stun","turn","stunCount","turnCount","getMaxVolume","analyser","fftBins","maxVolume","Infinity","getFloatFrequencyData","ii","WildEmitter","audioContextType","AudioContext","webkitAudioContext","audioContext","harker","smoothing","interval","threshold","play","history","running","sourceNode","createAnalyser","fftSize","smoothingTimeConstant","Float32Array","jquery","HTMLAudioElement","HTMLVideoElement","createMediaElementSource","createMediaStreamSource","connect","destination","speaking","setThreshold","t","setInterval","speakingHistory","looper","currentVolume","create","ctor","superCtor","constructor","configurable","TempCtor","isNode","merge_recursive","base","extend","typeOf","merge","clone","argv","result","size","item","sitem","Public","toString","toLowerCase","output","protocols","protocol","parts","trim","split","username","credential","urls","grammar","v","reg","o","names","format","s","u","e","p","z","r","c","b","a","rate","address","subtype","config","sessionConfig","str","raddr","tcptype","generation","keys","obj","writer","write","parse","parseFmtpConfig","parsePayloads","parseRemoteCandidates","toIntIfInt","Number","attachProperties","match","location","rawName","parseReg","content","needsBlank","keyLocation","validLine","RegExp","test","session","media","filter","l","rtp","fmtp","fmtpReducer","acc","expr","reduce","candidates","component","ip","port","formatRegExp","formatStr","replace","x","makeLine","defaultOuterOrder","defaultInnerOrder","version","mLine","payloads","outerOrder","innerOrder","el","arrayEquals","array","cache","mlB2UMap","mlU2BMap","addSetupAttr","uLine","setup","transform","sdpMLineIndex","sdpMid","getFirstSendingIndexFromAnswer","isArray","direction","desc","every","mid","rewrite","codec","type2bl","types","rtcpMux","sources","ssrc","msid","ssrcGroups","bundleOnly","groups","some","group","mids","msidSemantic","semantic","token","resStr","hasBundle","sort","mustBeBundle","cached","iceUfrag","icePwd","fingerprint","recvonlySsrcs","mid2ul","bIdx","uIdx","sources2ul","rtcpFb","bLine","hash","ssrc2group","ssrcGroup","ssrcs","ssrc2ml","related","done","source","attribute","MODEL","NAME","TYPE","VENDOR","VERSION","MOBILE","TABLET","util","regexes","extensions","margedRegexes","has","str1","str2","lowerize","mapper","rgx","arrays","k","q","matches","regex","props","exec","maps","oldsafari","1.0","1.2","1.3","2.0","2.0.2","2.0.3","2.0.4","?","device","amazon","model","Fire Phone","sprint","Evo Shift 4G","vendor","HTC","Sprint","os","windows","ME","NT 3.11","NT 4.0","2000","XP","Vista","7","8","8.1","10","RT","cpu","engine","uastring","getResult","rgxmap","getCPU","architecture","getDevice","getEngine","getOS","getUA","setUA","BROWSER","MAJOR","CPU","ARCHITECTURE","DEVICE","CONSOLE","SMARTTV","WEARABLE","EMBEDDED","ENGINE","OS","define","amd","$","jQuery","Zepto","prop","rng","crypto","global","msCrypto","getRandomValues","_rnds8","Uint8Array","_rnds","buf","offset","oct","_hexToByte","unparse","bth","_byteToHex","v1","clockseq","_clockseq","msecs","Date","getTime","nsecs","_lastNSecs","dt","_lastMSecs","tl","tmh","node","_nodeId","rnds","_rng","substr","_seedBytes","mixin","isWildEmitter","groupName","fn","callbacks","hasGroup","func","_groupName","off","releaseGroup","handlers","specialCallbacks","getWildcardCallbacks","eventName"],"mappings":"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","file":"bundle.js","sourcesContent":["(function(){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 0) {\n return sdp.slice(0, n);\n } else {\n return sdp;\n }\n}\nfunction getSimulcastInfo(videoStream) {\n var videoTracks = videoStream.getVideoTracks();\n if (!videoTracks.length) {\n logger.warn('No video tracks available in the video stream');\n return '';\n }\n var lines = [\n 'a=x-google-flag:conference',\n 'a=ssrc-group:SIM 1 2 3',\n 'a=ssrc:1 cname:localVideo',\n 'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,\n 'a=ssrc:1 mslabel:' + videoStream.id,\n 'a=ssrc:1 label:' + videoTracks[0].id,\n 'a=ssrc:2 cname:localVideo',\n 'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,\n 'a=ssrc:2 mslabel:' + videoStream.id,\n 'a=ssrc:2 label:' + videoTracks[0].id,\n 'a=ssrc:3 cname:localVideo',\n 'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,\n 'a=ssrc:3 mslabel:' + videoStream.id,\n 'a=ssrc:3 label:' + videoTracks[0].id\n ];\n lines.push('');\n return lines.join('\\n');\n}\nfunction WebRtcPeer(mode, options, callback) {\n if (!(this instanceof WebRtcPeer)) {\n return new WebRtcPeer(mode, options, callback);\n }\n WebRtcPeer.super_.call(this);\n if (options instanceof Function) {\n callback = options;\n options = undefined;\n }\n options = options || {};\n callback = (callback || noop).bind(this);\n var self = this;\n var localVideo = options.localVideo;\n var remoteVideo = options.remoteVideo;\n var videoStream = options.videoStream;\n var audioStream = options.audioStream;\n var mediaConstraints = options.mediaConstraints;\n var connectionConstraints = options.connectionConstraints;\n var pc = options.peerConnection;\n var sendSource = options.sendSource || 'webcam';\n var dataChannelConfig = options.dataChannelConfig;\n var useDataChannels = options.dataChannels || false;\n var dataChannel;\n var guid = uuid.v4();\n var configuration = recursive({ iceServers: freeice() }, options.configuration);\n var onicecandidate = options.onicecandidate;\n if (onicecandidate)\n this.on('icecandidate', onicecandidate);\n var oncandidategatheringdone = options.oncandidategatheringdone;\n if (oncandidategatheringdone) {\n this.on('candidategatheringdone', oncandidategatheringdone);\n }\n var simulcast = options.simulcast;\n var multistream = options.multistream;\n var interop = new sdpTranslator.Interop();\n var candidatesQueueOut = [];\n var candidategatheringdone = false;\n Object.defineProperties(this, {\n 'peerConnection': {\n get: function () {\n return pc;\n }\n },\n 'id': {\n value: options.id || guid,\n writable: false\n },\n 'remoteVideo': {\n get: function () {\n return remoteVideo;\n }\n },\n 'localVideo': {\n get: function () {\n return localVideo;\n }\n },\n 'dataChannel': {\n get: function () {\n return dataChannel;\n }\n },\n 'currentFrame': {\n get: function () {\n if (!remoteVideo)\n return;\n if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)\n throw new Error('No video stream data available');\n var canvas = document.createElement('canvas');\n canvas.width = remoteVideo.videoWidth;\n canvas.height = remoteVideo.videoHeight;\n canvas.getContext('2d').drawImage(remoteVideo, 0, 0);\n return canvas;\n }\n }\n });\n if (!pc) {\n pc = new RTCPeerConnection(configuration);\n if (useDataChannels && !dataChannel) {\n var dcId = 'WebRtcPeer-' + self.id;\n var dcOptions = undefined;\n if (dataChannelConfig) {\n dcId = dataChannelConfig.id || dcId;\n dcOptions = dataChannelConfig.options;\n }\n dataChannel = pc.createDataChannel(dcId, dcOptions);\n if (dataChannelConfig) {\n dataChannel.onopen = dataChannelConfig.onopen;\n dataChannel.onclose = dataChannelConfig.onclose;\n dataChannel.onmessage = dataChannelConfig.onmessage;\n dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;\n dataChannel.onerror = dataChannelConfig.onerror || noop;\n }\n }\n }\n pc.addEventListener('icecandidate', function (event) {\n var candidate = event.candidate;\n if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) {\n if (candidate) {\n var cand;\n if (multistream && usePlanB) {\n cand = interop.candidateToUnifiedPlan(candidate);\n } else {\n cand = candidate;\n }\n self.emit('icecandidate', cand);\n candidategatheringdone = false;\n } else if (!candidategatheringdone) {\n self.emit('candidategatheringdone');\n candidategatheringdone = true;\n }\n } else if (!candidategatheringdone) {\n candidatesQueueOut.push(candidate);\n if (!candidate)\n candidategatheringdone = true;\n }\n });\n pc.onaddstream = options.onaddstream;\n pc.onnegotiationneeded = options.onnegotiationneeded;\n this.on('newListener', function (event, listener) {\n if (event === 'icecandidate' || event === 'candidategatheringdone') {\n while (candidatesQueueOut.length) {\n var candidate = candidatesQueueOut.shift();\n if (!candidate === (event === 'candidategatheringdone')) {\n listener(candidate);\n }\n }\n }\n });\n var addIceCandidate = bufferizeCandidates(pc);\n this.addIceCandidate = function (iceCandidate, callback) {\n var candidate;\n if (multistream && usePlanB) {\n candidate = interop.candidateToPlanB(iceCandidate);\n } else {\n candidate = new RTCIceCandidate(iceCandidate);\n }\n logger.debug('Remote ICE candidate received', iceCandidate);\n callback = (callback || noop).bind(this);\n addIceCandidate(candidate, callback);\n };\n this.generateOffer = function (callback) {\n callback = callback.bind(this);\n var offerAudio = true;\n var offerVideo = true;\n if (mediaConstraints) {\n offerAudio = typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true;\n offerVideo = typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true;\n }\n var browserDependantConstraints = {\n offerToReceiveAudio: mode !== 'sendonly' && offerAudio,\n offerToReceiveVideo: mode !== 'sendonly' && offerVideo\n };\n var constraints = browserDependantConstraints;\n logger.debug('constraints: ' + JSON.stringify(constraints));\n pc.createOffer(constraints).then(function (offer) {\n logger.debug('Created SDP offer');\n offer = mangleSdpToAddSimulcast(offer);\n return pc.setLocalDescription(offer);\n }).then(function () {\n var localDescription = pc.localDescription;\n logger.debug('Local description set', localDescription.sdp);\n if (multistream && usePlanB) {\n localDescription = interop.toUnifiedPlan(localDescription);\n logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));\n }\n callback(null, localDescription.sdp, self.processAnswer.bind(self));\n }).catch(callback);\n };\n this.getLocalSessionDescriptor = function () {\n return pc.localDescription;\n };\n this.getRemoteSessionDescriptor = function () {\n return pc.remoteDescription;\n };\n function setRemoteVideo() {\n if (remoteVideo) {\n remoteVideo.pause();\n var stream = pc.getRemoteStreams()[0];\n remoteVideo.srcObject = stream;\n logger.debug('Remote stream:', stream);\n remoteVideo.load();\n }\n }\n this.showLocalVideo = function () {\n localVideo.srcObject = videoStream;\n localVideo.muted = true;\n };\n this.send = function (data) {\n if (dataChannel && dataChannel.readyState === 'open') {\n dataChannel.send(data);\n } else {\n logger.warn('Trying to send data over a non-existing or closed data channel');\n }\n };\n this.processAnswer = function (sdpAnswer, callback) {\n callback = (callback || noop).bind(this);\n var answer = new RTCSessionDescription({\n type: 'answer',\n sdp: sdpAnswer\n });\n if (multistream && usePlanB) {\n var planBAnswer = interop.toPlanB(answer);\n logger.debug('asnwer::planB', dumpSDP(planBAnswer));\n answer = planBAnswer;\n }\n logger.debug('SDP answer received, setting remote description');\n if (pc.signalingState === 'closed') {\n return callback('PeerConnection is closed');\n }\n pc.setRemoteDescription(answer, function () {\n setRemoteVideo();\n callback();\n }, callback);\n };\n this.processOffer = function (sdpOffer, callback) {\n callback = callback.bind(this);\n var offer = new RTCSessionDescription({\n type: 'offer',\n sdp: sdpOffer\n });\n if (multistream && usePlanB) {\n var planBOffer = interop.toPlanB(offer);\n logger.debug('offer::planB', dumpSDP(planBOffer));\n offer = planBOffer;\n }\n logger.debug('SDP offer received, setting remote description');\n if (pc.signalingState === 'closed') {\n return callback('PeerConnection is closed');\n }\n pc.setRemoteDescription(offer).then(function () {\n return setRemoteVideo();\n }).then(function () {\n return pc.createAnswer();\n }).then(function (answer) {\n answer = mangleSdpToAddSimulcast(answer);\n logger.debug('Created SDP answer');\n return pc.setLocalDescription(answer);\n }).then(function () {\n var localDescription = pc.localDescription;\n if (multistream && usePlanB) {\n localDescription = interop.toUnifiedPlan(localDescription);\n logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription));\n }\n logger.debug('Local description set', localDescription.sdp);\n callback(null, localDescription.sdp);\n }).catch(callback);\n };\n function mangleSdpToAddSimulcast(answer) {\n if (simulcast) {\n if (browser.name === 'Chrome' || browser.name === 'Chromium') {\n logger.debug('Adding multicast info');\n answer = new RTCSessionDescription({\n 'type': answer.type,\n 'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream)\n });\n } else {\n logger.warn('Simulcast is only available in Chrome browser.');\n }\n }\n return answer;\n }\n function start() {\n if (pc.signalingState === 'closed') {\n callback('The peer connection object is in \"closed\" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');\n }\n if (videoStream && localVideo) {\n self.showLocalVideo();\n }\n if (videoStream) {\n pc.addStream(videoStream);\n }\n if (audioStream) {\n pc.addStream(audioStream);\n }\n var browser = parser.getBrowser();\n if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) {\n mode = 'sendrecv';\n }\n callback();\n }\n if (mode !== 'recvonly' && !videoStream && !audioStream) {\n function getMedia(constraints) {\n if (constraints === undefined) {\n constraints = MEDIA_CONSTRAINTS;\n }\n navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {\n videoStream = stream;\n start();\n }).catch(callback);\n }\n if (sendSource === 'webcam') {\n getMedia(mediaConstraints);\n } else {\n getScreenConstraints(sendSource, function (error, constraints_) {\n if (error)\n return callback(error);\n constraints = [mediaConstraints];\n constraints.unshift(constraints_);\n getMedia(recursive.apply(undefined, constraints));\n }, guid);\n }\n } else {\n setTimeout(start, 0);\n }\n this.on('_dispose', function () {\n if (localVideo) {\n localVideo.pause();\n localVideo.srcObject = null;\n localVideo.load();\n localVideo.muted = false;\n }\n if (remoteVideo) {\n remoteVideo.pause();\n remoteVideo.srcObject = null;\n remoteVideo.load();\n }\n self.removeAllListeners();\n if (window.cancelChooseDesktopMedia !== undefined) {\n window.cancelChooseDesktopMedia(guid);\n }\n });\n}\ninherits(WebRtcPeer, EventEmitter);\nfunction createEnableDescriptor(type) {\n var method = 'get' + type + 'Tracks';\n return {\n enumerable: true,\n get: function () {\n if (!this.peerConnection)\n return;\n var streams = this.peerConnection.getLocalStreams();\n if (!streams.length)\n return;\n for (var i = 0, stream; stream = streams[i]; i++) {\n var tracks = stream[method]();\n for (var j = 0, track; track = tracks[j]; j++)\n if (!track.enabled)\n return false;\n }\n return true;\n },\n set: function (value) {\n function trackSetEnable(track) {\n track.enabled = value;\n }\n this.peerConnection.getLocalStreams().forEach(function (stream) {\n stream[method]().forEach(trackSetEnable);\n });\n }\n };\n}\nObject.defineProperties(WebRtcPeer.prototype, {\n 'enabled': {\n enumerable: true,\n get: function () {\n return this.audioEnabled && this.videoEnabled;\n },\n set: function (value) {\n this.audioEnabled = this.videoEnabled = value;\n }\n },\n 'audioEnabled': createEnableDescriptor('Audio'),\n 'videoEnabled': createEnableDescriptor('Video')\n});\nWebRtcPeer.prototype.getLocalStream = function (index) {\n if (this.peerConnection) {\n return this.peerConnection.getLocalStreams()[index || 0];\n }\n};\nWebRtcPeer.prototype.getRemoteStream = function (index) {\n if (this.peerConnection) {\n return this.peerConnection.getRemoteStreams()[index || 0];\n }\n};\nWebRtcPeer.prototype.dispose = function () {\n logger.debug('Disposing WebRtcPeer');\n var pc = this.peerConnection;\n var dc = this.dataChannel;\n try {\n if (dc) {\n if (dc.signalingState === 'closed')\n return;\n dc.close();\n }\n if (pc) {\n if (pc.signalingState === 'closed')\n return;\n pc.getLocalStreams().forEach(streamStop);\n pc.close();\n }\n } catch (err) {\n logger.warn('Exception disposing webrtc peer ' + err);\n }\n this.emit('_dispose');\n};\nfunction WebRtcPeerRecvonly(options, callback) {\n if (!(this instanceof WebRtcPeerRecvonly)) {\n return new WebRtcPeerRecvonly(options, callback);\n }\n WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback);\n}\ninherits(WebRtcPeerRecvonly, WebRtcPeer);\nfunction WebRtcPeerSendonly(options, callback) {\n if (!(this instanceof WebRtcPeerSendonly)) {\n return new WebRtcPeerSendonly(options, callback);\n }\n WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback);\n}\ninherits(WebRtcPeerSendonly, WebRtcPeer);\nfunction WebRtcPeerSendrecv(options, callback) {\n if (!(this instanceof WebRtcPeerSendrecv)) {\n return new WebRtcPeerSendrecv(options, callback);\n }\n WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback);\n}\ninherits(WebRtcPeerSendrecv, WebRtcPeer);\nfunction harkUtils(stream, options) {\n return hark(stream, options);\n}\nexports.bufferizeCandidates = bufferizeCandidates;\nexports.WebRtcPeerRecvonly = WebRtcPeerRecvonly;\nexports.WebRtcPeerSendonly = WebRtcPeerSendonly;\nexports.WebRtcPeerSendrecv = WebRtcPeerSendrecv;\nexports.hark = harkUtils;","if (window.addEventListener)\n module.exports = require('./index');","var WebRtcPeer = require('./WebRtcPeer');\nexports.WebRtcPeer = WebRtcPeer;","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nfunction EventEmitter() {\n this._events = this._events || {};\n this._maxListeners = this._maxListeners || undefined;\n}\nmodule.exports = EventEmitter;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nEventEmitter.defaultMaxListeners = 10;\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function(n) {\n if (!isNumber(n) || n < 0 || isNaN(n))\n throw TypeError('n must be a positive number');\n this._maxListeners = n;\n return this;\n};\n\nEventEmitter.prototype.emit = function(type) {\n var er, handler, len, args, i, listeners;\n\n if (!this._events)\n this._events = {};\n\n // If there is no 'error' event listener then throw.\n if (type === 'error') {\n if (!this._events.error ||\n (isObject(this._events.error) && !this._events.error.length)) {\n er = arguments[1];\n if (er instanceof Error) {\n throw er; // Unhandled 'error' event\n } else {\n // At least give some kind of context to the user\n var err = new Error('Uncaught, unspecified \"error\" event. (' + er + ')');\n err.context = er;\n throw err;\n }\n }\n }\n\n handler = this._events[type];\n\n if (isUndefined(handler))\n return false;\n\n if (isFunction(handler)) {\n switch (arguments.length) {\n // fast cases\n case 1:\n handler.call(this);\n break;\n case 2:\n handler.call(this, arguments[1]);\n break;\n case 3:\n handler.call(this, arguments[1], arguments[2]);\n break;\n // slower\n default:\n args = Array.prototype.slice.call(arguments, 1);\n handler.apply(this, args);\n }\n } else if (isObject(handler)) {\n args = Array.prototype.slice.call(arguments, 1);\n listeners = handler.slice();\n len = listeners.length;\n for (i = 0; i < len; i++)\n listeners[i].apply(this, args);\n }\n\n return true;\n};\n\nEventEmitter.prototype.addListener = function(type, listener) {\n var m;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events)\n this._events = {};\n\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (this._events.newListener)\n this.emit('newListener', type,\n isFunction(listener.listener) ?\n listener.listener : listener);\n\n if (!this._events[type])\n // Optimize the case of one listener. Don't need the extra array object.\n this._events[type] = listener;\n else if (isObject(this._events[type]))\n // If we've already got an array, just append.\n this._events[type].push(listener);\n else\n // Adding the second element, need to change to array.\n this._events[type] = [this._events[type], listener];\n\n // Check for listener leak\n if (isObject(this._events[type]) && !this._events[type].warned) {\n if (!isUndefined(this._maxListeners)) {\n m = this._maxListeners;\n } else {\n m = EventEmitter.defaultMaxListeners;\n }\n\n if (m && m > 0 && this._events[type].length > m) {\n this._events[type].warned = true;\n console.error('(node) warning: possible EventEmitter memory ' +\n 'leak detected. %d listeners added. ' +\n 'Use emitter.setMaxListeners() to increase limit.',\n this._events[type].length);\n if (typeof console.trace === 'function') {\n // not supported in IE 10\n console.trace();\n }\n }\n }\n\n return this;\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.once = function(type, listener) {\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n var fired = false;\n\n function g() {\n this.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n g.listener = listener;\n this.on(type, g);\n\n return this;\n};\n\n// emits a 'removeListener' event iff the listener was removed\nEventEmitter.prototype.removeListener = function(type, listener) {\n var list, position, length, i;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events || !this._events[type])\n return this;\n\n list = this._events[type];\n length = list.length;\n position = -1;\n\n if (list === listener ||\n (isFunction(list.listener) && list.listener === listener)) {\n delete this._events[type];\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n\n } else if (isObject(list)) {\n for (i = length; i-- > 0;) {\n if (list[i] === listener ||\n (list[i].listener && list[i].listener === listener)) {\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (list.length === 1) {\n list.length = 0;\n delete this._events[type];\n } else {\n list.splice(position, 1);\n }\n\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n }\n\n return this;\n};\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n var key, listeners;\n\n if (!this._events)\n return this;\n\n // not listening for removeListener, no need to emit\n if (!this._events.removeListener) {\n if (arguments.length === 0)\n this._events = {};\n else if (this._events[type])\n delete this._events[type];\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n for (key in this._events) {\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = {};\n return this;\n }\n\n listeners = this._events[type];\n\n if (isFunction(listeners)) {\n this.removeListener(type, listeners);\n } else if (listeners) {\n // LIFO order\n while (listeners.length)\n this.removeListener(type, listeners[listeners.length - 1]);\n }\n delete this._events[type];\n\n return this;\n};\n\nEventEmitter.prototype.listeners = function(type) {\n var ret;\n if (!this._events || !this._events[type])\n ret = [];\n else if (isFunction(this._events[type]))\n ret = [this._events[type]];\n else\n ret = this._events[type].slice();\n return ret;\n};\n\nEventEmitter.prototype.listenerCount = function(type) {\n if (this._events) {\n var evlistener = this._events[type];\n\n if (isFunction(evlistener))\n return 1;\n else if (evlistener)\n return evlistener.length;\n }\n return 0;\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n return emitter.listenerCount(type);\n};\n\nfunction isFunction(arg) {\n return typeof arg === 'function';\n}\n\nfunction isNumber(arg) {\n return typeof arg === 'number';\n}\n\nfunction isObject(arg) {\n return typeof arg === 'object' && arg !== null;\n}\n\nfunction isUndefined(arg) {\n return arg === void 0;\n}\n","/* jshint node: true */\n'use strict';\n\nvar normalice = require('normalice');\n\n/**\n # freeice\n\n The `freeice` module is a simple way of getting random STUN or TURN server\n for your WebRTC application. The list of servers (just STUN at this stage)\n were sourced from this [gist](https://gist.github.com/zziuni/3741933).\n\n ## Example Use\n\n The following demonstrates how you can use `freeice` with\n [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):\n\n <<< examples/quickconnect.js\n\n As the `freeice` module generates ice servers in a list compliant with the\n WebRTC spec you will be able to use it with raw `RTCPeerConnection`\n constructors and other WebRTC libraries.\n\n ## Hey, don't use my STUN/TURN server!\n\n If for some reason your free STUN or TURN server ends up in the\n list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or\n [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))\n that is used in this module, you can feel\n free to open an issue on this repository and those servers will be removed\n within 24 hours (or sooner). This is the quickest and probably the most\n polite way to have something removed (and provides us some visibility\n if someone opens a pull request requesting that a server is added).\n\n ## Please add my server!\n\n If you have a server that you wish to add to the list, that's awesome! I'm\n sure I speak on behalf of a whole pile of WebRTC developers who say thanks.\n To get it into the list, feel free to either open a pull request or if you\n find that process a bit daunting then just create an issue requesting\n the addition of the server (make sure you provide all the details, and if\n you have a Terms of Service then including that in the PR/issue would be\n awesome).\n\n ## I know of a free server, can I add it?\n\n Sure, if you do your homework and make sure it is ok to use (I'm currently\n in the process of reviewing the terms of those STUN servers included from\n the original list). If it's ok to go, then please see the previous entry\n for how to add it.\n\n ## Current List of Servers\n\n * current as at the time of last `README.md` file generation\n\n ### STUN\n\n <<< stun.json\n\n ### TURN\n\n <<< turn.json\n\n**/\n\nvar freeice = module.exports = function(opts) {\n // if a list of servers has been provided, then use it instead of defaults\n var servers = {\n stun: (opts || {}).stun || require('./stun.json'),\n turn: (opts || {}).turn || require('./turn.json')\n };\n\n var stunCount = (opts || {}).stunCount || 2;\n var turnCount = (opts || {}).turnCount || 0;\n var selected;\n\n function getServers(type, count) {\n var out = [];\n var input = [].concat(servers[type]);\n var idx;\n\n while (input.length && out.length < count) {\n idx = (Math.random() * input.length) | 0;\n out = out.concat(input.splice(idx, 1));\n }\n\n return out.map(function(url) {\n //If it's a not a string, don't try to \"normalice\" it otherwise using type:url will screw it up\n if ((typeof url !== 'string') && (! (url instanceof String))) {\n return url;\n } else {\n return normalice(type + ':' + url);\n }\n });\n }\n\n // add stun servers\n selected = [].concat(getServers('stun', stunCount));\n\n if (turnCount) {\n selected = selected.concat(getServers('turn', turnCount));\n }\n\n return selected;\n};\n","var WildEmitter = require('wildemitter');\n\nfunction getMaxVolume (analyser, fftBins) {\n var maxVolume = -Infinity;\n analyser.getFloatFrequencyData(fftBins);\n\n for(var i=4, ii=fftBins.length; i < ii; i++) {\n if (fftBins[i] > maxVolume && fftBins[i] < 0) {\n maxVolume = fftBins[i];\n }\n };\n\n return maxVolume;\n}\n\n\nvar audioContextType = window.AudioContext || window.webkitAudioContext;\n// use a single audio context due to hardware limits\nvar audioContext = null;\nmodule.exports = function(stream, options) {\n var harker = new WildEmitter();\n\n\n // make it not break in non-supported browsers\n if (!audioContextType) return harker;\n\n //Config\n var options = options || {},\n smoothing = (options.smoothing || 0.1),\n interval = (options.interval || 50),\n threshold = options.threshold,\n play = options.play,\n history = options.history || 10,\n running = true;\n\n //Setup Audio Context\n if (!audioContext) {\n audioContext = new audioContextType();\n }\n var sourceNode, fftBins, analyser;\n\n analyser = audioContext.createAnalyser();\n analyser.fftSize = 512;\n analyser.smoothingTimeConstant = smoothing;\n fftBins = new Float32Array(analyser.fftSize);\n\n if (stream.jquery) stream = stream[0];\n if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {\n //Audio Tag\n sourceNode = audioContext.createMediaElementSource(stream);\n if (typeof play === 'undefined') play = true;\n threshold = threshold || -50;\n } else {\n //WebRTC Stream\n sourceNode = audioContext.createMediaStreamSource(stream);\n threshold = threshold || -50;\n }\n\n sourceNode.connect(analyser);\n if (play) analyser.connect(audioContext.destination);\n\n harker.speaking = false;\n\n harker.setThreshold = function(t) {\n threshold = t;\n };\n\n harker.setInterval = function(i) {\n interval = i;\n };\n \n harker.stop = function() {\n running = false;\n harker.emit('volume_change', -100, threshold);\n if (harker.speaking) {\n harker.speaking = false;\n harker.emit('stopped_speaking');\n }\n };\n harker.speakingHistory = [];\n for (var i = 0; i < history; i++) {\n harker.speakingHistory.push(0);\n }\n\n // Poll the analyser node to determine if speaking\n // and emit events if changed\n var looper = function() {\n setTimeout(function() {\n \n //check if stop has been called\n if(!running) {\n return;\n }\n \n var currentVolume = getMaxVolume(analyser, fftBins);\n\n harker.emit('volume_change', currentVolume, threshold);\n\n var history = 0;\n if (currentVolume > threshold && !harker.speaking) {\n // trigger quickly, short history\n for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {\n history += harker.speakingHistory[i];\n }\n if (history >= 2) {\n harker.speaking = true;\n harker.emit('speaking');\n }\n } else if (currentVolume < threshold && harker.speaking) {\n for (var i = 0; i < harker.speakingHistory.length; i++) {\n history += harker.speakingHistory[i];\n }\n if (history == 0) {\n harker.speaking = false;\n harker.emit('stopped_speaking');\n }\n }\n harker.speakingHistory.shift();\n harker.speakingHistory.push(0 + (currentVolume > threshold));\n\n looper();\n }, interval);\n };\n looper();\n\n\n return harker;\n}\n","if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n module.exports = function inherits(ctor, superCtor) {\n ctor.super_ = superCtor\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n });\n };\n} else {\n // old school shim for old browsers\n module.exports = function inherits(ctor, superCtor) {\n ctor.super_ = superCtor\n var TempCtor = function () {}\n TempCtor.prototype = superCtor.prototype\n ctor.prototype = new TempCtor()\n ctor.prototype.constructor = ctor\n }\n}\n","/*!\r\n * @name JavaScript/NodeJS Merge v1.2.0\r\n * @author yeikos\r\n * @repository https://github.com/yeikos/js.merge\r\n\r\n * Copyright 2014 yeikos - MIT license\r\n * https://raw.github.com/yeikos/js.merge/master/LICENSE\r\n */\r\n\r\n;(function(isNode) {\r\n\r\n\t/**\r\n\t * Merge one or more objects \r\n\t * @param bool? clone\r\n\t * @param mixed,... arguments\r\n\t * @return object\r\n\t */\r\n\r\n\tvar Public = function(clone) {\r\n\r\n\t\treturn merge(clone === true, false, arguments);\r\n\r\n\t}, publicName = 'merge';\r\n\r\n\t/**\r\n\t * Merge two or more objects recursively \r\n\t * @param bool? clone\r\n\t * @param mixed,... arguments\r\n\t * @return object\r\n\t */\r\n\r\n\tPublic.recursive = function(clone) {\r\n\r\n\t\treturn merge(clone === true, true, arguments);\r\n\r\n\t};\r\n\r\n\t/**\r\n\t * Clone the input removing any reference\r\n\t * @param mixed input\r\n\t * @return mixed\r\n\t */\r\n\r\n\tPublic.clone = function(input) {\r\n\r\n\t\tvar output = input,\r\n\t\t\ttype = typeOf(input),\r\n\t\t\tindex, size;\r\n\r\n\t\tif (type === 'array') {\r\n\r\n\t\t\toutput = [];\r\n\t\t\tsize = input.length;\r\n\r\n\t\t\tfor (index=0;index 1) {\n url = parts[1];\n parts = parts[0].split(':');\n\n // add the output credential and username\n output.username = parts[0];\n output.credential = (input || {}).credential || parts[1] || '';\n }\n\n output.url = protocol + url;\n output.urls = [ output.url ];\n\n return output;\n};\n","var grammar = module.exports = {\n v: [{\n name: 'version',\n reg: /^(\\d*)$/\n }],\n o: [{ //o=- 20518 0 IN IP4 203.0.113.1\n // NB: sessionId will be a String in most cases because it is huge\n name: 'origin',\n reg: /^(\\S*) (\\d*) (\\d*) (\\S*) IP(\\d) (\\S*)/,\n names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],\n format: \"%s %s %d %s IP%d %s\"\n }],\n // default parsing of these only (though some of these feel outdated)\n s: [{ name: 'name' }],\n i: [{ name: 'description' }],\n u: [{ name: 'uri' }],\n e: [{ name: 'email' }],\n p: [{ name: 'phone' }],\n z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..\n r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly\n //k: [{}], // outdated thing ignored\n t: [{ //t=0 0\n name: 'timing',\n reg: /^(\\d*) (\\d*)/,\n names: ['start', 'stop'],\n format: \"%d %d\"\n }],\n c: [{ //c=IN IP4 10.47.197.26\n name: 'connection',\n reg: /^IN IP(\\d) (\\S*)/,\n names: ['version', 'ip'],\n format: \"IN IP%d %s\"\n }],\n b: [{ //b=AS:4000\n push: 'bandwidth',\n reg: /^(TIAS|AS|CT|RR|RS):(\\d*)/,\n names: ['type', 'limit'],\n format: \"%s:%s\"\n }],\n m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31\n // NB: special - pushes to session\n // TODO: rtp/fmtp should be filtered by the payloads found here?\n reg: /^(\\w*) (\\d*) ([\\w\\/]*)(?: (.*))?/,\n names: ['type', 'port', 'protocol', 'payloads'],\n format: \"%s %d %s %s\"\n }],\n a: [\n { //a=rtpmap:110 opus/48000/2\n push: 'rtp',\n reg: /^rtpmap:(\\d*) ([\\w\\-]*)(?:\\s*\\/(\\d*)(?:\\s*\\/(\\S*))?)?/,\n names: ['payload', 'codec', 'rate', 'encoding'],\n format: function (o) {\n return (o.encoding) ?\n \"rtpmap:%d %s/%s/%s\":\n o.rate ?\n \"rtpmap:%d %s/%s\":\n \"rtpmap:%d %s\";\n }\n },\n {\n //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000\n //a=fmtp:111 minptime=10; useinbandfec=1\n push: 'fmtp',\n reg: /^fmtp:(\\d*) ([\\S| ]*)/,\n names: ['payload', 'config'],\n format: \"fmtp:%d %s\"\n },\n { //a=control:streamid=0\n name: 'control',\n reg: /^control:(.*)/,\n format: \"control:%s\"\n },\n { //a=rtcp:65179 IN IP4 193.84.77.194\n name: 'rtcp',\n reg: /^rtcp:(\\d*)(?: (\\S*) IP(\\d) (\\S*))?/,\n names: ['port', 'netType', 'ipVer', 'address'],\n format: function (o) {\n return (o.address != null) ?\n \"rtcp:%d %s IP%d %s\":\n \"rtcp:%d\";\n }\n },\n { //a=rtcp-fb:98 trr-int 100\n push: 'rtcpFbTrrInt',\n reg: /^rtcp-fb:(\\*|\\d*) trr-int (\\d*)/,\n names: ['payload', 'value'],\n format: \"rtcp-fb:%d trr-int %d\"\n },\n { //a=rtcp-fb:98 nack rpsi\n push: 'rtcpFb',\n reg: /^rtcp-fb:(\\*|\\d*) ([\\w-_]*)(?: ([\\w-_]*))?/,\n names: ['payload', 'type', 'subtype'],\n format: function (o) {\n return (o.subtype != null) ?\n \"rtcp-fb:%s %s %s\":\n \"rtcp-fb:%s %s\";\n }\n },\n { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n //a=extmap:1/recvonly URI-gps-string\n push: 'ext',\n reg: /^extmap:([\\w_\\/]*) (\\S*)(?: (\\S*))?/,\n names: ['value', 'uri', 'config'], // value may include \"/direction\" suffix\n format: function (o) {\n return (o.config != null) ?\n \"extmap:%s %s %s\":\n \"extmap:%s %s\";\n }\n },\n {\n //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32\n push: 'crypto',\n reg: /^crypto:(\\d*) ([\\w_]*) (\\S*)(?: (\\S*))?/,\n names: ['id', 'suite', 'config', 'sessionConfig'],\n format: function (o) {\n return (o.sessionConfig != null) ?\n \"crypto:%d %s %s %s\":\n \"crypto:%d %s %s\";\n }\n },\n { //a=setup:actpass\n name: 'setup',\n reg: /^setup:(\\w*)/,\n format: \"setup:%s\"\n },\n { //a=mid:1\n name: 'mid',\n reg: /^mid:([^\\s]*)/,\n format: \"mid:%s\"\n },\n { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a\n name: 'msid',\n reg: /^msid:(.*)/,\n format: \"msid:%s\"\n },\n { //a=ptime:20\n name: 'ptime',\n reg: /^ptime:(\\d*)/,\n format: \"ptime:%d\"\n },\n { //a=maxptime:60\n name: 'maxptime',\n reg: /^maxptime:(\\d*)/,\n format: \"maxptime:%d\"\n },\n { //a=sendrecv\n name: 'direction',\n reg: /^(sendrecv|recvonly|sendonly|inactive)/\n },\n { //a=ice-lite\n name: 'icelite',\n reg: /^(ice-lite)/\n },\n { //a=ice-ufrag:F7gI\n name: 'iceUfrag',\n reg: /^ice-ufrag:(\\S*)/,\n format: \"ice-ufrag:%s\"\n },\n { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g\n name: 'icePwd',\n reg: /^ice-pwd:(\\S*)/,\n format: \"ice-pwd:%s\"\n },\n { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33\n name: 'fingerprint',\n reg: /^fingerprint:(\\S*) (\\S*)/,\n names: ['type', 'hash'],\n format: \"fingerprint:%s %s\"\n },\n {\n //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\n //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0\n //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0\n //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0\n //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0\n push:'candidates',\n reg: /^candidate:(\\S*) (\\d*) (\\S*) (\\d*) (\\S*) (\\d*) typ (\\S*)(?: raddr (\\S*) rport (\\d*))?(?: tcptype (\\S*))?(?: generation (\\d*))?/,\n names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'],\n format: function (o) {\n var str = \"candidate:%s %d %s %d %s %d typ %s\";\n\n str += (o.raddr != null) ? \" raddr %s rport %d\" : \"%v%v\";\n\n // NB: candidate has three optional chunks, so %void middles one if it's missing\n str += (o.tcptype != null) ? \" tcptype %s\" : \"%v\";\n\n if (o.generation != null) {\n str += \" generation %d\";\n }\n return str;\n }\n },\n { //a=end-of-candidates (keep after the candidates line for readability)\n name: 'endOfCandidates',\n reg: /^(end-of-candidates)/\n },\n { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...\n name: 'remoteCandidates',\n reg: /^remote-candidates:(.*)/,\n format: \"remote-candidates:%s\"\n },\n { //a=ice-options:google-ice\n name: 'iceOptions',\n reg: /^ice-options:(\\S*)/,\n format: \"ice-options:%s\"\n },\n { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1\n push: \"ssrcs\",\n reg: /^ssrc:(\\d*) ([\\w_]*):(.*)/,\n names: ['id', 'attribute', 'value'],\n format: \"ssrc:%d %s:%s\"\n },\n { //a=ssrc-group:FEC 1 2\n push: \"ssrcGroups\",\n reg: /^ssrc-group:(\\w*) (.*)/,\n names: ['semantics', 'ssrcs'],\n format: \"ssrc-group:%s %s\"\n },\n { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV\n name: \"msidSemantic\",\n reg: /^msid-semantic:\\s?(\\w*) (\\S*)/,\n names: ['semantic', 'token'],\n format: \"msid-semantic: %s %s\" // space after \":\" is not accidental\n },\n { //a=group:BUNDLE audio video\n push: 'groups',\n reg: /^group:(\\w*) (.*)/,\n names: ['type', 'mids'],\n format: \"group:%s %s\"\n },\n { //a=rtcp-mux\n name: 'rtcpMux',\n reg: /^(rtcp-mux)/\n },\n { //a=rtcp-rsize\n name: 'rtcpRsize',\n reg: /^(rtcp-rsize)/\n },\n { // any a= that we don't understand is kepts verbatim on media.invalid\n push: 'invalid',\n names: [\"value\"]\n }\n ]\n};\n\n// set sensible defaults to avoid polluting the grammar with boring details\nObject.keys(grammar).forEach(function (key) {\n var objs = grammar[key];\n objs.forEach(function (obj) {\n if (!obj.reg) {\n obj.reg = /(.*)/;\n }\n if (!obj.format) {\n obj.format = \"%s\";\n }\n });\n});\n","var parser = require('./parser');\nvar writer = require('./writer');\n\nexports.write = writer;\nexports.parse = parser.parse;\nexports.parseFmtpConfig = parser.parseFmtpConfig;\nexports.parsePayloads = parser.parsePayloads;\nexports.parseRemoteCandidates = parser.parseRemoteCandidates;\n","var toIntIfInt = function (v) {\n return String(Number(v)) === v ? Number(v) : v;\n};\n\nvar attachProperties = function (match, location, names, rawName) {\n if (rawName && !names) {\n location[rawName] = toIntIfInt(match[1]);\n }\n else {\n for (var i = 0; i < names.length; i += 1) {\n if (match[i+1] != null) {\n location[names[i]] = toIntIfInt(match[i+1]);\n }\n }\n }\n};\n\nvar parseReg = function (obj, location, content) {\n var needsBlank = obj.name && obj.names;\n if (obj.push && !location[obj.push]) {\n location[obj.push] = [];\n }\n else if (needsBlank && !location[obj.name]) {\n location[obj.name] = {};\n }\n var keyLocation = obj.push ?\n {} : // blank object that will be pushed\n needsBlank ? location[obj.name] : location; // otherwise, named location or root\n\n attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);\n\n if (obj.push) {\n location[obj.push].push(keyLocation);\n }\n};\n\nvar grammar = require('./grammar');\nvar validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);\n\nexports.parse = function (sdp) {\n var session = {}\n , media = []\n , location = session; // points at where properties go under (one of the above)\n\n // parse lines we understand\n sdp.split(/(\\r\\n|\\r|\\n)/).filter(validLine).forEach(function (l) {\n var type = l[0];\n var content = l.slice(2);\n if (type === 'm') {\n media.push({rtp: [], fmtp: []});\n location = media[media.length-1]; // point at latest media line\n }\n\n for (var j = 0; j < (grammar[type] || []).length; j += 1) {\n var obj = grammar[type][j];\n if (obj.reg.test(content)) {\n return parseReg(obj, location, content);\n }\n }\n });\n\n session.media = media; // link it up\n return session;\n};\n\nvar fmtpReducer = function (acc, expr) {\n var s = expr.split('=');\n if (s.length === 2) {\n acc[s[0]] = toIntIfInt(s[1]);\n }\n return acc;\n};\n\nexports.parseFmtpConfig = function (str) {\n return str.split(/\\;\\s?/).reduce(fmtpReducer, {});\n};\n\nexports.parsePayloads = function (str) {\n return str.split(' ').map(Number);\n};\n\nexports.parseRemoteCandidates = function (str) {\n var candidates = [];\n var parts = str.split(' ').map(toIntIfInt);\n for (var i = 0; i < parts.length; i += 3) {\n candidates.push({\n component: parts[i],\n ip: parts[i + 1],\n port: parts[i + 2]\n });\n }\n return candidates;\n};\n","var grammar = require('./grammar');\n\n// customized util.format - discards excess arguments and can void middle ones\nvar formatRegExp = /%[sdv%]/g;\nvar format = function (formatStr) {\n var i = 1;\n var args = arguments;\n var len = args.length;\n return formatStr.replace(formatRegExp, function (x) {\n if (i >= len) {\n return x; // missing argument\n }\n var arg = args[i];\n i += 1;\n switch (x) {\n case '%%':\n return '%';\n case '%s':\n return String(arg);\n case '%d':\n return Number(arg);\n case '%v':\n return '';\n }\n });\n // NB: we discard excess arguments - they are typically undefined from makeLine\n};\n\nvar makeLine = function (type, obj, location) {\n var str = obj.format instanceof Function ?\n (obj.format(obj.push ? location : location[obj.name])) :\n obj.format;\n\n var args = [type + '=' + str];\n if (obj.names) {\n for (var i = 0; i < obj.names.length; i += 1) {\n var n = obj.names[i];\n if (obj.name) {\n args.push(location[obj.name][n]);\n }\n else { // for mLine and push attributes\n args.push(location[obj.names[i]]);\n }\n }\n }\n else {\n args.push(location[obj.name]);\n }\n return format.apply(null, args);\n};\n\n// RFC specified order\n// TODO: extend this with all the rest\nvar defaultOuterOrder = [\n 'v', 'o', 's', 'i',\n 'u', 'e', 'p', 'c',\n 'b', 't', 'r', 'z', 'a'\n];\nvar defaultInnerOrder = ['i', 'c', 'b', 'a'];\n\n\nmodule.exports = function (session, opts) {\n opts = opts || {};\n // ensure certain properties exist\n if (session.version == null) {\n session.version = 0; // \"v=0\" must be there (only defined version atm)\n }\n if (session.name == null) {\n session.name = \" \"; // \"s= \" must be there if no meaningful name set\n }\n session.media.forEach(function (mLine) {\n if (mLine.payloads == null) {\n mLine.payloads = \"\";\n }\n });\n\n var outerOrder = opts.outerOrder || defaultOuterOrder;\n var innerOrder = opts.innerOrder || defaultInnerOrder;\n var sdp = [];\n\n // loop through outerOrder for matching properties on session\n outerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in session && session[obj.name] != null) {\n sdp.push(makeLine(type, obj, session));\n }\n else if (obj.push in session && session[obj.push] != null) {\n session[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n\n // then for each media line, follow the innerOrder\n session.media.forEach(function (mLine) {\n sdp.push(makeLine('m', grammar.m[0], mLine));\n\n innerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in mLine && mLine[obj.name] != null) {\n sdp.push(makeLine(type, obj, mLine));\n }\n else if (obj.push in mLine && mLine[obj.push] != null) {\n mLine[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n });\n\n return sdp.join('\\r\\n') + '\\r\\n';\n};\n","/* Copyright @ 2015 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nmodule.exports = function arrayEquals(array) {\n // if the other array is a falsy value, return\n if (!array)\n return false;\n\n // compare lengths - can save a lot of time\n if (this.length != array.length)\n return false;\n\n for (var i = 0, l = this.length; i < l; i++) {\n // Check if we have nested arrays\n if (this[i] instanceof Array && array[i] instanceof Array) {\n // recurse into the nested arrays\n if (!arrayEquals.apply(this[i], [array[i]]))\n return false;\n } else if (this[i] != array[i]) {\n // Warning - two different object instances will never be equal:\n // {x:20} != {x:20}\n return false;\n }\n }\n return true;\n};\n\n","/* Copyright @ 2015 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexports.Interop = require('./interop');\n","/* Copyright @ 2015 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* global RTCSessionDescription */\n/* global RTCIceCandidate */\n/* jshint -W097 */\n\"use strict\";\n\nvar transform = require('./transform');\nvar arrayEquals = require('./array-equals');\n\nfunction Interop() {\n\n /**\n * This map holds the most recent Unified Plan offer/answer SDP that was\n * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and\n * the SDP string as values.\n *\n * @type {{}}\n */\n this.cache = {\n mlB2UMap : {},\n mlU2BMap : {}\n };\n}\n\nmodule.exports = Interop;\n\n/**\n * Changes the candidate args to match with the related Unified Plan\n */\nInterop.prototype.candidateToUnifiedPlan = function(candidate) {\n var cand = new RTCIceCandidate(candidate);\n\n cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex];\n /* TODO: change sdpMid to (audio|video)-SSRC */\n\n return cand;\n};\n\n/**\n * Changes the candidate args to match with the related Plan B\n */\nInterop.prototype.candidateToPlanB = function(candidate) {\n var cand = new RTCIceCandidate(candidate);\n\n if (cand.sdpMid.indexOf('audio') === 0) {\n cand.sdpMid = 'audio';\n } else if (cand.sdpMid.indexOf('video') === 0) {\n cand.sdpMid = 'video';\n } else {\n throw new Error('candidate with ' + cand.sdpMid + ' not allowed');\n }\n\n cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex];\n\n return cand;\n};\n\n/**\n * Returns the index of the first m-line with the given media type and with a\n * direction which allows sending, in the last Unified Plan description with\n * type \"answer\" converted to Plan B. Returns {null} if there is no saved\n * answer, or if none of its m-lines with the given type allow sending.\n * @param type the media type (\"audio\" or \"video\").\n * @returns {*}\n */\nInterop.prototype.getFirstSendingIndexFromAnswer = function(type) {\n if (!this.cache.answer) {\n return null;\n }\n\n var session = transform.parse(this.cache.answer);\n if (session && session.media && Array.isArray(session.media)){\n for (var i = 0; i < session.media.length; i++) {\n if (session.media[i].type == type &&\n (!session.media[i].direction /* default to sendrecv */ ||\n session.media[i].direction === 'sendrecv' ||\n session.media[i].direction === 'sendonly')){\n return i;\n }\n }\n }\n\n return null;\n};\n\n/**\n * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A\n * PeerConnection wrapper transforms the SDP to Plan B before passing it to the\n * application.\n *\n * @param desc\n * @returns {*}\n */\nInterop.prototype.toPlanB = function(desc) {\n var self = this;\n //#region Preliminary input validation.\n\n if (typeof desc !== 'object' || desc === null ||\n typeof desc.sdp !== 'string') {\n console.warn('An empty description was passed as an argument.');\n return desc;\n }\n\n // Objectify the SDP for easier manipulation.\n var session = transform.parse(desc.sdp);\n\n // If the SDP contains no media, there's nothing to transform.\n if (typeof session.media === 'undefined' ||\n !Array.isArray(session.media) || session.media.length === 0) {\n console.warn('The description has no media.');\n return desc;\n }\n\n // Try some heuristics to \"make sure\" this is a Unified Plan SDP. Plan B\n // SDP has a video, an audio and a data \"channel\" at most.\n if (session.media.length <= 3 && session.media.every(function(m) {\n return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;\n })) {\n console.warn('This description does not look like Unified Plan.');\n return desc;\n }\n\n //#endregion\n\n // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443\n var sdp = desc.sdp;\n var rewrite = false;\n for (var i = 0; i < session.media.length; i++) {\n var uLine = session.media[i];\n uLine.rtp.forEach(function(rtp) {\n if (rtp.codec === 'NULL')\n {\n rewrite = true;\n var offer = transform.parse(self.cache.offer);\n rtp.codec = offer.media[i].rtp[0].codec;\n }\n });\n }\n if (rewrite) {\n sdp = transform.write(session);\n }\n\n // Unified Plan SDP is our \"precious\". Cache it for later use in the Plan B\n // -> Unified Plan transformation.\n this.cache[desc.type] = sdp;\n\n //#region Convert from Unified Plan to Plan B.\n\n // We rebuild the session.media array.\n var media = session.media;\n session.media = [];\n\n // Associative array that maps channel types to channel objects for fast\n // access to channel objects by their type, e.g. type2bl['audio']->channel\n // obj.\n var type2bl = {};\n\n // Used to build the group:BUNDLE value after the channels construction\n // loop.\n var types = [];\n\n media.forEach(function(uLine) {\n // rtcp-mux is required in the Plan B SDP.\n if ((typeof uLine.rtcpMux !== 'string' ||\n uLine.rtcpMux !== 'rtcp-mux') &&\n uLine.direction !== 'inactive') {\n throw new Error('Cannot convert to Plan B because m-lines ' +\n 'without the rtcp-mux attribute were found.');\n }\n\n // If we don't have a channel for this uLine.type OR the selected is\n // inactive, then select this uLine as the channel basis.\n if (typeof type2bl[uLine.type] === 'undefined' ||\n type2bl[uLine.type].direction === 'inactive') {\n type2bl[uLine.type] = uLine;\n }\n\n if (uLine.protocol != type2bl[uLine.type].protocol) {\n throw new Error('Cannot convert to Plan B because m-lines ' +\n 'have different protocols and this library does not have ' +\n 'support for that');\n }\n\n if (uLine.payloads != type2bl[uLine.type].payloads) {\n throw new Error('Cannot convert to Plan B because m-lines ' +\n 'have different payloads and this library does not have ' +\n 'support for that');\n }\n\n });\n\n // Implode the Unified Plan m-lines/tracks into Plan B channels.\n media.forEach(function(uLine) {\n if (uLine.type === 'application') {\n session.media.push(uLine);\n types.push(uLine.mid);\n return;\n }\n\n // Add sources to the channel and handle a=msid.\n if (typeof uLine.sources === 'object') {\n Object.keys(uLine.sources).forEach(function(ssrc) {\n if (typeof type2bl[uLine.type].sources !== 'object')\n type2bl[uLine.type].sources = {};\n\n // Assign the sources to the channel.\n type2bl[uLine.type].sources[ssrc] =\n uLine.sources[ssrc];\n\n if (typeof uLine.msid !== 'undefined') {\n // In Plan B the msid is an SSRC attribute. Also, we don't\n // care about the obsolete label and mslabel attributes.\n //\n // Note that it is not guaranteed that the uLine will\n // have an msid. recvonly channels in particular don't have\n // one.\n type2bl[uLine.type].sources[ssrc].msid =\n uLine.msid;\n }\n // NOTE ssrcs in ssrc groups will share msids, as\n // draft-uberti-rtcweb-plan-00 mandates.\n });\n }\n\n // Add ssrc groups to the channel.\n if (typeof uLine.ssrcGroups !== 'undefined' &&\n Array.isArray(uLine.ssrcGroups)) {\n\n // Create the ssrcGroups array, if it's not defined.\n if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' ||\n !Array.isArray(type2bl[uLine.type].ssrcGroups)) {\n type2bl[uLine.type].ssrcGroups = [];\n }\n\n type2bl[uLine.type].ssrcGroups =\n type2bl[uLine.type].ssrcGroups.concat(\n uLine.ssrcGroups);\n }\n\n if (type2bl[uLine.type] === uLine) {\n // Plan B mids are in ['audio', 'video', 'data']\n uLine.mid = uLine.type;\n\n // Plan B doesn't support/need the bundle-only attribute.\n delete uLine.bundleOnly;\n\n // In Plan B the msid is an SSRC attribute.\n delete uLine.msid;\n\n\t if (uLine.type == media[0].type) {\n\t types.unshift(uLine.type);\n\t // Add the channel to the new media array.\n\t session.media.unshift(uLine);\n\t } else {\n\t types.push(uLine.type);\n\t // Add the channel to the new media array.\n\t session.media.push(uLine);\n\t }\n }\n });\n\n if (typeof session.groups !== 'undefined') {\n // We regenerate the BUNDLE group with the new mids.\n session.groups.some(function(group) {\n\t if (group.type === 'BUNDLE') {\n\t group.mids = types.join(' ');\n\t return true;\n\t }\n });\n }\n\n // msid semantic\n session.msidSemantic = {\n semantic: 'WMS',\n token: '*'\n };\n\n var resStr = transform.write(session);\n\n return new RTCSessionDescription({\n type: desc.type,\n sdp: resStr\n });\n\n //#endregion\n};\n\n/* follow rules defined in RFC4145 */\nfunction addSetupAttr(uLine) {\n if (typeof uLine.setup === 'undefined') {\n return;\n }\n\n if (uLine.setup === \"active\") {\n uLine.setup = \"passive\";\n } else if (uLine.setup === \"passive\") {\n uLine.setup = \"active\";\n }\n}\n\n/**\n * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A\n * PeerConnection wrapper transforms the SDP to Unified Plan before passing it\n * to FF.\n *\n * @param desc\n * @returns {*}\n */\nInterop.prototype.toUnifiedPlan = function(desc) {\n var self = this;\n //#region Preliminary input validation.\n\n if (typeof desc !== 'object' || desc === null ||\n typeof desc.sdp !== 'string') {\n console.warn('An empty description was passed as an argument.');\n return desc;\n }\n\n var session = transform.parse(desc.sdp);\n\n // If the SDP contains no media, there's nothing to transform.\n if (typeof session.media === 'undefined' ||\n !Array.isArray(session.media) || session.media.length === 0) {\n console.warn('The description has no media.');\n return desc;\n }\n\n // Try some heuristics to \"make sure\" this is a Plan B SDP. Plan B SDP has\n // a video, an audio and a data \"channel\" at most.\n if (session.media.length > 3 || !session.media.every(function(m) {\n return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;\n })) {\n console.warn('This description does not look like Plan B.');\n return desc;\n }\n\n // Make sure this Plan B SDP can be converted to a Unified Plan SDP.\n var mids = [];\n session.media.forEach(function(m) {\n mids.push(m.mid);\n });\n\n var hasBundle = false;\n if (typeof session.groups !== 'undefined' &&\n Array.isArray(session.groups)) {\n hasBundle = session.groups.every(function(g) {\n return g.type !== 'BUNDLE' ||\n arrayEquals.apply(g.mids.sort(), [mids.sort()]);\n });\n }\n\n if (!hasBundle) {\n var mustBeBundle = false;\n\n session.media.forEach(function(m) {\n if (m.direction !== 'inactive') {\n mustBeBundle = true;\n }\n });\n\n if (mustBeBundle) {\n throw new Error(\"Cannot convert to Unified Plan because m-lines that\" +\n \" are not bundled were found.\");\n }\n }\n\n //#endregion\n\n\n //#region Convert from Plan B to Unified Plan.\n\n // Unfortunately, a Plan B offer/answer doesn't have enough information to\n // rebuild an equivalent Unified Plan offer/answer.\n //\n // For example, if this is a local answer (in Unified Plan style) that we\n // convert to Plan B prior to handing it over to the application (the\n // PeerConnection wrapper called us, for instance, after a successful\n // createAnswer), we want to remember the m-line at which we've seen the\n // (local) SSRC. That's because when the application wants to do call the\n // SLD method, forcing us to do the inverse transformation (from Plan B to\n // Unified Plan), we need to know to which m-line to assign the (local)\n // SSRC. We also need to know all the other m-lines that the original\n // answer had and include them in the transformed answer as well.\n //\n // Another example is if this is a remote offer that we convert to Plan B\n // prior to giving it to the application, we want to remember the mid at\n // which we've seen the (remote) SSRC.\n //\n // In the iteration that follows, we use the cached Unified Plan (if it\n // exists) to assign mids to ssrcs.\n\n var type;\n if (desc.type === 'answer') {\n type = 'offer';\n } else if (desc.type === 'offer') {\n type = 'answer';\n } else {\n throw new Error(\"Type '\" + desc.type + \"' not supported.\");\n }\n\n var cached;\n if (typeof this.cache[type] !== 'undefined') {\n cached = transform.parse(this.cache[type]);\n }\n\n var recvonlySsrcs = {\n audio: {},\n video: {}\n };\n\n // A helper map that sends mids to m-line objects. We use it later to\n // rebuild the Unified Plan style session.media array.\n var mid2ul = {};\n var bIdx = 0;\n var uIdx = 0;\n\n var sources2ul = {};\n\n var candidates;\n var iceUfrag;\n var icePwd;\n var fingerprint;\n var payloads = {};\n var rtcpFb = {};\n var rtp = {};\n\n session.media.forEach(function(bLine) {\n if ((typeof bLine.rtcpMux !== 'string' ||\n bLine.rtcpMux !== 'rtcp-mux') &&\n bLine.direction !== 'inactive') {\n throw new Error(\"Cannot convert to Unified Plan because m-lines \" +\n \"without the rtcp-mux attribute were found.\");\n }\n\n if (bLine.type === 'application') {\n mid2ul[bLine.mid] = bLine;\n return;\n }\n\n // With rtcp-mux and bundle all the channels should have the same ICE\n // stuff.\n var sources = bLine.sources;\n var ssrcGroups = bLine.ssrcGroups;\n var port = bLine.port;\n\n /* Chrome adds different candidates even using bundle, so we concat the candidates list */\n if (typeof bLine.candidates != 'undefined') {\n if (typeof candidates != 'undefined') {\n candidates = candidates.concat(bLine.candidates);\n } else {\n candidates = bLine.candidates;\n }\n }\n\n if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) {\n throw new Error(\"Only BUNDLE supported, iceUfrag must be the same for all m-lines.\\n\" +\n \"\\tLast iceUfrag: \" + iceUfrag + \"\\n\" +\n \"\\tNew iceUfrag: \" + bLine.iceUfrag\n );\n }\n\n if (typeof bLine.iceUfrag != 'undefined') {\n iceUfrag = bLine.iceUfrag;\n }\n\n if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) {\n throw new Error(\"Only BUNDLE supported, icePwd must be the same for all m-lines.\\n\" +\n \"\\tLast icePwd: \" + icePwd + \"\\n\" +\n \"\\tNew icePwd: \" + bLine.icePwd\n );\n }\n\n if (typeof bLine.icePwd != 'undefined') {\n icePwd = bLine.icePwd;\n }\n\n if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') &&\n (fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) {\n throw new Error(\"Only BUNDLE supported, fingerprint must be the same for all m-lines.\\n\" +\n \"\\tLast fingerprint: \" + JSON.stringify(fingerprint) + \"\\n\" +\n \"\\tNew fingerprint: \" + JSON.stringify(bLine.fingerprint)\n );\n }\n\n if (typeof bLine.fingerprint != 'undefined') {\n fingerprint = bLine.fingerprint;\n }\n\n payloads[bLine.type] = bLine.payloads;\n rtcpFb[bLine.type] = bLine.rtcpFb;\n rtp[bLine.type] = bLine.rtp;\n\n // inverted ssrc group map\n var ssrc2group = {};\n if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {\n ssrcGroups.forEach(function (ssrcGroup) {\n // XXX This might brake if an SSRC is in more than one group\n // for some reason.\n if (typeof ssrcGroup.ssrcs !== 'undefined' &&\n Array.isArray(ssrcGroup.ssrcs)) {\n ssrcGroup.ssrcs.forEach(function (ssrc) {\n if (typeof ssrc2group[ssrc] === 'undefined') {\n ssrc2group[ssrc] = [];\n }\n\n ssrc2group[ssrc].push(ssrcGroup);\n });\n }\n });\n }\n\n // ssrc to m-line index.\n var ssrc2ml = {};\n\n if (typeof sources === 'object') {\n\n // We'll use the \"bLine\" object as a prototype for each new \"mLine\"\n // that we create, but first we need to clean it up a bit.\n delete bLine.sources;\n delete bLine.ssrcGroups;\n delete bLine.candidates;\n delete bLine.iceUfrag;\n delete bLine.icePwd;\n delete bLine.fingerprint;\n delete bLine.port;\n delete bLine.mid;\n\n // Explode the Plan B channel sources with one m-line per source.\n Object.keys(sources).forEach(function(ssrc) {\n\n // The (unified) m-line for this SSRC. We either create it from\n // scratch or, if it's a grouped SSRC, we re-use a related\n // mline. In other words, if the source is grouped with another\n // source, put the two together in the same m-line.\n var uLine;\n\n // We assume here that we are the answerer in the O/A, so any\n // offers which we translate come from the remote side, while\n // answers are local. So the check below is to make that we\n // handle receive-only SSRCs in a special way only if they come\n // from the remote side.\n if (desc.type==='offer') {\n // We want to detect SSRCs which are used by a remote peer\n // in an m-line with direction=recvonly (i.e. they are\n // being used for RTCP only).\n // This information would have gotten lost if the remote\n // peer used Unified Plan and their local description was\n // translated to Plan B. So we use the lack of an MSID\n // attribute to deduce a \"receive only\" SSRC.\n if (!sources[ssrc].msid) {\n recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];\n // Receive-only SSRCs must not create new m-lines. We\n // will assign them to an existing m-line later.\n return;\n }\n }\n\n if (typeof ssrc2group[ssrc] !== 'undefined' &&\n Array.isArray(ssrc2group[ssrc])) {\n ssrc2group[ssrc].some(function (ssrcGroup) {\n // ssrcGroup.ssrcs *is* an Array, no need to check\n // again here.\n return ssrcGroup.ssrcs.some(function (related) {\n if (typeof ssrc2ml[related] === 'object') {\n uLine = ssrc2ml[related];\n return true;\n }\n });\n });\n }\n\n if (typeof uLine === 'object') {\n // the m-line already exists. Just add the source.\n uLine.sources[ssrc] = sources[ssrc];\n delete sources[ssrc].msid;\n } else {\n // Use the \"bLine\" as a prototype for the \"uLine\".\n uLine = Object.create(bLine);\n ssrc2ml[ssrc] = uLine;\n\n if (typeof sources[ssrc].msid !== 'undefined') {\n // Assign the msid of the source to the m-line. Note\n // that it is not guaranteed that the source will have\n // msid. In particular \"recvonly\" sources don't have an\n // msid. Note that \"recvonly\" is a term only defined\n // for m-lines.\n uLine.msid = sources[ssrc].msid;\n delete sources[ssrc].msid;\n }\n\n // We assign one SSRC per media line.\n uLine.sources = {};\n uLine.sources[ssrc] = sources[ssrc];\n uLine.ssrcGroups = ssrc2group[ssrc];\n\n // Use the cached Unified Plan SDP (if it exists) to assign\n // SSRCs to mids.\n if (typeof cached !== 'undefined' &&\n typeof cached.media !== 'undefined' &&\n Array.isArray(cached.media)) {\n\n cached.media.forEach(function (m) {\n if (typeof m.sources === 'object') {\n Object.keys(m.sources).forEach(function (s) {\n if (s === ssrc) {\n uLine.mid = m.mid;\n }\n });\n }\n });\n }\n\n if (typeof uLine.mid === 'undefined') {\n\n // If this is an SSRC that we see for the first time\n // assign it a new mid. This is typically the case when\n // this method is called to transform a remote\n // description for the first time or when there is a\n // new SSRC in the remote description because a new\n // peer has joined the conference. Local SSRCs should\n // have already been added to the map in the toPlanB\n // method.\n //\n // Because FF generates answers in Unified Plan style,\n // we MUST already have a cached answer with all the\n // local SSRCs mapped to some m-line/mid.\n\n uLine.mid = [bLine.type, '-', ssrc].join('');\n }\n\n // Include the candidates in the 1st media line.\n uLine.candidates = candidates;\n uLine.iceUfrag = iceUfrag;\n uLine.icePwd = icePwd;\n uLine.fingerprint = fingerprint;\n uLine.port = port;\n\n mid2ul[uLine.mid] = uLine;\n sources2ul[uIdx] = uLine.sources;\n\n self.cache.mlU2BMap[uIdx] = bIdx;\n if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {\n self.cache.mlB2UMap[bIdx] = uIdx;\n }\n uIdx++;\n }\n });\n } else {\n var uLine = bLine;\n\n uLine.candidates = candidates;\n uLine.iceUfrag = iceUfrag;\n uLine.icePwd = icePwd;\n uLine.fingerprint = fingerprint;\n uLine.port = port;\n\n mid2ul[uLine.mid] = uLine;\n\n self.cache.mlU2BMap[uIdx] = bIdx;\n if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {\n self.cache.mlB2UMap[bIdx] = uIdx;\n }\n }\n\n bIdx++;\n });\n\n // Rebuild the media array in the right order and add the missing mLines\n // (missing from the Plan B SDP).\n session.media = [];\n mids = []; // reuse\n\n if (desc.type === 'answer') {\n\n // The media lines in the answer must match the media lines in the\n // offer. The order is important too. Here we assume that Firefox is\n // the answerer, so we merely have to use the reconstructed (unified)\n // answer to update the cached (unified) answer accordingly.\n //\n // In the general case, one would have to use the cached (unified)\n // offer to find the m-lines that are missing from the reconstructed\n // answer, potentially grabbing them from the cached (unified) answer.\n // One has to be careful with this approach because inactive m-lines do\n // not always have an mid, making it tricky (impossible?) to find where\n // exactly and which m-lines are missing from the reconstructed answer.\n\n for (var i = 0; i < cached.media.length; i++) {\n var uLine = cached.media[i];\n\n delete uLine.msid;\n delete uLine.sources;\n delete uLine.ssrcGroups;\n\n if (typeof sources2ul[i] === 'undefined') {\n if (!uLine.direction\n || uLine.direction === 'sendrecv')\n uLine.direction = 'recvonly';\n else if (uLine.direction === 'sendonly')\n uLine.direction = 'inactive';\n } else {\n if (!uLine.direction\n || uLine.direction === 'sendrecv')\n uLine.direction = 'sendrecv';\n else if (uLine.direction === 'recvonly')\n uLine.direction = 'sendonly';\n }\n\n uLine.sources = sources2ul[i];\n uLine.candidates = candidates;\n uLine.iceUfrag = iceUfrag;\n uLine.icePwd = icePwd;\n uLine.fingerprint = fingerprint;\n\n uLine.rtp = rtp[uLine.type];\n uLine.payloads = payloads[uLine.type];\n uLine.rtcpFb = rtcpFb[uLine.type];\n\n session.media.push(uLine);\n\n if (typeof uLine.mid === 'string') {\n // inactive lines don't/may not have an mid.\n mids.push(uLine.mid);\n }\n }\n } else {\n\n // SDP offer/answer (and the JSEP spec) forbids removing an m-section\n // under any circumstances. If we are no longer interested in sending a\n // track, we just remove the msid and ssrc attributes and set it to\n // either a=recvonly (as the reofferer, we must use recvonly if the\n // other side was previously sending on the m-section, but we can also\n // leave the possibility open if it wasn't previously in use), or\n // a=inactive.\n\n if (typeof cached !== 'undefined' &&\n typeof cached.media !== 'undefined' &&\n Array.isArray(cached.media)) {\n cached.media.forEach(function(uLine) {\n mids.push(uLine.mid);\n if (typeof mid2ul[uLine.mid] !== 'undefined') {\n session.media.push(mid2ul[uLine.mid]);\n } else {\n delete uLine.msid;\n delete uLine.sources;\n delete uLine.ssrcGroups;\n\n if (!uLine.direction\n || uLine.direction === 'sendrecv') {\n uLine.direction = 'sendonly';\n }\n if (!uLine.direction\n || uLine.direction === 'recvonly') {\n uLine.direction = 'inactive';\n }\n\n addSetupAttr (uLine);\n session.media.push(uLine);\n }\n });\n }\n\n // Add all the remaining (new) m-lines of the transformed SDP.\n Object.keys(mid2ul).forEach(function(mid) {\n if (mids.indexOf(mid) === -1) {\n mids.push(mid);\n if (mid2ul[mid].direction === 'recvonly') {\n // This is a remote recvonly channel. Add its SSRC to the\n // appropriate sendrecv or sendonly channel.\n // TODO(gp) what if we don't have sendrecv/sendonly\n // channel?\n\n var done = false;\n\n session.media.some(function (uLine) {\n if ((uLine.direction === 'sendrecv' ||\n uLine.direction === 'sendonly') &&\n uLine.type === mid2ul[mid].type) {\n // mid2ul[mid] shouldn't have any ssrc-groups\n Object.keys(mid2ul[mid].sources).forEach(\n function (ssrc) {\n uLine.sources[ssrc] =\n mid2ul[mid].sources[ssrc];\n });\n\n done = true;\n return true;\n }\n });\n\n if (!done) {\n session.media.push(mid2ul[mid]);\n }\n } else {\n session.media.push(mid2ul[mid]);\n }\n }\n });\n }\n\n // After we have constructed the Plan Unified m-lines we can figure out\n // where (in which m-line) to place the 'recvonly SSRCs'.\n // Note: we assume here that we are the answerer in the O/A, so any offers\n // which we translate come from the remote side, while answers are local\n // (and so our last local description is cached as an 'answer').\n [\"audio\", \"video\"].forEach(function (type) {\n if (!session || !session.media || !Array.isArray(session.media))\n return;\n\n var idx = null;\n if (Object.keys(recvonlySsrcs[type]).length > 0) {\n idx = self.getFirstSendingIndexFromAnswer(type);\n if (idx === null){\n // If this is the first offer we receive, we don't have a\n // cached answer. Assume that we will be sending media using\n // the first m-line for each media type.\n\n for (var i = 0; i < session.media.length; i++) {\n if (session.media[i].type === type) {\n idx = i;\n break;\n }\n }\n }\n }\n\n if (idx && session.media.length > idx) {\n var mLine = session.media[idx];\n Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) {\n if (mLine.sources && mLine.sources[ssrc]) {\n console.warn(\"Replacing an existing SSRC.\");\n }\n if (!mLine.sources) {\n mLine.sources = {};\n }\n\n mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];\n });\n }\n });\n\n if (typeof session.groups !== 'undefined') {\n // We regenerate the BUNDLE group (since we regenerated the mids)\n session.groups.some(function(group) {\n\t if (group.type === 'BUNDLE') {\n\t group.mids = mids.join(' ');\n\t return true;\n\t }\n });\n }\n\n // msid semantic\n session.msidSemantic = {\n semantic: 'WMS',\n token: '*'\n };\n\n var resStr = transform.write(session);\n\n // Cache the transformed SDP (Unified Plan) for later re-use in this\n // function.\n this.cache[desc.type] = resStr;\n\n return new RTCSessionDescription({\n type: desc.type,\n sdp: resStr\n });\n\n //#endregion\n};\n","/* Copyright @ 2015 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nvar transform = require('sdp-transform');\n\nexports.write = function(session, opts) {\n\n if (typeof session !== 'undefined' &&\n typeof session.media !== 'undefined' &&\n Array.isArray(session.media)) {\n\n session.media.forEach(function (mLine) {\n // expand sources to ssrcs\n if (typeof mLine.sources !== 'undefined' &&\n Object.keys(mLine.sources).length !== 0) {\n mLine.ssrcs = [];\n Object.keys(mLine.sources).forEach(function (ssrc) {\n var source = mLine.sources[ssrc];\n Object.keys(source).forEach(function (attribute) {\n mLine.ssrcs.push({\n id: ssrc,\n attribute: attribute,\n value: source[attribute]\n });\n });\n });\n delete mLine.sources;\n }\n\n // join ssrcs in ssrc groups\n if (typeof mLine.ssrcGroups !== 'undefined' &&\n Array.isArray(mLine.ssrcGroups)) {\n mLine.ssrcGroups.forEach(function (ssrcGroup) {\n if (typeof ssrcGroup.ssrcs !== 'undefined' &&\n Array.isArray(ssrcGroup.ssrcs)) {\n ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');\n }\n });\n }\n });\n }\n\n // join group mids\n if (typeof session !== 'undefined' &&\n typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {\n\n session.groups.forEach(function (g) {\n if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {\n g.mids = g.mids.join(' ');\n }\n });\n }\n\n return transform.write(session, opts);\n};\n\nexports.parse = function(sdp) {\n var session = transform.parse(sdp);\n\n if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&\n Array.isArray(session.media)) {\n\n session.media.forEach(function (mLine) {\n // group sources attributes by ssrc\n if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {\n mLine.sources = {};\n mLine.ssrcs.forEach(function (ssrc) {\n if (!mLine.sources[ssrc.id])\n mLine.sources[ssrc.id] = {};\n mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;\n });\n\n delete mLine.ssrcs;\n }\n\n // split ssrcs in ssrc groups\n if (typeof mLine.ssrcGroups !== 'undefined' &&\n Array.isArray(mLine.ssrcGroups)) {\n mLine.ssrcGroups.forEach(function (ssrcGroup) {\n if (typeof ssrcGroup.ssrcs === 'string') {\n ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');\n }\n });\n }\n });\n }\n // split group mids\n if (typeof session !== 'undefined' &&\n typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {\n\n session.groups.forEach(function (g) {\n if (typeof g.mids === 'string') {\n g.mids = g.mids.split(' ');\n }\n });\n }\n\n return session;\n};\n\n","/**\n * UAParser.js v0.7.17\n * Lightweight JavaScript-based User-Agent string parser\n * https://github.com/faisalman/ua-parser-js\n *\n * Copyright © 2012-2016 Faisal Salman \n * Dual licensed under GPLv2 & MIT\n */\n\n(function (window, undefined) {\n\n 'use strict';\n\n //////////////\n // Constants\n /////////////\n\n\n var LIBVERSION = '0.7.17',\n EMPTY = '',\n UNKNOWN = '?',\n FUNC_TYPE = 'function',\n UNDEF_TYPE = 'undefined',\n OBJ_TYPE = 'object',\n STR_TYPE = 'string',\n MAJOR = 'major', // deprecated\n MODEL = 'model',\n NAME = 'name',\n TYPE = 'type',\n VENDOR = 'vendor',\n VERSION = 'version',\n ARCHITECTURE= 'architecture',\n CONSOLE = 'console',\n MOBILE = 'mobile',\n TABLET = 'tablet',\n SMARTTV = 'smarttv',\n WEARABLE = 'wearable',\n EMBEDDED = 'embedded';\n\n\n ///////////\n // Helper\n //////////\n\n\n var util = {\n extend : function (regexes, extensions) {\n var margedRegexes = {};\n for (var i in regexes) {\n if (extensions[i] && extensions[i].length % 2 === 0) {\n margedRegexes[i] = extensions[i].concat(regexes[i]);\n } else {\n margedRegexes[i] = regexes[i];\n }\n }\n return margedRegexes;\n },\n has : function (str1, str2) {\n if (typeof str1 === \"string\") {\n return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;\n } else {\n return false;\n }\n },\n lowerize : function (str) {\n return str.toLowerCase();\n },\n major : function (version) {\n return typeof(version) === STR_TYPE ? version.replace(/[^\\d\\.]/g,'').split(\".\")[0] : undefined;\n },\n trim : function (str) {\n return str.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g, '');\n }\n };\n\n\n ///////////////\n // Map helper\n //////////////\n\n\n var mapper = {\n\n rgx : function (ua, arrays) {\n\n //var result = {},\n var i = 0, j, k, p, q, matches, match;//, args = arguments;\n\n /*// construct object barebones\n for (p = 0; p < args[1].length; p++) {\n q = args[1][p];\n result[typeof q === OBJ_TYPE ? q[0] : q] = undefined;\n }*/\n\n // loop through all regexes maps\n while (i < arrays.length && !matches) {\n\n var regex = arrays[i], // even sequence (0,2,4,..)\n props = arrays[i + 1]; // odd sequence (1,3,5,..)\n j = k = 0;\n\n // try matching uastring with regexes\n while (j < regex.length && !matches) {\n\n matches = regex[j++].exec(ua);\n\n if (!!matches) {\n for (p = 0; p < props.length; p++) {\n match = matches[++k];\n q = props[p];\n // check if given property is actually array\n if (typeof q === OBJ_TYPE && q.length > 0) {\n if (q.length == 2) {\n if (typeof q[1] == FUNC_TYPE) {\n // assign modified match\n this[q[0]] = q[1].call(this, match);\n } else {\n // assign given value, ignore regex match\n this[q[0]] = q[1];\n }\n } else if (q.length == 3) {\n // check whether function or regex\n if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {\n // call function (usually string mapper)\n this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;\n } else {\n // sanitize match using given regex\n this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;\n }\n } else if (q.length == 4) {\n this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;\n }\n } else {\n this[q] = match ? match : undefined;\n }\n }\n }\n }\n i += 2;\n }\n // console.log(this);\n //return this;\n },\n\n str : function (str, map) {\n\n for (var i in map) {\n // check if array\n if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {\n for (var j = 0; j < map[i].length; j++) {\n if (util.has(map[i][j], str)) {\n return (i === UNKNOWN) ? undefined : i;\n }\n }\n } else if (util.has(map[i], str)) {\n return (i === UNKNOWN) ? undefined : i;\n }\n }\n return str;\n }\n };\n\n\n ///////////////\n // String map\n //////////////\n\n\n var maps = {\n\n browser : {\n oldsafari : {\n version : {\n '1.0' : '/8',\n '1.2' : '/1',\n '1.3' : '/3',\n '2.0' : '/412',\n '2.0.2' : '/416',\n '2.0.3' : '/417',\n '2.0.4' : '/419',\n '?' : '/'\n }\n }\n },\n\n device : {\n amazon : {\n model : {\n 'Fire Phone' : ['SD', 'KF']\n }\n },\n sprint : {\n model : {\n 'Evo Shift 4G' : '7373KT'\n },\n vendor : {\n 'HTC' : 'APA',\n 'Sprint' : 'Sprint'\n }\n }\n },\n\n os : {\n windows : {\n version : {\n 'ME' : '4.90',\n 'NT 3.11' : 'NT3.51',\n 'NT 4.0' : 'NT4.0',\n '2000' : 'NT 5.0',\n 'XP' : ['NT 5.1', 'NT 5.2'],\n 'Vista' : 'NT 6.0',\n '7' : 'NT 6.1',\n '8' : 'NT 6.2',\n '8.1' : 'NT 6.3',\n '10' : ['NT 6.4', 'NT 10.0'],\n 'RT' : 'ARM'\n }\n }\n }\n };\n\n\n //////////////\n // Regex map\n /////////////\n\n\n var regexes = {\n\n browser : [[\n\n // Presto based\n /(opera\\smini)\\/([\\w\\.-]+)/i, // Opera Mini\n /(opera\\s[mobiletab]+).+version\\/([\\w\\.-]+)/i, // Opera Mobi/Tablet\n /(opera).+version\\/([\\w\\.]+)/i, // Opera > 9.80\n /(opera)[\\/\\s]+([\\w\\.]+)/i // Opera < 9.80\n ], [NAME, VERSION], [\n\n /(opios)[\\/\\s]+([\\w\\.]+)/i // Opera mini on iphone >= 8.0\n ], [[NAME, 'Opera Mini'], VERSION], [\n\n /\\s(opr)\\/([\\w\\.]+)/i // Opera Webkit\n ], [[NAME, 'Opera'], VERSION], [\n\n // Mixed\n /(kindle)\\/([\\w\\.]+)/i, // Kindle\n /(lunascape|maxthon|netfront|jasmine|blazer)[\\/\\s]?([\\w\\.]+)*/i,\n // Lunascape/Maxthon/Netfront/Jasmine/Blazer\n\n // Trident based\n /(avant\\s|iemobile|slim|baidu)(?:browser)?[\\/\\s]?([\\w\\.]*)/i,\n // Avant/IEMobile/SlimBrowser/Baidu\n /(?:ms|\\()(ie)\\s([\\w\\.]+)/i, // Internet Explorer\n\n // Webkit/KHTML based\n /(rekonq)\\/([\\w\\.]+)*/i, // Rekonq\n /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser)\\/([\\w\\.-]+)/i\n // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser\n ], [NAME, VERSION], [\n\n /(trident).+rv[:\\s]([\\w\\.]+).+like\\sgecko/i // IE11\n ], [[NAME, 'IE'], VERSION], [\n\n /(edge)\\/((\\d+)?[\\w\\.]+)/i // Microsoft Edge\n ], [NAME, VERSION], [\n\n /(yabrowser)\\/([\\w\\.]+)/i // Yandex\n ], [[NAME, 'Yandex'], VERSION], [\n\n /(puffin)\\/([\\w\\.]+)/i // Puffin\n ], [[NAME, 'Puffin'], VERSION], [\n\n /((?:[\\s\\/])uc?\\s?browser|(?:juc.+)ucweb)[\\/\\s]?([\\w\\.]+)/i\n // UCBrowser\n ], [[NAME, 'UCBrowser'], VERSION], [\n\n /(comodo_dragon)\\/([\\w\\.]+)/i // Comodo Dragon\n ], [[NAME, /_/g, ' '], VERSION], [\n\n /(micromessenger)\\/([\\w\\.]+)/i // WeChat\n ], [[NAME, 'WeChat'], VERSION], [\n\n /(QQ)\\/([\\d\\.]+)/i // QQ, aka ShouQ\n ], [NAME, VERSION], [\n\n /m?(qqbrowser)[\\/\\s]?([\\w\\.]+)/i // QQBrowser\n ], [NAME, VERSION], [\n\n /xiaomi\\/miuibrowser\\/([\\w\\.]+)/i // MIUI Browser\n ], [VERSION, [NAME, 'MIUI Browser']], [\n\n /;fbav\\/([\\w\\.]+);/i // Facebook App for iOS & Android\n ], [VERSION, [NAME, 'Facebook']], [\n\n /headlesschrome(?:\\/([\\w\\.]+)|\\s)/i // Chrome Headless\n ], [VERSION, [NAME, 'Chrome Headless']], [\n\n /\\swv\\).+(chrome)\\/([\\w\\.]+)/i // Chrome WebView\n ], [[NAME, /(.+)/, '$1 WebView'], VERSION], [\n\n /((?:oculus|samsung)browser)\\/([\\w\\.]+)/i\n ], [[NAME, /(.+(?:g|us))(.+)/, '$1 $2'], VERSION], [ // Oculus / Samsung Browser\n\n /android.+version\\/([\\w\\.]+)\\s+(?:mobile\\s?safari|safari)*/i // Android Browser\n ], [VERSION, [NAME, 'Android Browser']], [\n\n /(chrome|omniweb|arora|[tizenoka]{5}\\s?browser)\\/v?([\\w\\.]+)/i\n // Chrome/OmniWeb/Arora/Tizen/Nokia\n ], [NAME, VERSION], [\n\n /(dolfin)\\/([\\w\\.]+)/i // Dolphin\n ], [[NAME, 'Dolphin'], VERSION], [\n\n /((?:android.+)crmo|crios)\\/([\\w\\.]+)/i // Chrome for Android/iOS\n ], [[NAME, 'Chrome'], VERSION], [\n\n /(coast)\\/([\\w\\.]+)/i // Opera Coast\n ], [[NAME, 'Opera Coast'], VERSION], [\n\n /fxios\\/([\\w\\.-]+)/i // Firefox for iOS\n ], [VERSION, [NAME, 'Firefox']], [\n\n /version\\/([\\w\\.]+).+?mobile\\/\\w+\\s(safari)/i // Mobile Safari\n ], [VERSION, [NAME, 'Mobile Safari']], [\n\n /version\\/([\\w\\.]+).+?(mobile\\s?safari|safari)/i // Safari & Safari Mobile\n ], [VERSION, NAME], [\n\n /webkit.+?(gsa)\\/([\\w\\.]+).+?(mobile\\s?safari|safari)(\\/[\\w\\.]+)/i // Google Search Appliance on iOS\n ], [[NAME, 'GSA'], VERSION], [\n\n /webkit.+?(mobile\\s?safari|safari)(\\/[\\w\\.]+)/i // Safari < 3.0\n ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [\n\n /(konqueror)\\/([\\w\\.]+)/i, // Konqueror\n /(webkit|khtml)\\/([\\w\\.]+)/i\n ], [NAME, VERSION], [\n\n // Gecko based\n /(navigator|netscape)\\/([\\w\\.-]+)/i // Netscape\n ], [[NAME, 'Netscape'], VERSION], [\n /(swiftfox)/i, // Swiftfox\n /(icedragon|iceweasel|camino|chimera|fennec|maemo\\sbrowser|minimo|conkeror)[\\/\\s]?([\\w\\.\\+]+)/i,\n // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror\n /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\\/([\\w\\.-]+)/i,\n // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix\n /(mozilla)\\/([\\w\\.]+).+rv\\:.+gecko\\/\\d+/i, // Mozilla\n\n // Other\n /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\\/\\s]?([\\w\\.]+)/i,\n // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir\n /(links)\\s\\(([\\w\\.]+)/i, // Links\n /(gobrowser)\\/?([\\w\\.]+)*/i, // GoBrowser\n /(ice\\s?browser)\\/v?([\\w\\._]+)/i, // ICE Browser\n /(mosaic)[\\/\\s]([\\w\\.]+)/i // Mosaic\n ], [NAME, VERSION]\n\n /* /////////////////////\n // Media players BEGIN\n ////////////////////////\n\n , [\n\n /(apple(?:coremedia|))\\/((\\d+)[\\w\\._]+)/i, // Generic Apple CoreMedia\n /(coremedia) v((\\d+)[\\w\\._]+)/i\n ], [NAME, VERSION], [\n\n /(aqualung|lyssna|bsplayer)\\/((\\d+)?[\\w\\.-]+)/i // Aqualung/Lyssna/BSPlayer\n ], [NAME, VERSION], [\n\n /(ares|ossproxy)\\s((\\d+)[\\w\\.-]+)/i // Ares/OSSProxy\n ], [NAME, VERSION], [\n\n /(audacious|audimusicstream|amarok|bass|core|dalvik|gnomemplayer|music on console|nsplayer|psp-internetradioplayer|videos)\\/((\\d+)[\\w\\.-]+)/i,\n // Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/Dalvik/GnomeMplayer/MoC\n // NSPlayer/PSP-InternetRadioPlayer/Videos\n /(clementine|music player daemon)\\s((\\d+)[\\w\\.-]+)/i, // Clementine/MPD\n /(lg player|nexplayer)\\s((\\d+)[\\d\\.]+)/i,\n /player\\/(nexplayer|lg player)\\s((\\d+)[\\w\\.-]+)/i // NexPlayer/LG Player\n ], [NAME, VERSION], [\n /(nexplayer)\\s((\\d+)[\\w\\.-]+)/i // Nexplayer\n ], [NAME, VERSION], [\n\n /(flrp)\\/((\\d+)[\\w\\.-]+)/i // Flip Player\n ], [[NAME, 'Flip Player'], VERSION], [\n\n /(fstream|nativehost|queryseekspider|ia-archiver|facebookexternalhit)/i\n // FStream/NativeHost/QuerySeekSpider/IA Archiver/facebookexternalhit\n ], [NAME], [\n\n /(gstreamer) souphttpsrc (?:\\([^\\)]+\\)){0,1} libsoup\\/((\\d+)[\\w\\.-]+)/i\n // Gstreamer\n ], [NAME, VERSION], [\n\n /(htc streaming player)\\s[\\w_]+\\s\\/\\s((\\d+)[\\d\\.]+)/i, // HTC Streaming Player\n /(java|python-urllib|python-requests|wget|libcurl)\\/((\\d+)[\\w\\.-_]+)/i,\n // Java/urllib/requests/wget/cURL\n /(lavf)((\\d+)[\\d\\.]+)/i // Lavf (FFMPEG)\n ], [NAME, VERSION], [\n\n /(htc_one_s)\\/((\\d+)[\\d\\.]+)/i // HTC One S\n ], [[NAME, /_/g, ' '], VERSION], [\n\n /(mplayer)(?:\\s|\\/)(?:(?:sherpya-){0,1}svn)(?:-|\\s)(r\\d+(?:-\\d+[\\w\\.-]+){0,1})/i\n // MPlayer SVN\n ], [NAME, VERSION], [\n\n /(mplayer)(?:\\s|\\/|[unkow-]+)((\\d+)[\\w\\.-]+)/i // MPlayer\n ], [NAME, VERSION], [\n\n /(mplayer)/i, // MPlayer (no other info)\n /(yourmuze)/i, // YourMuze\n /(media player classic|nero showtime)/i // Media Player Classic/Nero ShowTime\n ], [NAME], [\n\n /(nero (?:home|scout))\\/((\\d+)[\\w\\.-]+)/i // Nero Home/Nero Scout\n ], [NAME, VERSION], [\n\n /(nokia\\d+)\\/((\\d+)[\\w\\.-]+)/i // Nokia\n ], [NAME, VERSION], [\n\n /\\s(songbird)\\/((\\d+)[\\w\\.-]+)/i // Songbird/Philips-Songbird\n ], [NAME, VERSION], [\n\n /(winamp)3 version ((\\d+)[\\w\\.-]+)/i, // Winamp\n /(winamp)\\s((\\d+)[\\w\\.-]+)/i,\n /(winamp)mpeg\\/((\\d+)[\\w\\.-]+)/i\n ], [NAME, VERSION], [\n\n /(ocms-bot|tapinradio|tunein radio|unknown|winamp|inlight radio)/i // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)\n // inlight radio\n ], [NAME], [\n\n /(quicktime|rma|radioapp|radioclientapplication|soundtap|totem|stagefright|streamium)\\/((\\d+)[\\w\\.-]+)/i\n // QuickTime/RealMedia/RadioApp/RadioClientApplication/\n // SoundTap/Totem/Stagefright/Streamium\n ], [NAME, VERSION], [\n\n /(smp)((\\d+)[\\d\\.]+)/i // SMP\n ], [NAME, VERSION], [\n\n /(vlc) media player - version ((\\d+)[\\w\\.]+)/i, // VLC Videolan\n /(vlc)\\/((\\d+)[\\w\\.-]+)/i,\n /(xbmc|gvfs|xine|xmms|irapp)\\/((\\d+)[\\w\\.-]+)/i, // XBMC/gvfs/Xine/XMMS/irapp\n /(foobar2000)\\/((\\d+)[\\d\\.]+)/i, // Foobar2000\n /(itunes)\\/((\\d+)[\\d\\.]+)/i // iTunes\n ], [NAME, VERSION], [\n\n /(wmplayer)\\/((\\d+)[\\w\\.-]+)/i, // Windows Media Player\n /(windows-media-player)\\/((\\d+)[\\w\\.-]+)/i\n ], [[NAME, /-/g, ' '], VERSION], [\n\n /windows\\/((\\d+)[\\w\\.-]+) upnp\\/[\\d\\.]+ dlnadoc\\/[\\d\\.]+ (home media server)/i\n // Windows Media Server\n ], [VERSION, [NAME, 'Windows']], [\n\n /(com\\.riseupradioalarm)\\/((\\d+)[\\d\\.]*)/i // RiseUP Radio Alarm\n ], [NAME, VERSION], [\n\n /(rad.io)\\s((\\d+)[\\d\\.]+)/i, // Rad.io\n /(radio.(?:de|at|fr))\\s((\\d+)[\\d\\.]+)/i\n ], [[NAME, 'rad.io'], VERSION]\n\n //////////////////////\n // Media players END\n ////////////////////*/\n\n ],\n\n cpu : [[\n\n /(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\\)]/i // AMD64\n ], [[ARCHITECTURE, 'amd64']], [\n\n /(ia32(?=;))/i // IA32 (quicktime)\n ], [[ARCHITECTURE, util.lowerize]], [\n\n /((?:i[346]|x)86)[;\\)]/i // IA32\n ], [[ARCHITECTURE, 'ia32']], [\n\n // PocketPC mistakenly identified as PowerPC\n /windows\\s(ce|mobile);\\sppc;/i\n ], [[ARCHITECTURE, 'arm']], [\n\n /((?:ppc|powerpc)(?:64)?)(?:\\smac|;|\\))/i // PowerPC\n ], [[ARCHITECTURE, /ower/, '', util.lowerize]], [\n\n /(sun4\\w)[;\\)]/i // SPARC\n ], [[ARCHITECTURE, 'sparc']], [\n\n /((?:avr32|ia64(?=;))|68k(?=\\))|arm(?:64|(?=v\\d+;))|(?=atmel\\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i\n // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC\n ], [[ARCHITECTURE, util.lowerize]]\n ],\n\n device : [[\n\n /\\((ipad|playbook);[\\w\\s\\);-]+(rim|apple)/i // iPad/PlayBook\n ], [MODEL, VENDOR, [TYPE, TABLET]], [\n\n /applecoremedia\\/[\\w\\.]+ \\((ipad)/ // iPad\n ], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [\n\n /(apple\\s{0,1}tv)/i // Apple TV\n ], [[MODEL, 'Apple TV'], [VENDOR, 'Apple']], [\n\n /(archos)\\s(gamepad2?)/i, // Archos\n /(hp).+(touchpad)/i, // HP TouchPad\n /(hp).+(tablet)/i, // HP Tablet\n /(kindle)\\/([\\w\\.]+)/i, // Kindle\n /\\s(nook)[\\w\\s]+build\\/(\\w+)/i, // Nook\n /(dell)\\s(strea[kpr\\s\\d]*[\\dko])/i // Dell Streak\n ], [VENDOR, MODEL, [TYPE, TABLET]], [\n\n /(kf[A-z]+)\\sbuild\\/[\\w\\.]+.*silk\\//i // Kindle Fire HD\n ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [\n /(sd|kf)[0349hijorstuw]+\\sbuild\\/[\\w\\.]+.*silk\\//i // Fire Phone\n ], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [\n\n /\\((ip[honed|\\s\\w*]+);.+(apple)/i // iPod/iPhone\n ], [MODEL, VENDOR, [TYPE, MOBILE]], [\n /\\((ip[honed|\\s\\w*]+);/i // iPod/iPhone\n ], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [\n\n /(blackberry)[\\s-]?(\\w+)/i, // BlackBerry\n /(blackberry|benq|palm(?=\\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[\\s_-]?([\\w-]+)*/i,\n // BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron\n /(hp)\\s([\\w\\s]+\\w)/i, // HP iPAQ\n /(asus)-?(\\w+)/i // Asus\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n /\\(bb10;\\s(\\w+)/i // BlackBerry 10\n ], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [\n // Asus Tablets\n /android.+(transfo[prime\\s]{4,10}\\s\\w+|eeepc|slider\\s\\w+|nexus 7|padfone)/i\n ], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [\n\n /(sony)\\s(tablet\\s[ps])\\sbuild\\//i, // Sony\n /(sony)?(?:sgp.+)\\sbuild\\//i\n ], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [\n /android.+\\s([c-g]\\d{4}|so[-l]\\w+)\\sbuild\\//i\n ], [MODEL, [VENDOR, 'Sony'], [TYPE, MOBILE]], [\n\n /\\s(ouya)\\s/i, // Ouya\n /(nintendo)\\s([wids3u]+)/i // Nintendo\n ], [VENDOR, MODEL, [TYPE, CONSOLE]], [\n\n /android.+;\\s(shield)\\sbuild/i // Nvidia\n ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [\n\n /(playstation\\s[34portablevi]+)/i // Playstation\n ], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [\n\n /(sprint\\s(\\w+))/i // Sprint Phones\n ], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [\n\n /(lenovo)\\s?(S(?:5000|6000)+(?:[-][\\w+]))/i // Lenovo tablets\n ], [VENDOR, MODEL, [TYPE, TABLET]], [\n\n /(htc)[;_\\s-]+([\\w\\s]+(?=\\))|\\w+)*/i, // HTC\n /(zte)-(\\w+)*/i, // ZTE\n /(alcatel|geeksphone|lenovo|nexian|panasonic|(?=;\\s)sony)[_\\s-]?([\\w-]+)*/i\n // Alcatel/GeeksPhone/Lenovo/Nexian/Panasonic/Sony\n ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [\n\n /(nexus\\s9)/i // HTC Nexus 9\n ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [\n\n /d\\/huawei([\\w\\s-]+)[;\\)]/i,\n /(nexus\\s6p)/i // Huawei\n ], [MODEL, [VENDOR, 'Huawei'], [TYPE, MOBILE]], [\n\n /(microsoft);\\s(lumia[\\s\\w]+)/i // Microsoft Lumia\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n\n /[\\s\\(;](xbox(?:\\sone)?)[\\s\\);]/i // Microsoft Xbox\n ], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [\n /(kin\\.[onetw]{3})/i // Microsoft Kin\n ], [[MODEL, /\\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [\n\n // Motorola\n /\\s(milestone|droid(?:[2-4x]|\\s(?:bionic|x2|pro|razr))?(:?\\s4g)?)[\\w\\s]+build\\//i,\n /mot[\\s-]?(\\w+)*/i,\n /(XT\\d{3,4}) build\\//i,\n /(nexus\\s6)/i\n ], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [\n /android.+\\s(mz60\\d|xoom[\\s2]{0,2})\\sbuild\\//i\n ], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [\n\n /hbbtv\\/\\d+\\.\\d+\\.\\d+\\s+\\([\\w\\s]*;\\s*(\\w[^;]*);([^;]*)/i // HbbTV devices\n ], [[VENDOR, util.trim], [MODEL, util.trim], [TYPE, SMARTTV]], [\n\n /hbbtv.+maple;(\\d+)/i\n ], [[MODEL, /^/, 'SmartTV'], [VENDOR, 'Samsung'], [TYPE, SMARTTV]], [\n\n /\\(dtv[\\);].+(aquos)/i // Sharp\n ], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [\n\n /android.+((sch-i[89]0\\d|shw-m380s|gt-p\\d{4}|gt-n\\d+|sgh-t8[56]9|nexus 10))/i,\n /((SM-T\\w+))/i\n ], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [ // Samsung\n /smart-tv.+(samsung)/i\n ], [VENDOR, [TYPE, SMARTTV], MODEL], [\n /((s[cgp]h-\\w+|gt-\\w+|galaxy\\snexus|sm-\\w[\\w\\d]+))/i,\n /(sam[sung]*)[\\s-]*(\\w+-?[\\w-]*)*/i,\n /sec-((sgh\\w+))/i\n ], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [\n\n /sie-(\\w+)*/i // Siemens\n ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [\n\n /(maemo|nokia).*(n900|lumia\\s\\d+)/i, // Nokia\n /(nokia)[\\s_-]?([\\w-]+)*/i\n ], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [\n\n /android\\s3\\.[\\s\\w;-]{10}(a\\d{3})/i // Acer\n ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [\n\n /android.+([vl]k\\-?\\d{3})\\s+build/i // LG Tablet\n ], [MODEL, [VENDOR, 'LG'], [TYPE, TABLET]], [\n /android\\s3\\.[\\s\\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet\n ], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [\n /(lg) netcast\\.tv/i // LG SmartTV\n ], [VENDOR, MODEL, [TYPE, SMARTTV]], [\n /(nexus\\s[45])/i, // LG\n /lg[e;\\s\\/-]+(\\w+)*/i,\n /android.+lg(\\-?[\\d\\w]+)\\s+build/i\n ], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [\n\n /android.+(ideatab[a-z0-9\\-\\s]+)/i // Lenovo\n ], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [\n\n /linux;.+((jolla));/i // Jolla\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n\n /((pebble))app\\/[\\d\\.]+\\s/i // Pebble\n ], [VENDOR, MODEL, [TYPE, WEARABLE]], [\n\n /android.+;\\s(oppo)\\s?([\\w\\s]+)\\sbuild/i // OPPO\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n\n /crkey/i // Google Chromecast\n ], [[MODEL, 'Chromecast'], [VENDOR, 'Google']], [\n\n /android.+;\\s(glass)\\s\\d/i // Google Glass\n ], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [\n\n /android.+;\\s(pixel c)\\s/i // Google Pixel C\n ], [MODEL, [VENDOR, 'Google'], [TYPE, TABLET]], [\n\n /android.+;\\s(pixel xl|pixel)\\s/i // Google Pixel\n ], [MODEL, [VENDOR, 'Google'], [TYPE, MOBILE]], [\n\n /android.+(\\w+)\\s+build\\/hm\\1/i, // Xiaomi Hongmi 'numeric' models\n /android.+(hm[\\s\\-_]*note?[\\s_]*(?:\\d\\w)?)\\s+build/i, // Xiaomi Hongmi\n /android.+(mi[\\s\\-_]*(?:one|one[\\s_]plus|note lte)?[\\s_]*(?:\\d\\w)?)\\s+build/i, // Xiaomi Mi\n /android.+(redmi[\\s\\-_]*(?:note)?(?:[\\s_]*[\\w\\s]+)?)\\s+build/i // Redmi Phones\n ], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [\n /android.+(mi[\\s\\-_]*(?:pad)?(?:[\\s_]*[\\w\\s]+)?)\\s+build/i // Mi Pad tablets\n ],[[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, TABLET]], [\n /android.+;\\s(m[1-5]\\snote)\\sbuild/i // Meizu Tablet\n ], [MODEL, [VENDOR, 'Meizu'], [TYPE, TABLET]], [\n\n /android.+a000(1)\\s+build/i // OnePlus\n ], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [\n\n /android.+[;\\/]\\s*(RCT[\\d\\w]+)\\s+build/i // RCA Tablets\n ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Venue[\\d\\s]*)\\s+build/i // Dell Venue Tablets\n ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Q[T|M][\\d\\w]+)\\s+build/i // Verizon Tablet\n ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s+(Barnes[&\\s]+Noble\\s+|BN[RT])(V?.*)\\s+build/i // Barnes & Noble Tablet\n ], [[VENDOR, 'Barnes & Noble'], MODEL, [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s+(TM\\d{3}.*\\b)\\s+build/i // Barnes & Noble Tablet\n ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(zte)?.+(k\\d{2})\\s+build/i // ZTE K Series Tablet\n ], [[VENDOR, 'ZTE'], MODEL, [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(gen\\d{3})\\s+build.*49h/i // Swiss GEN Mobile\n ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [\n\n /android.+[;\\/]\\s*(zur\\d{3})\\s+build/i // Swiss ZUR Tablet\n ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*((Zeki)?TB.*\\b)\\s+build/i // Zeki Tablets\n ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [\n\n /(android).+[;\\/]\\s+([YR]\\d{2}x?.*)\\s+build/i,\n /android.+[;\\/]\\s+(Dragon[\\-\\s]+Touch\\s+|DT)(.+)\\s+build/i // Dragon Touch Tablet\n ], [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(NS-?.+)\\s+build/i // Insignia Tablets\n ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*((NX|Next)-?.+)\\s+build/i // NextBook Tablets\n ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Xtreme\\_?)?(V(1[045]|2[015]|30|40|60|7[05]|90))\\s+build/i\n ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [ // Voice Xtreme Phones\n\n /android.+[;\\/]\\s*(LVTEL\\-?)?(V1[12])\\s+build/i // LvTel Phones\n ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [\n\n /android.+[;\\/]\\s*(V(100MD|700NA|7011|917G).*\\b)\\s+build/i // Envizen Tablets\n ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Le[\\s\\-]+Pan)[\\s\\-]+(.*\\b)\\s+build/i // Le Pan Tablets\n ], [VENDOR, MODEL, [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Trio[\\s\\-]*.*)\\s+build/i // MachSpeed Tablets\n ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*(Trinity)[\\-\\s]*(T\\d{3})\\s+build/i // Trinity Tablets\n ], [VENDOR, MODEL, [TYPE, TABLET]], [\n\n /android.+[;\\/]\\s*TU_(1491)\\s+build/i // Rotor Tablets\n ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]], [\n\n /android.+(KS(.+))\\s+build/i // Amazon Kindle Tablets\n ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [\n\n /android.+(Gigaset)[\\s\\-]+(Q.+)\\s+build/i // Gigaset Tablets\n ], [VENDOR, MODEL, [TYPE, TABLET]], [\n\n /\\s(tablet|tab)[;\\/]/i, // Unidentifiable Tablet\n /\\s(mobile)(?:[;\\/]|\\ssafari)/i // Unidentifiable Mobile\n ], [[TYPE, util.lowerize], VENDOR, MODEL], [\n\n /(android.+)[;\\/].+build/i // Generic Android Device\n ], [MODEL, [VENDOR, 'Generic']]\n\n\n /*//////////////////////////\n // TODO: move to string map\n ////////////////////////////\n\n /(C6603)/i // Sony Xperia Z C6603\n ], [[MODEL, 'Xperia Z C6603'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [\n /(C6903)/i // Sony Xperia Z 1\n ], [[MODEL, 'Xperia Z 1'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [\n\n /(SM-G900[F|H])/i // Samsung Galaxy S5\n ], [[MODEL, 'Galaxy S5'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [\n /(SM-G7102)/i // Samsung Galaxy Grand 2\n ], [[MODEL, 'Galaxy Grand 2'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [\n /(SM-G530H)/i // Samsung Galaxy Grand Prime\n ], [[MODEL, 'Galaxy Grand Prime'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [\n /(SM-G313HZ)/i // Samsung Galaxy V\n ], [[MODEL, 'Galaxy V'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [\n /(SM-T805)/i // Samsung Galaxy Tab S 10.5\n ], [[MODEL, 'Galaxy Tab S 10.5'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [\n /(SM-G800F)/i // Samsung Galaxy S5 Mini\n ], [[MODEL, 'Galaxy S5 Mini'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [\n /(SM-T311)/i // Samsung Galaxy Tab 3 8.0\n ], [[MODEL, 'Galaxy Tab 3 8.0'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [\n\n /(T3C)/i // Advan Vandroid T3C\n ], [MODEL, [VENDOR, 'Advan'], [TYPE, TABLET]], [\n /(ADVAN T1J\\+)/i // Advan Vandroid T1J+\n ], [[MODEL, 'Vandroid T1J+'], [VENDOR, 'Advan'], [TYPE, TABLET]], [\n /(ADVAN S4A)/i // Advan Vandroid S4A\n ], [[MODEL, 'Vandroid S4A'], [VENDOR, 'Advan'], [TYPE, MOBILE]], [\n\n /(V972M)/i // ZTE V972M\n ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [\n\n /(i-mobile)\\s(IQ\\s[\\d\\.]+)/i // i-mobile IQ\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n /(IQ6.3)/i // i-mobile IQ IQ 6.3\n ], [[MODEL, 'IQ 6.3'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [\n /(i-mobile)\\s(i-style\\s[\\d\\.]+)/i // i-mobile i-STYLE\n ], [VENDOR, MODEL, [TYPE, MOBILE]], [\n /(i-STYLE2.1)/i // i-mobile i-STYLE 2.1\n ], [[MODEL, 'i-STYLE 2.1'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [\n\n /(mobiistar touch LAI 512)/i // mobiistar touch LAI 512\n ], [[MODEL, 'Touch LAI 512'], [VENDOR, 'mobiistar'], [TYPE, MOBILE]], [\n\n /////////////\n // END TODO\n ///////////*/\n\n ],\n\n engine : [[\n\n /windows.+\\sedge\\/([\\w\\.]+)/i // EdgeHTML\n ], [VERSION, [NAME, 'EdgeHTML']], [\n\n /(presto)\\/([\\w\\.]+)/i, // Presto\n /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\\/([\\w\\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m\n /(khtml|tasman|links)[\\/\\s]\\(?([\\w\\.]+)/i, // KHTML/Tasman/Links\n /(icab)[\\/\\s]([23]\\.[\\d\\.]+)/i // iCab\n ], [NAME, VERSION], [\n\n /rv\\:([\\w\\.]+).*(gecko)/i // Gecko\n ], [VERSION, NAME]\n ],\n\n os : [[\n\n // Windows based\n /microsoft\\s(windows)\\s(vista|xp)/i // Windows (iTunes)\n ], [NAME, VERSION], [\n /(windows)\\snt\\s6\\.2;\\s(arm)/i, // Windows RT\n /(windows\\sphone(?:\\sos)*)[\\s\\/]?([\\d\\.\\s]+\\w)*/i, // Windows Phone\n /(windows\\smobile|windows)[\\s\\/]?([ntce\\d\\.\\s]+\\w)/i\n ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [\n /(win(?=3|9|n)|win\\s9x\\s)([nt\\d\\.]+)/i\n ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [\n\n // Mobile/Embedded OS\n /\\((bb)(10);/i // BlackBerry 10\n ], [[NAME, 'BlackBerry'], VERSION], [\n /(blackberry)\\w*\\/?([\\w\\.]+)*/i, // Blackberry\n /(tizen)[\\/\\s]([\\w\\.]+)/i, // Tizen\n /(android|webos|palm\\sos|qnx|bada|rim\\stablet\\sos|meego|contiki)[\\/\\s-]?([\\w\\.]+)*/i,\n // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki\n /linux;.+(sailfish);/i // Sailfish OS\n ], [NAME, VERSION], [\n /(symbian\\s?os|symbos|s60(?=;))[\\/\\s-]?([\\w\\.]+)*/i // Symbian\n ], [[NAME, 'Symbian'], VERSION], [\n /\\((series40);/i // Series 40\n ], [NAME], [\n /mozilla.+\\(mobile;.+gecko.+firefox/i // Firefox OS\n ], [[NAME, 'Firefox OS'], VERSION], [\n\n // Console\n /(nintendo|playstation)\\s([wids34portablevu]+)/i, // Nintendo/Playstation\n\n // GNU/Linux based\n /(mint)[\\/\\s\\(]?(\\w+)*/i, // Mint\n /(mageia|vectorlinux)[;\\s]/i, // Mageia/VectorLinux\n /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|(?=\\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\\/\\s-]?(?!chrom)([\\w\\.-]+)*/i,\n // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware\n // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus\n /(hurd|linux)\\s?([\\w\\.]+)*/i, // Hurd/Linux\n /(gnu)\\s?([\\w\\.]+)*/i // GNU\n ], [NAME, VERSION], [\n\n /(cros)\\s[\\w]+\\s([\\w\\.]+\\w)/i // Chromium OS\n ], [[NAME, 'Chromium OS'], VERSION],[\n\n // Solaris\n /(sunos)\\s?([\\w\\.]+\\d)*/i // Solaris\n ], [[NAME, 'Solaris'], VERSION], [\n\n // BSD based\n /\\s([frentopc-]{0,4}bsd|dragonfly)\\s?([\\w\\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly\n ], [NAME, VERSION],[\n\n /(haiku)\\s(\\w+)/i // Haiku\n ], [NAME, VERSION],[\n\n /cfnetwork\\/.+darwin/i,\n /ip[honead]+(?:.*os\\s([\\w]+)\\slike\\smac|;\\sopera)/i // iOS\n ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [\n\n /(mac\\sos\\sx)\\s?([\\w\\s\\.]+\\w)*/i,\n /(macintosh|mac(?=_powerpc)\\s)/i // Mac OS\n ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [\n\n // Other\n /((?:open)?solaris)[\\/\\s-]?([\\w\\.]+)*/i, // Solaris\n /(aix)\\s((\\d)(?=\\.|\\)|\\s)[\\w\\.]*)*/i, // AIX\n /(plan\\s9|minix|beos|os\\/2|amigaos|morphos|risc\\sos|openvms)/i,\n // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS\n /(unix)\\s?([\\w\\.]+)*/i // UNIX\n ], [NAME, VERSION]\n ]\n };\n\n\n /////////////////\n // Constructor\n ////////////////\n /*\n var Browser = function (name, version) {\n this[NAME] = name;\n this[VERSION] = version;\n };\n var CPU = function (arch) {\n this[ARCHITECTURE] = arch;\n };\n var Device = function (vendor, model, type) {\n this[VENDOR] = vendor;\n this[MODEL] = model;\n this[TYPE] = type;\n };\n var Engine = Browser;\n var OS = Browser;\n */\n var UAParser = function (uastring, extensions) {\n\n if (typeof uastring === 'object') {\n extensions = uastring;\n uastring = undefined;\n }\n\n if (!(this instanceof UAParser)) {\n return new UAParser(uastring, extensions).getResult();\n }\n\n var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);\n var rgxmap = extensions ? util.extend(regexes, extensions) : regexes;\n //var browser = new Browser();\n //var cpu = new CPU();\n //var device = new Device();\n //var engine = new Engine();\n //var os = new OS();\n\n this.getBrowser = function () {\n var browser = { name: undefined, version: undefined };\n mapper.rgx.call(browser, ua, rgxmap.browser);\n browser.major = util.major(browser.version); // deprecated\n return browser;\n };\n this.getCPU = function () {\n var cpu = { architecture: undefined };\n mapper.rgx.call(cpu, ua, rgxmap.cpu);\n return cpu;\n };\n this.getDevice = function () {\n var device = { vendor: undefined, model: undefined, type: undefined };\n mapper.rgx.call(device, ua, rgxmap.device);\n return device;\n };\n this.getEngine = function () {\n var engine = { name: undefined, version: undefined };\n mapper.rgx.call(engine, ua, rgxmap.engine);\n return engine;\n };\n this.getOS = function () {\n var os = { name: undefined, version: undefined };\n mapper.rgx.call(os, ua, rgxmap.os);\n return os;\n };\n this.getResult = function () {\n return {\n ua : this.getUA(),\n browser : this.getBrowser(),\n engine : this.getEngine(),\n os : this.getOS(),\n device : this.getDevice(),\n cpu : this.getCPU()\n };\n };\n this.getUA = function () {\n return ua;\n };\n this.setUA = function (uastring) {\n ua = uastring;\n //browser = new Browser();\n //cpu = new CPU();\n //device = new Device();\n //engine = new Engine();\n //os = new OS();\n return this;\n };\n return this;\n };\n\n UAParser.VERSION = LIBVERSION;\n UAParser.BROWSER = {\n NAME : NAME,\n MAJOR : MAJOR, // deprecated\n VERSION : VERSION\n };\n UAParser.CPU = {\n ARCHITECTURE : ARCHITECTURE\n };\n UAParser.DEVICE = {\n MODEL : MODEL,\n VENDOR : VENDOR,\n TYPE : TYPE,\n CONSOLE : CONSOLE,\n MOBILE : MOBILE,\n SMARTTV : SMARTTV,\n TABLET : TABLET,\n WEARABLE: WEARABLE,\n EMBEDDED: EMBEDDED\n };\n UAParser.ENGINE = {\n NAME : NAME,\n VERSION : VERSION\n };\n UAParser.OS = {\n NAME : NAME,\n VERSION : VERSION\n };\n //UAParser.Utils = util;\n\n ///////////\n // Export\n //////////\n\n\n // check js environment\n if (typeof(exports) !== UNDEF_TYPE) {\n // nodejs env\n if (typeof module !== UNDEF_TYPE && module.exports) {\n exports = module.exports = UAParser;\n }\n // TODO: test!!!!!!!!\n /*\n if (require && require.main === module && process) {\n // cli\n var jsonize = function (arr) {\n var res = [];\n for (var i in arr) {\n res.push(new UAParser(arr[i]).getResult());\n }\n process.stdout.write(JSON.stringify(res, null, 2) + '\\n');\n };\n if (process.stdin.isTTY) {\n // via args\n jsonize(process.argv.slice(2));\n } else {\n // via pipe\n var str = '';\n process.stdin.on('readable', function() {\n var read = process.stdin.read();\n if (read !== null) {\n str += read;\n }\n });\n process.stdin.on('end', function () {\n jsonize(str.replace(/\\n$/, '').split('\\n'));\n });\n }\n }\n */\n exports.UAParser = UAParser;\n } else {\n // requirejs env (optional)\n if (typeof(define) === FUNC_TYPE && define.amd) {\n define(function () {\n return UAParser;\n });\n } else if (window) {\n // browser env\n window.UAParser = UAParser;\n }\n }\n\n // jQuery/Zepto specific (optional)\n // Note:\n // In AMD env the global scope should be kept clean, but jQuery is an exception.\n // jQuery always exports to global scope, unless jQuery.noConflict(true) is used,\n // and we should catch that.\n var $ = window && (window.jQuery || window.Zepto);\n if (typeof $ !== UNDEF_TYPE) {\n var parser = new UAParser();\n $.ua = parser.getResult();\n $.ua.get = function () {\n return parser.getUA();\n };\n $.ua.set = function (uastring) {\n parser.setUA(uastring);\n var result = parser.getResult();\n for (var prop in result) {\n $.ua[prop] = result[prop];\n }\n };\n }\n\n})(typeof window === 'object' ? window : this);\n","\nvar rng;\n\nvar crypto = global.crypto || global.msCrypto; // for IE 11\nif (crypto && crypto.getRandomValues) {\n // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto\n // Moderately fast, high quality\n var _rnds8 = new Uint8Array(16);\n rng = function whatwgRNG() {\n crypto.getRandomValues(_rnds8);\n return _rnds8;\n };\n}\n\nif (!rng) {\n // Math.random()-based (RNG)\n //\n // If all else fails, use Math.random(). It's fast, but is of unspecified\n // quality.\n var _rnds = new Array(16);\n rng = function() {\n for (var i = 0, r; i < 16; i++) {\n if ((i & 0x03) === 0) r = Math.random() * 0x100000000;\n _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;\n }\n\n return _rnds;\n };\n}\n\nmodule.exports = rng;\n\n","// uuid.js\n//\n// Copyright (c) 2010-2012 Robert Kieffer\n// MIT License - http://opensource.org/licenses/mit-license.php\n\n// Unique ID creation requires a high quality random # generator. We feature\n// detect to determine the best RNG source, normalizing to a function that\n// returns 128-bits of randomness, since that's what's usually required\nvar _rng = require('./rng');\n\n// Maps for number <-> hex string conversion\nvar _byteToHex = [];\nvar _hexToByte = {};\nfor (var i = 0; i < 256; i++) {\n _byteToHex[i] = (i + 0x100).toString(16).substr(1);\n _hexToByte[_byteToHex[i]] = i;\n}\n\n// **`parse()` - Parse a UUID into it's component bytes**\nfunction parse(s, buf, offset) {\n var i = (buf && offset) || 0, ii = 0;\n\n buf = buf || [];\n s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {\n if (ii < 16) { // Don't overflow!\n buf[i + ii++] = _hexToByte[oct];\n }\n });\n\n // Zero out remaining bytes if string was short\n while (ii < 16) {\n buf[i + ii++] = 0;\n }\n\n return buf;\n}\n\n// **`unparse()` - Convert UUID byte array (ala parse()) into a string**\nfunction unparse(buf, offset) {\n var i = offset || 0, bth = _byteToHex;\n return bth[buf[i++]] + bth[buf[i++]] +\n bth[buf[i++]] + bth[buf[i++]] + '-' +\n bth[buf[i++]] + bth[buf[i++]] + '-' +\n bth[buf[i++]] + bth[buf[i++]] + '-' +\n bth[buf[i++]] + bth[buf[i++]] + '-' +\n bth[buf[i++]] + bth[buf[i++]] +\n bth[buf[i++]] + bth[buf[i++]] +\n bth[buf[i++]] + bth[buf[i++]];\n}\n\n// **`v1()` - Generate time-based UUID**\n//\n// Inspired by https://github.com/LiosK/UUID.js\n// and http://docs.python.org/library/uuid.html\n\n// random #'s we need to init node and clockseq\nvar _seedBytes = _rng();\n\n// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)\nvar _nodeId = [\n _seedBytes[0] | 0x01,\n _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]\n];\n\n// Per 4.2.2, randomize (14 bit) clockseq\nvar _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;\n\n// Previous uuid creation time\nvar _lastMSecs = 0, _lastNSecs = 0;\n\n// See https://github.com/broofa/node-uuid for API details\nfunction v1(options, buf, offset) {\n var i = buf && offset || 0;\n var b = buf || [];\n\n options = options || {};\n\n var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;\n\n // UUID timestamps are 100 nano-second units since the Gregorian epoch,\n // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so\n // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'\n // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.\n var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();\n\n // Per 4.2.1.2, use count of uuid's generated during the current clock\n // cycle to simulate higher resolution clock\n var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;\n\n // Time since last uuid creation (in msecs)\n var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;\n\n // Per 4.2.1.2, Bump clockseq on clock regression\n if (dt < 0 && options.clockseq === undefined) {\n clockseq = clockseq + 1 & 0x3fff;\n }\n\n // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new\n // time interval\n if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {\n nsecs = 0;\n }\n\n // Per 4.2.1.2 Throw error if too many uuids are requested\n if (nsecs >= 10000) {\n throw new Error('uuid.v1(): Can\\'t create more than 10M uuids/sec');\n }\n\n _lastMSecs = msecs;\n _lastNSecs = nsecs;\n _clockseq = clockseq;\n\n // Per 4.1.4 - Convert from unix epoch to Gregorian epoch\n msecs += 12219292800000;\n\n // `time_low`\n var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;\n b[i++] = tl >>> 24 & 0xff;\n b[i++] = tl >>> 16 & 0xff;\n b[i++] = tl >>> 8 & 0xff;\n b[i++] = tl & 0xff;\n\n // `time_mid`\n var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;\n b[i++] = tmh >>> 8 & 0xff;\n b[i++] = tmh & 0xff;\n\n // `time_high_and_version`\n b[i++] = tmh >>> 24 & 0xf | 0x10; // include version\n b[i++] = tmh >>> 16 & 0xff;\n\n // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)\n b[i++] = clockseq >>> 8 | 0x80;\n\n // `clock_seq_low`\n b[i++] = clockseq & 0xff;\n\n // `node`\n var node = options.node || _nodeId;\n for (var n = 0; n < 6; n++) {\n b[i + n] = node[n];\n }\n\n return buf ? buf : unparse(b);\n}\n\n// **`v4()` - Generate random UUID**\n\n// See https://github.com/broofa/node-uuid for API details\nfunction v4(options, buf, offset) {\n // Deprecated - 'format' argument, as supported in v1.2\n var i = buf && offset || 0;\n\n if (typeof(options) == 'string') {\n buf = options == 'binary' ? new Array(16) : null;\n options = null;\n }\n options = options || {};\n\n var rnds = options.random || (options.rng || _rng)();\n\n // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`\n rnds[6] = (rnds[6] & 0x0f) | 0x40;\n rnds[8] = (rnds[8] & 0x3f) | 0x80;\n\n // Copy bytes to buffer, if provided\n if (buf) {\n for (var ii = 0; ii < 16; ii++) {\n buf[i + ii] = rnds[ii];\n }\n }\n\n return buf || unparse(rnds);\n}\n\n// Export public API\nvar uuid = v4;\nuuid.v1 = v1;\nuuid.v4 = v4;\nuuid.parse = parse;\nuuid.unparse = unparse;\n\nmodule.exports = uuid;\n","/*\r\nWildEmitter.js is a slim little event emitter by @henrikjoreteg largely based\r\non @visionmedia's Emitter from UI Kit.\r\n\r\nWhy? I wanted it standalone.\r\n\r\nI also wanted support for wildcard emitters like this:\r\n\r\nemitter.on('*', function (eventName, other, event, payloads) {\r\n\r\n});\r\n\r\nemitter.on('somenamespace*', function (eventName, payloads) {\r\n\r\n});\r\n\r\nPlease note that callbacks triggered by wildcard registered events also get\r\nthe event name as the first argument.\r\n*/\r\n\r\nmodule.exports = WildEmitter;\r\n\r\nfunction WildEmitter() { }\r\n\r\nWildEmitter.mixin = function (constructor) {\r\n var prototype = constructor.prototype || constructor;\r\n\r\n prototype.isWildEmitter= true;\r\n\r\n // Listen on the given `event` with `fn`. Store a group name if present.\r\n prototype.on = function (event, groupName, fn) {\r\n this.callbacks = this.callbacks || {};\r\n var hasGroup = (arguments.length === 3),\r\n group = hasGroup ? arguments[1] : undefined,\r\n func = hasGroup ? arguments[2] : arguments[1];\r\n func._groupName = group;\r\n (this.callbacks[event] = this.callbacks[event] || []).push(func);\r\n return this;\r\n };\r\n\r\n // Adds an `event` listener that will be invoked a single\r\n // time then automatically removed.\r\n prototype.once = function (event, groupName, fn) {\r\n var self = this,\r\n hasGroup = (arguments.length === 3),\r\n group = hasGroup ? arguments[1] : undefined,\r\n func = hasGroup ? arguments[2] : arguments[1];\r\n function on() {\r\n self.off(event, on);\r\n func.apply(this, arguments);\r\n }\r\n this.on(event, group, on);\r\n return this;\r\n };\r\n\r\n // Unbinds an entire group\r\n prototype.releaseGroup = function (groupName) {\r\n this.callbacks = this.callbacks || {};\r\n var item, i, len, handlers;\r\n for (item in this.callbacks) {\r\n handlers = this.callbacks[item];\r\n for (i = 0, len = handlers.length; i < len; i++) {\r\n if (handlers[i]._groupName === groupName) {\r\n //console.log('removing');\r\n // remove it and shorten the array we're looping through\r\n handlers.splice(i, 1);\r\n i--;\r\n len--;\r\n }\r\n }\r\n }\r\n return this;\r\n };\r\n\r\n // Remove the given callback for `event` or all\r\n // registered callbacks.\r\n prototype.off = function (event, fn) {\r\n this.callbacks = this.callbacks || {};\r\n var callbacks = this.callbacks[event],\r\n i;\r\n\r\n if (!callbacks) return this;\r\n\r\n // remove all handlers\r\n if (arguments.length === 1) {\r\n delete this.callbacks[event];\r\n return this;\r\n }\r\n\r\n // remove specific handler\r\n i = callbacks.indexOf(fn);\r\n callbacks.splice(i, 1);\r\n if (callbacks.length === 0) {\r\n delete this.callbacks[event];\r\n }\r\n return this;\r\n };\r\n\r\n /// Emit `event` with the given args.\r\n // also calls any `*` handlers\r\n prototype.emit = function (event) {\r\n this.callbacks = this.callbacks || {};\r\n var args = [].slice.call(arguments, 1),\r\n callbacks = this.callbacks[event],\r\n specialCallbacks = this.getWildcardCallbacks(event),\r\n i,\r\n len,\r\n item,\r\n listeners;\r\n\r\n if (callbacks) {\r\n listeners = callbacks.slice();\r\n for (i = 0, len = listeners.length; i < len; ++i) {\r\n if (!listeners[i]) {\r\n break;\r\n }\r\n listeners[i].apply(this, args);\r\n }\r\n }\r\n\r\n if (specialCallbacks) {\r\n len = specialCallbacks.length;\r\n listeners = specialCallbacks.slice();\r\n for (i = 0, len = listeners.length; i < len; ++i) {\r\n if (!listeners[i]) {\r\n break;\r\n }\r\n listeners[i].apply(this, [event].concat(args));\r\n }\r\n }\r\n\r\n return this;\r\n };\r\n\r\n // Helper for for finding special wildcard event handlers that match the event\r\n prototype.getWildcardCallbacks = function (eventName) {\r\n this.callbacks = this.callbacks || {};\r\n var item,\r\n split,\r\n result = [];\r\n\r\n for (item in this.callbacks) {\r\n split = item.split('*');\r\n if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {\r\n result = result.concat(this.callbacks[item]);\r\n }\r\n }\r\n return result;\r\n };\r\n\r\n};\r\n\r\nWildEmitter.mixin(WildEmitter);\r\n"]} \ No newline at end of file diff --git a/bower_components/kurento-utils/js/kurento-utils.min.js b/bower_components/kurento-utils/js/kurento-utils.min.js new file mode 100755 index 0000000..84a3417 --- /dev/null +++ b/bower_components/kurento-utils/js/kurento-utils.min.js @@ -0,0 +1,56 @@ +(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.kurentoUtils = f()}})(function(){var define,module,exports;return (function(){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;o0?e.slice(0,n):e}function getSimulcastInfo(e){var n=e.getVideoTracks();if(!n.length)return logger.warn("No video tracks available in the video stream"),"";var t=["a=x-google-flag:conference","a=ssrc-group:SIM 1 2 3","a=ssrc:1 cname:localVideo","a=ssrc:1 msid:"+e.id+" "+n[0].id,"a=ssrc:1 mslabel:"+e.id,"a=ssrc:1 label:"+n[0].id,"a=ssrc:2 cname:localVideo","a=ssrc:2 msid:"+e.id+" "+n[0].id,"a=ssrc:2 mslabel:"+e.id,"a=ssrc:2 label:"+n[0].id,"a=ssrc:3 cname:localVideo","a=ssrc:3 msid:"+e.id+" "+n[0].id,"a=ssrc:3 mslabel:"+e.id,"a=ssrc:3 label:"+n[0].id];return t.push(""),t.join("\n")}function WebRtcPeer(e,n,t){function r(){if(l){l.pause();var e=p.getRemoteStreams()[0];l.srcObject=e,logger.debug("Remote stream:",e),l.load()}}function i(e){return R&&("Chrome"===browser.name||"Chromium"===browser.name?(logger.debug("Adding multicast info"),e=new RTCSessionDescription({type:e.type,sdp:removeFIDFromOffer(e.sdp)+getSimulcastInfo(u)})):logger.warn("Simulcast is only available in Chrome browser.")),e}function o(){"closed"===p.signalingState&&t('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'),u&&d&&c.showLocalVideo(),u&&p.addStream(u),f&&p.addStream(f);var n=parser.getBrowser();"sendonly"!==e||"Chrome"!==n.name&&"Chromium"!==n.name||39!==n.major||(e="sendrecv"),t()}function a(e){void 0===e&&(e=MEDIA_CONSTRAINTS),navigator.mediaDevices.getUserMedia(e).then(function(e){u=e,o()}).catch(t)}if(!(this instanceof WebRtcPeer))return new WebRtcPeer(e,n,t);WebRtcPeer.super_.call(this),n instanceof Function&&(t=n,n=void 0),n=n||{},t=(t||noop).bind(this);var s,c=this,d=n.localVideo,l=n.remoteVideo,u=n.videoStream,f=n.audioStream,g=n.mediaConstraints,p=(n.connectionConstraints,n.peerConnection),h=n.sendSource||"webcam",b=n.dataChannelConfig,m=n.dataChannels||!1,v=uuid.v4(),P=recursive({iceServers:freeice()},n.configuration),S=n.onicecandidate;S&&this.on("icecandidate",S);var w=n.oncandidategatheringdone;w&&this.on("candidategatheringdone",w);var R=n.simulcast,C=n.multistream,y=new sdpTranslator.Interop,D=[],W=!1;if(Object.defineProperties(this,{peerConnection:{get:function(){return p}},id:{value:n.id||v,writable:!1},remoteVideo:{get:function(){return l}},localVideo:{get:function(){return d}},dataChannel:{get:function(){return s}},currentFrame:{get:function(){if(l){if(l.readyStateUnifiedPlan",dumpSDP(e))),n(null,e.sdp,c.processAnswer.bind(c))}).catch(n)},this.getLocalSessionDescriptor=function(){return p.localDescription},this.getRemoteSessionDescriptor=function(){return p.remoteDescription},this.showLocalVideo=function(){d.srcObject=u,d.muted=!0},this.send=function(e){s&&"open"===s.readyState?s.send(e):logger.warn("Trying to send data over a non-existing or closed data channel")},this.processAnswer=function(e,n){n=(n||noop).bind(this);var t=new RTCSessionDescription({type:"answer",sdp:e});if(C&&usePlanB){var i=y.toPlanB(t);logger.debug("asnwer::planB",dumpSDP(i)),t=i}if(logger.debug("SDP answer received, setting remote description"),"closed"===p.signalingState)return n("PeerConnection is closed");p.setRemoteDescription(t,function(){r(),n()},n)},this.processOffer=function(e,n){n=n.bind(this);var t=new RTCSessionDescription({type:"offer",sdp:e});if(C&&usePlanB){var o=y.toPlanB(t);logger.debug("offer::planB",dumpSDP(o)),t=o}if(logger.debug("SDP offer received, setting remote description"),"closed"===p.signalingState)return n("PeerConnection is closed");p.setRemoteDescription(t).then(function(){return r()}).then(function(){return p.createAnswer()}).then(function(e){return e=i(e),logger.debug("Created SDP answer"),p.setLocalDescription(e)}).then(function(){var e=p.localDescription;C&&usePlanB&&(e=y.toUnifiedPlan(e),logger.debug("answer::origPlanB->UnifiedPlan",dumpSDP(e))),logger.debug("Local description set",e.sdp),n(null,e.sdp)}).catch(n)},"recvonly"===e||u||f?setTimeout(o,0):"webcam"===h?a(g):getScreenConstraints(h,function(e,n){if(e)return t(e);constraints=[g],constraints.unshift(n),a(recursive.apply(void 0,constraints))},v),this.on("_dispose",function(){d&&(d.pause(),d.srcObject=null,d.load(),d.muted=!1),l&&(l.pause(),l.srcObject=null,l.load()),c.removeAllListeners(),void 0!==window.cancelChooseDesktopMedia&&window.cancelChooseDesktopMedia(v)})}function createEnableDescriptor(e){var n="get"+e+"Tracks";return{enumerable:!0,get:function(){if(this.peerConnection){var e=this.peerConnection.getLocalStreams();if(e.length){for(var t,r=0;t=e[r];r++)for(var i,o=t[n](),a=0;i=o[a];a++)if(!i.enabled)return!1;return!0}}},set:function(e){function t(n){n.enabled=e}this.peerConnection.getLocalStreams().forEach(function(e){e[n]().forEach(t)})}}}function WebRtcPeerRecvonly(e,n){if(!(this instanceof WebRtcPeerRecvonly))return new WebRtcPeerRecvonly(e,n);WebRtcPeerRecvonly.super_.call(this,"recvonly",e,n)}function WebRtcPeerSendonly(e,n){if(!(this instanceof WebRtcPeerSendonly))return new WebRtcPeerSendonly(e,n);WebRtcPeerSendonly.super_.call(this,"sendonly",e,n)}function WebRtcPeerSendrecv(e,n){if(!(this instanceof WebRtcPeerSendrecv))return new WebRtcPeerSendrecv(e,n);WebRtcPeerSendrecv.super_.call(this,"sendrecv",e,n)}function harkUtils(e,n){return hark(e,n)}var freeice=require("freeice"),inherits=require("inherits"),UAParser=require("ua-parser-js"),uuid=require("uuid"),hark=require("hark"),EventEmitter=require("events").EventEmitter,recursive=require("merge").recursive.bind(void 0,!0),sdpTranslator=require("sdp-translator"),logger=window.Logger||console;try{require("kurento-browser-extensions")}catch(e){"undefined"==typeof getScreenConstraints&&(logger.warn("screen sharing is not available"),getScreenConstraints=function(e,n){n(new Error("This library is not enabled for screen sharing"))})}var MEDIA_CONSTRAINTS={audio:!0,video:{width:640,framerate:15}},ua=window&&window.navigator?window.navigator.userAgent:"",parser=new UAParser(ua),browser=parser.getBrowser(),usePlanB=!1;"Chrome"!==browser.name&&"Chromium"!==browser.name||(logger.debug(browser.name+": using SDP PlanB"),usePlanB=!0);var dumpSDP=function(e){return void 0===e||null===e?"":"type: "+e.type+"\r\n"+e.sdp};inherits(WebRtcPeer,EventEmitter),Object.defineProperties(WebRtcPeer.prototype,{enabled:{enumerable:!0,get:function(){return this.audioEnabled&&this.videoEnabled},set:function(e){this.audioEnabled=this.videoEnabled=e}},audioEnabled:createEnableDescriptor("Audio"),videoEnabled:createEnableDescriptor("Video")}),WebRtcPeer.prototype.getLocalStream=function(e){if(this.peerConnection)return this.peerConnection.getLocalStreams()[e||0]},WebRtcPeer.prototype.getRemoteStream=function(e){if(this.peerConnection)return this.peerConnection.getRemoteStreams()[e||0]},WebRtcPeer.prototype.dispose=function(){logger.debug("Disposing WebRtcPeer");var e=this.peerConnection,n=this.dataChannel;try{if(n){if("closed"===n.signalingState)return;n.close()}if(e){if("closed"===e.signalingState)return;e.getLocalStreams().forEach(streamStop),e.close()}}catch(e){logger.warn("Exception disposing webrtc peer "+e)}this.emit("_dispose")},inherits(WebRtcPeerRecvonly,WebRtcPeer),inherits(WebRtcPeerSendonly,WebRtcPeer),inherits(WebRtcPeerSendrecv,WebRtcPeer),exports.bufferizeCandidates=bufferizeCandidates,exports.WebRtcPeerRecvonly=WebRtcPeerRecvonly,exports.WebRtcPeerSendonly=WebRtcPeerSendonly,exports.WebRtcPeerSendrecv=WebRtcPeerSendrecv,exports.hark=harkUtils; +},{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid":23}],2:[function(require,module,exports){ +window.addEventListener&&(module.exports=require("./index")); +},{"./index":3}],3:[function(require,module,exports){ +var WebRtcPeer=require("./WebRtcPeer");exports.WebRtcPeer=WebRtcPeer; +},{"./WebRtcPeer":1}],4:[function(require,module,exports){ +function EventEmitter(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function isFunction(e){return"function"==typeof e}function isNumber(e){return"number"==typeof e}function isObject(e){return"object"==typeof e&&null!==e}function isUndefined(e){return void 0===e}module.exports=EventEmitter,EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._maxListeners=void 0,EventEmitter.defaultMaxListeners=10,EventEmitter.prototype.setMaxListeners=function(e){if(!isNumber(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},EventEmitter.prototype.emit=function(e){var t,i,n,s,r,o;if(this._events||(this._events={}),"error"===e&&(!this._events.error||isObject(this._events.error)&&!this._events.error.length)){if((t=arguments[1])instanceof Error)throw t;var h=new Error('Uncaught, unspecified "error" event. ('+t+")");throw h.context=t,h}if(i=this._events[e],isUndefined(i))return!1;if(isFunction(i))switch(arguments.length){case 1:i.call(this);break;case 2:i.call(this,arguments[1]);break;case 3:i.call(this,arguments[1],arguments[2]);break;default:s=Array.prototype.slice.call(arguments,1),i.apply(this,s)}else if(isObject(i))for(s=Array.prototype.slice.call(arguments,1),o=i.slice(),n=o.length,r=0;r0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},EventEmitter.prototype.on=EventEmitter.prototype.addListener,EventEmitter.prototype.once=function(e,t){function i(){this.removeListener(e,i),n||(n=!0,t.apply(this,arguments))}if(!isFunction(t))throw TypeError("listener must be a function");var n=!1;return i.listener=t,this.on(e,i),this},EventEmitter.prototype.removeListener=function(e,t){var i,n,s,r;if(!isFunction(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(i=this._events[e],s=i.length,n=-1,i===t||isFunction(i.listener)&&i.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(isObject(i)){for(r=s;r-- >0;)if(i[r]===t||i[r].listener&&i[r].listener===t){n=r;break}if(n<0)return this;1===i.length?(i.length=0,delete this._events[e]):i.splice(n,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},EventEmitter.prototype.removeAllListeners=function(e){var t,i;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(i=this._events[e],isFunction(i))this.removeListener(e,i);else if(i)for(;i.length;)this.removeListener(e,i[i.length-1]);return delete this._events[e],this},EventEmitter.prototype.listeners=function(e){return this._events&&this._events[e]?isFunction(this._events[e])?[this._events[e]]:this._events[e].slice():[]},EventEmitter.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(isFunction(t))return 1;if(t)return t.length}return 0},EventEmitter.listenerCount=function(e,t){return e.listenerCount(t)}; +},{}],5:[function(require,module,exports){ +"use strict";var normalice=require("normalice"),freeice=module.exports=function(n){function t(n,t){for(var r,u=[],o=[].concat(e[n]);o.length&&u.lengthi&&t[n]<0&&(i=t[n]);return i}var WildEmitter=require("wildemitter"),audioContextType=window.AudioContext||window.webkitAudioContext,audioContext=null;module.exports=function(e,t){var i=new WildEmitter;if(!audioContextType)return i;var t=t||{},n=t.smoothing||.1,o=t.interval||50,a=t.threshold,r=t.play,s=t.history||10,u=!0;audioContext||(audioContext=new audioContextType);var p,g,d;d=audioContext.createAnalyser(),d.fftSize=512,d.smoothingTimeConstant=n,g=new Float32Array(d.fftSize),e.jquery&&(e=e[0]),e instanceof HTMLAudioElement||e instanceof HTMLVideoElement?(p=audioContext.createMediaElementSource(e),void 0===r&&(r=!0),a=a||-50):(p=audioContext.createMediaStreamSource(e),a=a||-50),p.connect(d),r&&d.connect(audioContext.destination),i.speaking=!1,i.setThreshold=function(e){a=e},i.setInterval=function(e){o=e},i.stop=function(){u=!1,i.emit("volume_change",-100,a),i.speaking&&(i.speaking=!1,i.emit("stopped_speaking"))},i.speakingHistory=[];for(var l=0;la&&!i.speaking){for(var n=i.speakingHistory.length-3;n=2&&(i.speaking=!0,i.emit("speaking"))}else if(ea)),f()}},o)};return f(),i}; +},{"wildemitter":24}],9:[function(require,module,exports){ +"function"==typeof Object.create?module.exports=function(t,e){t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})}:module.exports=function(t,e){t.super_=e;var o=function(){};o.prototype=e.prototype,t.prototype=new o,t.prototype.constructor=t}; +},{}],10:[function(require,module,exports){ + +},{}],11:[function(require,module,exports){ +!function(e){function o(e,r){if("object"!==n(e))return r;for(var t in r)"object"===n(e[t])&&"object"===n(r[t])?e[t]=o(e[t],r[t]):e[t]=r[t];return e}function r(e,r,c){var u=c[0],f=c.length;(e||"object"!==n(u))&&(u={});for(var i=0;i1&&(n=t[1],t=t[0].split(":"),l.username=t[0],l.credential=(e||{}).credential||t[1]||""),l.url=r+n,l.urls=[l.url],l):e):e}; +},{}],13:[function(require,module,exports){ +var grammar=module.exports={v:[{name:"version",reg:/^(\d*)$/}],o:[{name:"origin",reg:/^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,names:["username","sessionId","sessionVersion","netType","ipVer","address"],format:"%s %s %d %s IP%d %s"}],s:[{name:"name"}],i:[{name:"description"}],u:[{name:"uri"}],e:[{name:"email"}],p:[{name:"phone"}],z:[{name:"timezones"}],r:[{name:"repeats"}],t:[{name:"timing",reg:/^(\d*) (\d*)/,names:["start","stop"],format:"%d %d"}],c:[{name:"connection",reg:/^IN IP(\d) (\S*)/,names:["version","ip"],format:"IN IP%d %s"}],b:[{push:"bandwidth",reg:/^(TIAS|AS|CT|RR|RS):(\d*)/,names:["type","limit"],format:"%s:%s"}],m:[{reg:/^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,names:["type","port","protocol","payloads"],format:"%s %d %s %s"}],a:[{push:"rtp",reg:/^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,names:["payload","codec","rate","encoding"],format:function(e){return e.encoding?"rtpmap:%d %s/%s/%s":e.rate?"rtpmap:%d %s/%s":"rtpmap:%d %s"}},{push:"fmtp",reg:/^fmtp:(\d*) ([\S| ]*)/,names:["payload","config"],format:"fmtp:%d %s"},{name:"control",reg:/^control:(.*)/,format:"control:%s"},{name:"rtcp",reg:/^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,names:["port","netType","ipVer","address"],format:function(e){return null!=e.address?"rtcp:%d %s IP%d %s":"rtcp:%d"}},{push:"rtcpFbTrrInt",reg:/^rtcp-fb:(\*|\d*) trr-int (\d*)/,names:["payload","value"],format:"rtcp-fb:%d trr-int %d"},{push:"rtcpFb",reg:/^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,names:["payload","type","subtype"],format:function(e){return null!=e.subtype?"rtcp-fb:%s %s %s":"rtcp-fb:%s %s"}},{push:"ext",reg:/^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,names:["value","uri","config"],format:function(e){return null!=e.config?"extmap:%s %s %s":"extmap:%s %s"}},{push:"crypto",reg:/^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,names:["id","suite","config","sessionConfig"],format:function(e){return null!=e.sessionConfig?"crypto:%d %s %s %s":"crypto:%d %s %s"}},{name:"setup",reg:/^setup:(\w*)/,format:"setup:%s"},{name:"mid",reg:/^mid:([^\s]*)/,format:"mid:%s"},{name:"msid",reg:/^msid:(.*)/,format:"msid:%s"},{name:"ptime",reg:/^ptime:(\d*)/,format:"ptime:%d"},{name:"maxptime",reg:/^maxptime:(\d*)/,format:"maxptime:%d"},{name:"direction",reg:/^(sendrecv|recvonly|sendonly|inactive)/},{name:"icelite",reg:/^(ice-lite)/},{name:"iceUfrag",reg:/^ice-ufrag:(\S*)/,format:"ice-ufrag:%s"},{name:"icePwd",reg:/^ice-pwd:(\S*)/,format:"ice-pwd:%s"},{name:"fingerprint",reg:/^fingerprint:(\S*) (\S*)/,names:["type","hash"],format:"fingerprint:%s %s"},{push:"candidates",reg:/^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,names:["foundation","component","transport","priority","ip","port","type","raddr","rport","tcptype","generation"],format:function(e){var r="candidate:%s %d %s %d %s %d typ %s";return r+=null!=e.raddr?" raddr %s rport %d":"%v%v",r+=null!=e.tcptype?" tcptype %s":"%v",null!=e.generation&&(r+=" generation %d"),r}},{name:"endOfCandidates",reg:/^(end-of-candidates)/},{name:"remoteCandidates",reg:/^remote-candidates:(.*)/,format:"remote-candidates:%s"},{name:"iceOptions",reg:/^ice-options:(\S*)/,format:"ice-options:%s"},{push:"ssrcs",reg:/^ssrc:(\d*) ([\w_]*):(.*)/,names:["id","attribute","value"],format:"ssrc:%d %s:%s"},{push:"ssrcGroups",reg:/^ssrc-group:(\w*) (.*)/,names:["semantics","ssrcs"],format:"ssrc-group:%s %s"},{name:"msidSemantic",reg:/^msid-semantic:\s?(\w*) (\S*)/,names:["semantic","token"],format:"msid-semantic: %s %s"},{push:"groups",reg:/^group:(\w*) (.*)/,names:["type","mids"],format:"group:%s %s"},{name:"rtcpMux",reg:/^(rtcp-mux)/},{name:"rtcpRsize",reg:/^(rtcp-rsize)/},{push:"invalid",names:["value"]}]};Object.keys(grammar).forEach(function(e){grammar[e].forEach(function(e){e.reg||(e.reg=/(.*)/),e.format||(e.format="%s")})}); +},{}],14:[function(require,module,exports){ +var parser=require("./parser"),writer=require("./writer");exports.write=writer,exports.parse=parser.parse,exports.parseFmtpConfig=parser.parseFmtpConfig,exports.parsePayloads=parser.parsePayloads,exports.parseRemoteCandidates=parser.parseRemoteCandidates; +},{"./parser":15,"./writer":16}],15:[function(require,module,exports){ +var toIntIfInt=function(t){return String(Number(t))===t?Number(t):t},attachProperties=function(t,r,e,n){if(n&&!e)r[n]=toIntIfInt(t[1]);else for(var a=0;a=a)return n;var u=e[r];switch(r+=1,n){case"%%":return"%";case"%s":return String(u);case"%d":return Number(u);case"%v":return""}})},makeLine=function(n,r,e){var a=r.format instanceof Function?r.format(r.push?e:e[r.name]):r.format,u=[n+"="+a];if(r.names)for(var m=0;m3||!i.media.every(function(e){return-1!==["video","audio","data"].indexOf(e.mid)}))return console.warn("This description does not look like Plan B."),e;var t=[];i.media.forEach(function(e){t.push(e.mid)});var o=!1;if(void 0!==i.groups&&Array.isArray(i.groups)&&(o=i.groups.every(function(e){return"BUNDLE"!==e.type||arrayEquals.apply(e.mids.sort(),[t.sort()])})),!o){var n=!1;if(i.media.forEach(function(e){"inactive"!==e.direction&&(n=!0)}),n)throw new Error("Cannot convert to Unified Plan because m-lines that are not bundled were found.")}var s;if("answer"===e.type)s="offer";else{if("offer"!==e.type)throw new Error("Type '"+e.type+"' not supported.");s="answer"}var a;void 0!==this.cache[s]&&(a=transform.parse(this.cache[s]));var d,c,p,u,f={audio:{},video:{}},m={},y=0,l=0,v={},h={},w={},g={};if(i.media.forEach(function(i){if(("string"!=typeof i.rtcpMux||"rtcp-mux"!==i.rtcpMux)&&"inactive"!==i.direction)throw new Error("Cannot convert to Unified Plan because m-lines without the rtcp-mux attribute were found.");if("application"===i.type)return void(m[i.mid]=i);var t=i.sources,o=i.ssrcGroups,n=i.port;if(void 0!==i.candidates&&(d=void 0!==d?d.concat(i.candidates):i.candidates),void 0!==c&&void 0!==i.iceUfrag&&c!=i.iceUfrag)throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n\tLast iceUfrag: "+c+"\n\tNew iceUfrag: "+i.iceUfrag);if(void 0!==i.iceUfrag&&(c=i.iceUfrag),void 0!==p&&void 0!==i.icePwd&&p!=i.icePwd)throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n\tLast icePwd: "+p+"\n\tNew icePwd: "+i.icePwd);if(void 0!==i.icePwd&&(p=i.icePwd),void 0!==u&&void 0!==i.fingerprint&&(u.type!=i.fingerprint.type||u.hash!=i.fingerprint.hash))throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n\tLast fingerprint: "+JSON.stringify(u)+"\n\tNew fingerprint: "+JSON.stringify(i.fingerprint));void 0!==i.fingerprint&&(u=i.fingerprint),h[i.type]=i.payloads,w[i.type]=i.rtcpFb,g[i.type]=i.rtp;var s={};void 0!==o&&Array.isArray(o)&&o.forEach(function(e){void 0!==e.ssrcs&&Array.isArray(e.ssrcs)&&e.ssrcs.forEach(function(r){void 0===s[r]&&(s[r]=[]),s[r].push(e)})});var E={};if("object"==typeof t)delete i.sources,delete i.ssrcGroups,delete i.candidates,delete i.iceUfrag,delete i.icePwd,delete i.fingerprint,delete i.port,delete i.mid,Object.keys(t).forEach(function(o){var h;if("offer"===e.type&&!t[o].msid)return void(f[i.type][o]=t[o]);void 0!==s[o]&&Array.isArray(s[o])&&s[o].some(function(e){return e.ssrcs.some(function(e){if("object"==typeof E[e])return h=E[e],!0})}),"object"==typeof h?(h.sources[o]=t[o],delete t[o].msid):(h=Object.create(i),E[o]=h,void 0!==t[o].msid&&(h.msid=t[o].msid,delete t[o].msid),h.sources={},h.sources[o]=t[o],h.ssrcGroups=s[o],void 0!==a&&void 0!==a.media&&Array.isArray(a.media)&&a.media.forEach(function(e){"object"==typeof e.sources&&Object.keys(e.sources).forEach(function(r){r===o&&(h.mid=e.mid)})}),void 0===h.mid&&(h.mid=[i.type,"-",o].join("")),h.candidates=d,h.iceUfrag=c,h.icePwd=p,h.fingerprint=u,h.port=n,m[h.mid]=h,v[l]=h.sources,r.cache.mlU2BMap[l]=y,void 0===r.cache.mlB2UMap[y]&&(r.cache.mlB2UMap[y]=l),l++)});else{var U=i;U.candidates=d,U.iceUfrag=c,U.icePwd=p,U.fingerprint=u,U.port=n,m[U.mid]=U,r.cache.mlU2BMap[l]=y,void 0===r.cache.mlB2UMap[y]&&(r.cache.mlB2UMap[y]=l)}y++}),i.media=[],t=[],"answer"===e.type)for(var E=0;E0&&null===(t=r.getFirstSendingIndexFromAnswer(e)))for(var o=0;ot){var n=i.media[t];Object.keys(f[e]).forEach(function(r){n.sources&&n.sources[r]&&console.warn("Replacing an existing SSRC."),n.sources||(n.sources={}),n.sources[r]=f[e][r]})}}}),void 0!==i.groups&&i.groups.some(function(e){if("BUNDLE"===e.type)return e.mids=t.join(" "),!0}),i.msidSemantic={semantic:"WMS",token:"*"};var b=transform.write(i);return this.cache[e.type]=b,new RTCSessionDescription({type:e.type,sdp:b})}; +},{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){ +var transform=require("sdp-transform");exports.write=function(s,r){return void 0!==s&&void 0!==s.media&&Array.isArray(s.media)&&s.media.forEach(function(s){void 0!==s.sources&&0!==Object.keys(s.sources).length&&(s.ssrcs=[],Object.keys(s.sources).forEach(function(r){var o=s.sources[r];Object.keys(o).forEach(function(i){s.ssrcs.push({id:r,attribute:i,value:o[i]})})}),delete s.sources),void 0!==s.ssrcGroups&&Array.isArray(s.ssrcGroups)&&s.ssrcGroups.forEach(function(s){void 0!==s.ssrcs&&Array.isArray(s.ssrcs)&&(s.ssrcs=s.ssrcs.join(" "))})}),void 0!==s&&void 0!==s.groups&&Array.isArray(s.groups)&&s.groups.forEach(function(s){void 0!==s.mids&&Array.isArray(s.mids)&&(s.mids=s.mids.join(" "))}),transform.write(s,r)},exports.parse=function(s){var r=transform.parse(s);return void 0!==r&&void 0!==r.media&&Array.isArray(r.media)&&r.media.forEach(function(s){void 0!==s.ssrcs&&Array.isArray(s.ssrcs)&&(s.sources={},s.ssrcs.forEach(function(r){s.sources[r.id]||(s.sources[r.id]={}),s.sources[r.id][r.attribute]=r.value}),delete s.ssrcs),void 0!==s.ssrcGroups&&Array.isArray(s.ssrcGroups)&&s.ssrcGroups.forEach(function(s){"string"==typeof s.ssrcs&&(s.ssrcs=s.ssrcs.split(" "))})}),void 0!==r&&void 0!==r.groups&&Array.isArray(r.groups)&&r.groups.forEach(function(s){"string"==typeof s.mids&&(s.mids=s.mids.split(" "))}),r}; +},{"sdp-transform":14}],21:[function(require,module,exports){ +!function(i,s){"use strict";var e="model",o="name",r="type",n="vendor",a="version",t="mobile",d="tablet",l={extend:function(i,s){var e={};for(var o in i)s[o]&&s[o].length%2==0?e[o]=s[o].concat(i[o]):e[o]=i[o];return e},has:function(i,s){return"string"==typeof i&&-1!==s.toLowerCase().indexOf(i.toLowerCase())},lowerize:function(i){return i.toLowerCase()},major:function(i){return"string"==typeof i?i.replace(/[^\d\.]/g,"").split(".")[0]:void 0},trim:function(i){return i.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}},w={rgx:function(i,s){for(var e,o,r,n,a,t,d=0;d0?2==n.length?"function"==typeof n[1]?this[n[0]]=n[1].call(this,t):this[n[0]]=n[1]:3==n.length?"function"!=typeof n[1]||n[1].exec&&n[1].test?this[n[0]]=t?t.replace(n[1],n[2]):void 0:this[n[0]]=t?n[1].call(this,t,n[2]):void 0:4==n.length&&(this[n[0]]=t?n[3].call(this,t.replace(n[1],n[2])):void 0):this[n]=t||void 0;d+=2}},str:function(i,s){for(var e in s)if("object"==typeof s[e]&&s[e].length>0){for(var o=0;o>>((3&n)<<3)&255;return _rnds}}module.exports=rng; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],23:[function(require,module,exports){ +function parse(e,s,r){var t=s&&r||0,n=0;for(s=s||[],e.toLowerCase().replace(/[0-9a-f]{2}/g,function(e){n<16&&(s[t+n++]=_hexToByte[e])});n<16;)s[t+n++]=0;return s}function unparse(e,s){var r=s||0,t=_byteToHex;return t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]}function v1(e,s,r){var t=s&&r||0,n=s||[];e=e||{};var o=void 0!==e.clockseq?e.clockseq:_clockseq,a=void 0!==e.msecs?e.msecs:(new Date).getTime(),u=void 0!==e.nsecs?e.nsecs:_lastNSecs+1,c=a-_lastMSecs+(u-_lastNSecs)/1e4;if(c<0&&void 0===e.clockseq&&(o=o+1&16383),(c<0||a>_lastMSecs)&&void 0===e.nsecs&&(u=0),u>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");_lastMSecs=a,_lastNSecs=u,_clockseq=o,a+=122192928e5;var i=(1e4*(268435455&a)+u)%4294967296;n[t++]=i>>>24&255,n[t++]=i>>>16&255,n[t++]=i>>>8&255,n[t++]=255&i;var _=a/4294967296*1e4&268435455;n[t++]=_>>>8&255,n[t++]=255&_,n[t++]=_>>>24&15|16,n[t++]=_>>>16&255,n[t++]=o>>>8|128,n[t++]=255&o;for(var d=e.node||_nodeId,v=0;v<6;v++)n[t+v]=d[v];return s||unparse(n)}function v4(e,s,r){var t=s&&r||0;"string"==typeof e&&(s="binary"==e?new Array(16):null,e=null),e=e||{};var n=e.random||(e.rng||_rng)();if(n[6]=15&n[6]|64,n[8]=63&n[8]|128,s)for(var o=0;o<16;o++)s[t+o]=n[o];return s||unparse(n)}for(var _rng=require("./rng"),_byteToHex=[],_hexToByte={},i=0;i<256;i++)_byteToHex[i]=(i+256).toString(16).substr(1),_hexToByte[_byteToHex[i]]=i;var _seedBytes=_rng(),_nodeId=[1|_seedBytes[0],_seedBytes[1],_seedBytes[2],_seedBytes[3],_seedBytes[4],_seedBytes[5]],_clockseq=16383&(_seedBytes[6]<<8|_seedBytes[7]),_lastMSecs=0,_lastNSecs=0,uuid=v4;uuid.v1=v1,uuid.v4=v4,uuid.parse=parse,uuid.unparse=unparse,module.exports=uuid; +},{"./rng":22}],24:[function(require,module,exports){ +function WildEmitter(){}module.exports=WildEmitter,WildEmitter.mixin=function(t){var l=t.prototype||t;l.isWildEmitter=!0,l.on=function(t,l,i){this.callbacks=this.callbacks||{};var s=3===arguments.length,c=s?arguments[1]:void 0,a=s?arguments[2]:arguments[1];return a._groupName=c,(this.callbacks[t]=this.callbacks[t]||[]).push(a),this},l.once=function(t,l,i){function s(){c.off(t,s),h.apply(this,arguments)}var c=this,a=3===arguments.length,e=a?arguments[1]:void 0,h=a?arguments[2]:arguments[1];return this.on(t,e,s),this},l.releaseGroup=function(t){this.callbacks=this.callbacks||{};var l,i,s,c;for(l in this.callbacks)for(c=this.callbacks[l],i=0,s=c.length;iUnique Room URL

Copy following private URL:

You can share this private-session URI with fellows using email or social networks.

", + type: 'popup', + width: popup_width, + height: popup_height, + top: parseInt((screen.height / 2) - (popup_height / 2)), + left: parseInt((screen.width / 2) - (popup_width / 2)), + focused: true + }); + } +} + +function onOfferPresenter(error, offerSdp) { + if (error) return onError(error); + + var message = { + sdpOffer : offerSdp, + room: 'main' + }; + + socket.emit('presenter', message); +} + +/* function startSharing(){ if(!window.stream){ console.log('windos.stream not found'); @@ -481,10 +591,19 @@ function startSharing(){ websocket = io('https://kurento.fishrungames.com/'); - websocket.on('connect', function(data){ - + websocket.on('connect', function(socket){ console.log('Connected to socket.') - + websocket.emit('stream', true); + }); + + websocket.on('error', function(data){ + setDefaults(); + chrome.runtime.reload(); + }); + + websocket.on('disconnect', function(data){ + setDefaults(); + chrome.runtime.reload(); }); var options = {mimeType: 'video/webm;codecs=vp9'}; @@ -500,7 +619,8 @@ function startSharing(){ options = {mimeType: ''}; } } - } + } + try { mediaRecorder = new MediaRecorder(window.stream, options); } catch (e) { @@ -516,12 +636,11 @@ function startSharing(){ mediaRecorder.start(60); // collect data per 60ms } - +*/ function handleDataAvailable(event) { if (event.data && event.data.size > 0) { //recordedBlobs.push(event.data); websocket.emit('blob', event.data); - } } @@ -562,7 +681,7 @@ function gotStream(stream) { top: parseInt(screen.height), left: parseInt(screen.width) }, function(win) { - var background_page_id = win.id; + var a = win.id; setTimeout(function() { chrome.windows.remove(background_page_id); @@ -698,8 +817,8 @@ function setupRTCMultiConnection(stream) { // www.RTCMultiConnection.org/docs/openSignalingChannel/ var onMessageCallbacks = {}; - - var websocket = io('https://kurento.fishrungames.com/'); + // NotUsed + //var websocket = ws// @@ -829,6 +948,7 @@ function setDefaults() { connection.attachStreams = []; } + roomHash = ''; chrome.browserAction.setIcon({ path: 'images/desktopCapture22.png' }); diff --git a/manifest.json b/manifest.json index e50cf43..9b0fd14 100755 --- a/manifest.json +++ b/manifest.json @@ -9,6 +9,7 @@ "background":{ "scripts":[ "socket.io.js", + "bower_components/kurento-utils/js/kurento-utils.js", "RTCMultiConnection.js", "CodecsHandler.js", "IceServersHandler.js",