diff --git a/Gruntfile.js b/Gruntfile.js index 61060a3..9dea07d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -115,13 +115,13 @@ module.exports = function(grunt) { }, includereplace: { - production: { + withGoogle: { options: { // Task-specific options go here. - prefix: '@@', + prefix: '@Goo@', includesDir: '.', processIncludeContents: function (includeContents, localVars, filePath) { - if (filePath.indexOf(grunt.config.get('googleAdapterPath')) != -1) { + if (filePath.indexOf(grunt.config.get('googleAdapterPath')) !== -1) { // Indent file and indent Google's exports return includeContents // Comment export @@ -141,7 +141,20 @@ module.exports = function(grunt) { ], // Destination directory to copy files to dest: './' - } + }, + withPluginInfo: { + options: { + // Task-specific options go here. + prefix: '@Tem@', + includesDir: '<%= pluginInfoRoot %>/', + }, + // Files to perform replacements and includes with + src: [ + '<%= production %>/*.js', + ], + // Destination directory to copy files to + dest: './' + }, }, jshint: { @@ -159,7 +172,8 @@ module.exports = function(grunt) { node: true }, grunt.file.readJSON('.jshintrc')), src: [ - 'tests/*_test.js' + 'tests/*.js', + 'tests/unit/*.js' ] }, app: { @@ -279,6 +293,13 @@ module.exports = function(grunt) { grunt.log.writeln('bamboo/vars file successfully created'); }); + grunt.registerTask('CheckSubmodules', 'Checks that third_party/adapter is properly checked out', function() { + if(!grunt.file.exists(grunt.config.get('googleAdapterPath'))) { + grunt.fail.fatal('Couldn\'t find ' + grunt.config.get('googleAdapterPath') + '\n' + + 'Output would be incomplete. Did you remember to initialize submodules?\nPlease run: git submodule update --init'); + } + }); + grunt.registerTask('CheckPluginInfo', 'Checks for existing config file', function() { var fullPath = grunt.config.get('pluginInfoRoot') + '/' + grunt.config.get('pluginInfoFile'); grunt.verbose.writeln('Checking that the plugin info file exists.'); @@ -290,18 +311,24 @@ module.exports = function(grunt) { } }); - grunt.registerTask('dev', [ - 'CheckPluginInfo', - 'versionise', - 'clean:production', - 'concat', - 'replace:production', - 'includereplace:production', - 'uglify' - ]); + // NOTE(J-O) Prep for webrtc-adapter 0.2.10, will need to be compiled + grunt.registerTask('webrtc-adapter', 'Build the webrtc-adapter submodule', function() { + grunt.verbose.writeln('Spawning child process to compile webrtc-adapter subgrunt.'); + var done = this.async(); + var child = grunt.util.spawn({ + grunt: true, + args: ['--gruntfile', './third_party/adapter/Gruntfile.js', 'build'], + opts: {stdio: 'inherit'}, + }, function(error, result) {}); + child.on('close', function (code) { + done(code === 0); + }); + }); grunt.registerTask('publish', [ + 'CheckSubmodules', 'CheckPluginInfo', + // 'webrtc-adapter', 'versionise', 'clean:production', 'concat', diff --git a/bower.json b/bower.json index 5433cfe..bf83468 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "adapterjs", "description": "Creating a common API for WebRTC in the browser", - "version": "0.13.0", + "version": "0.13.1", "homepage": "https://temasys.github.io/", "author": { "name": "Temasys Communications Pte. Ltd.", diff --git a/package.json b/package.json index 6db0089..d10899a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "adapterjs", "description": "Creating a common API for WebRTC in the browser", - "version": "0.13.0", + "version": "0.13.1", "homepage": "https://temasys.github.io/", "author": { "name": "Temasys Communications Pte. Ltd.", diff --git a/publish/adapter.debug.js b/publish/adapter.debug.js index b2ab827..4cfed27 100644 --- a/publish/adapter.debug.js +++ b/publish/adapter.debug.js @@ -1,4 +1,4 @@ -/*! adapterjs - v0.13.0 - 2016-01-08 */ +/*! adapterjs - v0.13.1 - 2016-03-15 */ // Adapter's interface. var AdapterJS = AdapterJS || {}; @@ -17,7 +17,7 @@ AdapterJS.options = AdapterJS.options || {}; // AdapterJS.options.hidePluginInstallPrompt = true; // AdapterJS version -AdapterJS.VERSION = '0.13.0'; +AdapterJS.VERSION = '0.13.1'; // This function will be called when the WebRTC API is ready to be used // Whether it is the native implementation (Chrome, Firefox, Opera) or @@ -58,6 +58,7 @@ AdapterJS.webRTCReady = function (callback) { AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; // The object to store plugin information +/* jshint ignore:start */ AdapterJS.WebRTCPlugin.pluginInfo = { prefix : 'Tem', plugName : 'TemWebRTCPlugin', @@ -74,6 +75,7 @@ if(!!navigator.platform.match(/^Mac/i)) { else if(!!navigator.platform.match(/^Win/i)) { AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; } +/* jshint ignore:end */ AdapterJS.WebRTCPlugin.TAGS = { NONE : 'none', @@ -160,16 +162,14 @@ AdapterJS.WebRTCPlugin.callWhenPluginReady = null; // This function is the only private function that is not encapsulated to // allow the plugin method to be called. __TemWebRTCReady0 = function () { - webrtcDetectedVersion = AdapterJS.WebRTCPlugin.plugin.version; - if (document.readyState === 'complete') { AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } else { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { + var timer = setInterval(function () { if (document.readyState === 'complete') { // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); + clearInterval(timer); AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } @@ -240,65 +240,62 @@ AdapterJS.isDefined = null; // This sets: // - webrtcDetectedBrowser: The browser agent name. // - webrtcDetectedVersion: The browser version. +// - webrtcMinimumVersion: The minimum browser version still supported by AJS. // - webrtcDetectedType: The types of webRTC support. // - 'moz': Mozilla implementation of webRTC. // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. AdapterJS.parseWebrtcDetectedBrowser = function () { - var hasMatch, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + var hasMatch = null; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (typeof InstallTrigger !== 'undefined') { + // Firefox 1.0+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'moz'; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + // Safari + webrtcDetectedBrowser = 'safari'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; + hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (/*@cc_on!@*/false || !!document.documentMode) { + // Internet Explorer 6-11 webrtcDetectedBrowser = 'IE'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 9; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); + if (!webrtcDetectedVersion) { + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } + } else if (!!window.StyleMedia) { + // Edge 20+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = ''; + } else if (!!window.chrome && !!window.chrome.webstore) { + // Chrome 1+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'webkit'; + } else if ((webrtcDetectedBrowser === 'chrome'|| webrtcDetectedBrowser === 'opera') && + !!window.CSS) { + // Blink engine detection + webrtcDetectedBrowser = 'blink'; + // TODO: detected WebRTC version } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; -// To fix configuration as some browsers does not support -// the 'urls' attribute. -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; }; AdapterJS.addEvent = function(elem, evnt, func) { @@ -336,7 +333,7 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe i.style.transition = 'all .5s ease-out'; } document.body.appendChild(i); - c = (i.contentWindow) ? i.contentWindow : + var c = (i.contentWindow) ? i.contentWindow : (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; c.document.open(); c.document.write(' -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; + }; + + // Extracts DTLS parameters from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the fingerprint line as input. See also getIceParameters. + SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. + var fpLine = lines.filter(function(line) { + return line.indexOf('a=fingerprint:') === 0; + })[0].substr(14); + // Note: a=setup line is ignored since we use the 'auto' role. + var dtlsParameters = { + role: 'auto', + fingerprints: [{ + algorithm: fpLine.split(' ')[0], + value: fpLine.split(' ')[1] + }] + }; + return dtlsParameters; + }; + + // Serializes DTLS parameters to SDP. + SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + // Parses ICE information from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the ice-ufrag and ice-pwd lines as input. + SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; + }; + + // Serializes ICE parameters to SDP. + SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; + }; + + // Parses the SDP media section and returns RTCRtpParameters. + SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + } + } + // FIXME: parse headerExtensions, fecMechanisms and rtcp. + return description; + }; + + // Generates parts of the SDP media section describing the capabilities / parameters. + SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFtmp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + // FIXME: add headerExtensions, fecMechanismş and rtcp. + sdp += 'a=rtcp-mux\r\n'; + return sdp; + }; + + SDPUtils.writeSessionBoilerplate = function() { + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + }; + + SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. + if (transceiver.rtpSender) { + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid; + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' + + localCName + '\r\n'; + return sdp; + }; + + // Gets the direction from the mediaSection or the sessionpart. + SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; + }; + + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + + window.RTCPeerConnection = function(config) { + var self = this; + + this.onicecandidate = null; + this.onaddstream = null; + this.onremovestream = null; + this.onsignalingstatechange = null; + this.oniceconnectionstatechange = null; + this.onnegotiationneeded = null; + this.ondatachannel = null; + + this.localStreams = []; + this.remoteStreams = []; + this.getLocalStreams = function() { return self.localStreams; }; + this.getRemoteStreams = function() { return self.remoteStreams; }; + + this.localDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.remoteDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + + this.iceOptions = { + gatherPolicy: 'all', + iceServers: [] + }; + if (config && config.iceTransportPolicy) { + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + this.iceOptions.gatherPolicy = config.iceTransportPolicy; + break; + case 'none': + // FIXME: remove once implementation and spec have added this. + throw new TypeError('iceTransportPolicy "none" not supported'); + } + } + if (config && config.iceServers) { + // Edge does not like + // 1) stun: + // 2) turn: that does not have all of turn:host:port?transport=udp + // 3) an array of urls + config.iceServers.forEach(function(server) { + if (server.urls) { + var url; + if (typeof(server.urls) === 'string') { + url = server.urls; + } else { + url = server.urls[0]; + } + if (url.indexOf('transport=udp') !== -1) { + self.iceServers.push({ + username: server.username, + credential: server.credential, + urls: url + }); + } + } + }); + } + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + // since the iceGatherer is currently created in createOffer but we + // must not emit candidates until after setLocalDescription we buffer + // them in this array. + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { + var self = this; + // FIXME: need to apply ice candidates in a way which is async but in-order + this._localIceCandidatesBuffer.forEach(function(event) { + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + }); + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype.addStream = function(stream) { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + this.localStreams.push(stream.clone()); + this._maybeFireNegotiationNeeded(); + }; + + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this._maybeFireNegotiationNeeded(); + } + }; + + // Determines the intersection of local and remote capabilities. + window.RTCPeerConnection.prototype._getCommonCapabilities = + function(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate && + lCodec.numChannels === rCodec.numChannels) { + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // FIXME: also need to determine intersection between + // .rtcpFeedback and .parameters + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; + }; + + // Create ICE gatherer, ICE transport and DTLS transport. + window.RTCPeerConnection.prototype._createIceAndDtlsTransports = + function(mid, sdpMLineIndex) { + var self = this; + var iceGatherer = new RTCIceGatherer(self.iceOptions); + var iceTransport = new RTCIceTransport(iceGatherer); + iceGatherer.onlocalcandidate = function(evt) { + var event = {}; + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + if (!cand || Object.keys(cand).length === 0) { + // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet. + if (iceGatherer.state === undefined) { + iceGatherer.state = 'completed'; + } + + // Emit a candidate with type endOfCandidates to make the samples work. + // Edge requires addIceCandidate with this empty candidate to start checking. + // The real solution is to signal end-of-candidates to the other side when + // getting the null candidate but some apps (like the samples) don't do that. + event.candidate.candidate = + 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; + } else { + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = iceTransport.component === 'RTCP' ? 2 : 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + // FIXME: update .localDescription with candidate and (potentially) end-of-candidates. + // To make this harder, the gatherer might emit candidates before localdescription + // is set. To make things worse, gather.getLocalCandidates still errors in + // Edge 10547 when no candidates have been gathered yet. + + if (self.onicecandidate !== null) { + // Emit candidate if localDescription is set. + // Also emits null candidate when all gatherers are complete. + if (self.localDescription && self.localDescription.type === '') { + self._localIceCandidatesBuffer.push(event); + if (complete) { + self._localIceCandidatesBuffer.push({}); + } + } else { + self.onicecandidate(event); + if (complete) { + self.onicecandidate({}); + } + } + } + }; + iceTransport.onicestatechange = function() { + self._updateConnectionState(); + }; + + var dtlsTransport = new RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + self._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itself. + dtlsTransport.state = 'failed'; + self._updateConnectionState(); + }; + + return { + iceGatherer: iceGatherer, + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Start the RTP Sender and Receiver for a transceiver. + window.RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = this._getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = [{ + ssrc: transceiver.sendSsrc + }]; + params.rtcp = { + cname: localCName, + ssrc: transceiver.recvSsrc + }; + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver) { + params.encodings = [{ + ssrc: transceiver.recvSsrc + }]; + params.rtcp = { + cname: transceiver.cname, + ssrc: transceiver.sendSsrc + }; + transceiver.rtpReceiver.receive(params); + } + }; + + window.RTCPeerConnection.prototype.setLocalDescription = + function(description) { + var self = this; + if (description.type === 'offer') { + if (!this._pendingOffer) { + } else { + this.transceivers = this._pendingOffer; + delete this._pendingOffer; + } + } else if (description.type === 'answer') { + var sections = SDPUtils.splitSections(self.remoteDescription.sdp); + var sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = self.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + var rejected = mediaSection.split('\n', 1)[0] + .split(' ', 2)[1] === '0'; + + if (!rejected) { + var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + iceTransport.start(iceGatherer, remoteIceParameters, 'controlled'); + + var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + dtlsTransport.start(remoteDtlsParameters); + + // Calculate intersection of capabilities. + var params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this transceiver + // has already been started in setRemoteDescription. + self._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + '"'); + } + + // If a success callback was provided, emit ICE candidates after it has been + // executed. Otherwise, emit callback after the Promise is resolved. + var hasCallback = arguments.length > 1 && + typeof arguments[1] === 'function'; + if (hasCallback) { + var cb = arguments[1]; + window.setTimeout(function() { + cb(); + self._emitBufferedCandidates(); + }, 0); + } + var p = Promise.resolve(); + p.then(function() { + if (!hasCallback) { + window.setTimeout(self._emitBufferedCandidates.bind(self), 0); + } + }); + return p; + }; + + window.RTCPeerConnection.prototype.setRemoteDescription = + function(description) { + var self = this; + var stream = new MediaStream(); + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].substr(2).split(' '); + var kind = mline[0]; + var rejected = mline[1] === '0'; + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpSender; + var rtpReceiver; + var sendSsrc; + var recvSsrc; + var localCapabilities; + + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + } + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6); + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + recvSsrc = parseInt(remoteSsrc.ssrc, 10); + cname = remoteSsrc.value; + } + + if (description.type === 'offer') { + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + localCapabilities = RTCRtpReceiver.getCapabilities(kind); + sendSsrc = (2 * sdpMLineIndex + 2) * 1001; + + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + + // FIXME: not correct when there are multiple streams but that is + // not currently supported in this shim. + stream.addTrack(rtpReceiver.track); + + // FIXME: look at direction. + if (self.localStreams.length > 0 && + self.localStreams[0].getTracks().length >= sdpMLineIndex) { + // FIXME: actually more complicated, needs to match types etc + var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex]; + rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport); + } + + self.transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: remoteCapabilities, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + cname: cname, + sendSsrc: sendSsrc, + recvSsrc: recvSsrc + }; + // Start the RTCRtpReceiver now. The RTPSender is started in setLocalDescription. + self._transceive(self.transceivers[sdpMLineIndex], + false, + direction === 'sendrecv' || direction === 'sendonly'); + } else if (description.type === 'answer' && !rejected) { + transceiver = self.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpSender = transceiver.rtpSender; + rtpReceiver = transceiver.rtpReceiver; + sendSsrc = transceiver.sendSsrc; + //recvSsrc = transceiver.recvSsrc; + localCapabilities = transceiver.localCapabilities; + + self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc; + self.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + self.transceivers[sdpMLineIndex].cname = cname; + + iceTransport.start(iceGatherer, remoteIceParameters, 'controlling'); + dtlsTransport.start(remoteDtlsParameters); + + self._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + stream.addTrack(rtpReceiver.track); + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + this.remoteDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + '"'); + } + window.setTimeout(function() { + if (self.onaddstream !== null && stream.getTracks().length) { + self.remoteStreams.push(stream); + window.setTimeout(function() { + self.onaddstream({stream: stream}); + }, 0); + } + }, 0); + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + window.RTCPeerConnection.prototype._updateSignalingState = + function(newState) { + this.signalingState = newState; + if (this.onsignalingstatechange !== null) { + this.onsignalingstatechange(); + } + }; + + // Determine whether to fire the negotiationneeded event. + window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = + function() { + // Fire away (for now). + if (this.onnegotiationneeded !== null) { + this.onnegotiationneeded(); + } + }; + + // Update the connection state. + window.RTCPeerConnection.prototype._updateConnectionState = + function() { + var self = this; + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connecting > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== self.iceConnectionState) { + self.iceConnectionState = newState; + if (this.oniceconnectionstatechange !== null) { + this.oniceconnectionstatechange(); + } + } + }; + + window.RTCPeerConnection.prototype.createOffer = function() { + var self = this; + if (this._pendingOffer) { + throw new Error('createOffer called while there is a pending offer.'); + } + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; + } + + var tracks = []; + var numAudioTracks = 0; + var numVideoTracks = 0; + // Default to sendrecv. + if (this.localStreams.length) { + numAudioTracks = this.localStreams[0].getAudioTracks().length; + numVideoTracks = this.localStreams[0].getVideoTracks().length; + } + // Determine number of audio and video tracks we need to send/recv. + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + if (offerOptions.offerToReceiveVideo !== undefined) { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + if (this.localStreams.length) { + // Push local streams. + this.localStreams[0].getTracks().forEach(function(track) { + tracks.push({ + kind: track.kind, + track: track, + wantReceive: track.kind === 'audio' ? + numAudioTracks > 0 : numVideoTracks > 0 + }); + if (track.kind === 'audio') { + numAudioTracks--; + } else if (track.kind === 'video') { + numVideoTracks--; + } + }); + } + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + tracks.push({ + kind: 'audio', + wantReceive: true + }); + numAudioTracks--; + } + if (numVideoTracks > 0) { + tracks.push({ + kind: 'video', + wantReceive: true + }); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + var transceivers = []; + tracks.forEach(function(mline, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, dtls transport, + // potentially rtpsender and rtpreceiver. + var track = mline.track; + var kind = mline.kind; + var mid = generateIdentifier(); + + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpSender.getCapabilities(kind); + var rtpSender; + var rtpReceiver; + + // generate an ssrc now, to be used later in rtpSender.send + var sendSsrc = (2 * sdpMLineIndex + 1) * 1001; + if (track) { + rtpSender = new RTCRtpSender(track, transports.dtlsTransport); + } + + if (mline.wantReceive) { + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + } + + transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: null, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendSsrc: sendSsrc, + recvSsrc: null + }; + var transceiver = transceivers[sdpMLineIndex]; + sdp += SDPUtils.writeMediaSection(transceiver, + transceiver.localCapabilities, 'offer', self.localStreams[0]); + }); + + this._pendingOffer = transceivers; + var desc = new RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.createAnswer = function() { + var self = this; + var answerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + answerOptions = arguments[0]; + } else if (arguments.length === 3) { + answerOptions = arguments[2]; + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + this.transceivers.forEach(function(transceiver) { + // Calculate intersection of capabilities. + var commonCapabilities = self._getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, + 'answer', self.localStreams[0]); + }); + + var desc = new RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var mLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + mLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[mLineIndex]; + if (transceiver) { + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && cand.port === 0) { + return; + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component !== '1') { + return; + } + // A dirty hack to make samples work. + if (cand.type === 'endOfCandidates') { + cand = {}; + } + transceiver.iceTransport.addRemoteCandidate(cand); + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + var results = {}; + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + results[id] = result[id]; + }); + }); + if (cb) { + window.setTimeout(cb, 0, results); + } + resolve(results); + }); + }); + }; + } } else { webrtcUtils.log('Browser does not appear to be WebRTC-capable'); } @@ -1074,11 +2256,17 @@ if ( navigator.mozGetUserMedia /* Orginal exports removed in favor of AdapterJS custom export. if (typeof module !== 'undefined') { var RTCPeerConnection; + var RTCIceCandidate; + var RTCSessionDescription; if (typeof window !== 'undefined') { RTCPeerConnection = window.RTCPeerConnection; + RTCIceCandidate = window.RTCIceCandidate; + RTCSessionDescription = window.RTCSessionDescription; } module.exports = { RTCPeerConnection: RTCPeerConnection, + RTCIceCandidate: RTCIceCandidate, + RTCSessionDescription: RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1095,6 +2283,8 @@ if ( navigator.mozGetUserMedia define([], function() { return { RTCPeerConnection: window.RTCPeerConnection, + RTCIceCandidate: window.RTCIceCandidate, + RTCSessionDescription: window.RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1110,13 +2300,16 @@ if ( navigator.mozGetUserMedia } */ +/* jshint ignore:end */ // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// + AdapterJS.parseWebrtcDetectedBrowser(); + /////////////////////////////////////////////////////////////////// // EXTENSION FOR CHROME, FIREFOX AND EDGE - // Includes legacy functions + // Includes legacy functions // -- createIceServer // -- createIceServers // -- MediaStreamTrack.getSources @@ -1142,25 +2335,26 @@ if ( navigator.mozGetUserMedia createIceServer = function (url, username, password) { console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); - + // Note: Google's import of AJS will auto-reverse to 'url': '...' for FF < 38 + var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { url : url }; - } else if (url_parts[0].indexOf('turn') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { urls : [url] }; + } else if (urlParts[0].indexOf('turn') === 0) { if (webrtcDetectedVersion < 27) { - var turn_url_parts = url.split('?'); - if (turn_url_parts.length === 1 || - turn_url_parts[1].indexOf('transport=udp') === 0) { + var turnUrlParts = url.split('?'); + if (turnUrlParts.length === 1 || + turnUrlParts[1].indexOf('transport=udp') === 0) { iceServer = { - url : turn_url_parts[0], + urls : [turnUrlParts[0]], credential : password, username : username }; } } else { iceServer = { - url : url, + urls : [url], credential : password, username : username }; @@ -1184,12 +2378,12 @@ if ( navigator.mozGetUserMedia } else if ( navigator.webkitGetUserMedia ) { createIceServer = function (url, username, password) { console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); - + var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { iceServer = { 'url' : url }; - } else if (url_parts[0].indexOf('turn') === 0) { + } else if (urlParts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'credential' : password, @@ -1225,7 +2419,7 @@ if ( navigator.mozGetUserMedia // attachMediaStream and reattachMediaStream for Egde if (navigator.mediaDevices && navigator.userAgent.match( /Edge\/(\d+).(\d+)$/)) { - window.getUserMedia = navigator.getUserMedia.bind(navigator); + getUserMedia = window.getUserMedia = navigator.getUserMedia.bind(navigator); attachMediaStream = function(element, stream) { element.srcObject = stream; return element; @@ -1236,11 +2430,18 @@ if ( navigator.mozGetUserMedia }; } - // Need to override attachMediaStream and reattachMediaStream + // Need to override attachMediaStream and reattachMediaStream // to support the plugin's logic attachMediaStream_base = attachMediaStream; attachMediaStream = function (element, stream) { - attachMediaStream_base(element, stream); + if ((webrtcDetectedBrowser === 'chrome' || + webrtcDetectedBrowser === 'opera') && + !stream) { + // Chrome does not support "src = null" + element.src = ''; + } else { + attachMediaStream_base(element, stream); + } return element; }; reattachMediaStream_base = reattachMediaStream; @@ -1249,6 +2450,14 @@ if ( navigator.mozGetUserMedia return to; }; + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + // Removed Google defined promises when promise is not defined if (typeof Promise === 'undefined') { requestUserMedia = null; @@ -1305,7 +2514,6 @@ if ( navigator.mozGetUserMedia console.groupEnd = function (arg) {}; /* jshint +W020 */ } - webrtcDetectedType = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; @@ -1431,7 +2639,7 @@ if ( navigator.mozGetUserMedia AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - console.error("AdapterJS - WebRTC interface has already been defined"); + console.error('AdapterJS - WebRTC interface has already been defined'); return; } @@ -1443,13 +2651,13 @@ if ( navigator.mozGetUserMedia createIceServer = function (url, username, password) { var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { iceServer = { 'url' : url, 'hasCredentials' : false }; - } else if (url_parts[0].indexOf('turn') === 0) { + } else if (urlParts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'hasCredentials' : true, @@ -1475,27 +2683,58 @@ if ( navigator.mozGetUserMedia }; RTCPeerConnection = function (servers, constraints) { - var iceServers = null; - if (servers) { - iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - if (iceServers[i].urls && !iceServers[i].url) { - iceServers[i].url = iceServers[i].urls; - } - iceServers[i].hasCredentials = AdapterJS. - isDefined(iceServers[i].username) && - AdapterJS.isDefined(iceServers[i].credential); + // Validate server argumenr + if (!(servers === undefined || + servers === null || + Array.isArray(servers.iceServers))) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); + } + + // Validate constraints argument + if (typeof constraints !== 'undefined' && constraints !== null) { + var invalidConstraits = false; + invalidConstraits |= typeof constraints !== 'object'; + invalidConstraits |= constraints.hasOwnProperty('mandatory') && + constraints.mandatory !== undefined && + constraints.mandatory !== null && + constraints.mandatory.constructor !== Object; + invalidConstraits |= constraints.hasOwnProperty('optional') && + constraints.optional !== undefined && + constraints.optional !== null && + !Array.isArray(constraints.optional); + if (invalidConstraits) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed constraints object'); } } - var mandatory = (constraints && constraints.mandatory) ? - constraints.mandatory : null; - var optional = (constraints && constraints.optional) ? - constraints.optional : null; + // Call relevant PeerConnection constructor according to plugin version AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - PeerConnection(AdapterJS.WebRTCPlugin.pageId, - iceServers, mandatory, optional); + if (AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION && + AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION > 1) { + // RTCPeerConnection prototype from the new spec + return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); + } else { + // RTCPeerConnection prototype from the old spec + var iceServers = null; + if (servers && Array.isArray(servers.iceServers)) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + } }; MediaStreamTrack = {}; @@ -1505,7 +2744,7 @@ if ( navigator.mozGetUserMedia }); }; - window.getUserMedia = function (constraints, successCallback, failureCallback) { + getUserMedia = function (constraints, successCallback, failureCallback) { constraints.audio = constraints.audio || false; constraints.video = constraints.video || false; @@ -1514,11 +2753,16 @@ if ( navigator.mozGetUserMedia getUserMedia(constraints, successCallback, failureCallback); }); }; - window.navigator.getUserMedia = window.getUserMedia; + window.navigator.getUserMedia = getUserMedia; // Defined mediaDevices when promises are available - if ( !navigator.mediaDevices - && typeof Promise !== 'undefined') { + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { + requestUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + }; navigator.mediaDevices = {getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { @@ -1527,6 +2771,7 @@ if ( navigator.mozGetUserMedia resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], + id: device.id, deviceId: device.id, groupId: ''}; })); @@ -1636,31 +2881,32 @@ if ( navigator.mozGetUserMedia } }; - AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { properties = Object.getOwnPropertyNames( prototype ); - - for(prop in properties) { - propName = properties[prop]; - - if (typeof(propName.slice) === 'function') { - if (propName.slice(0,2) == 'on' && srcElem[propName] != null) { - if (isIE) { - destElem.attachEvent(propName,srcElem[propName]); - } else { - destElem.addEventListener(propName.slice(2), srcElem[propName], false) - } - } else { - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? + for(var prop in properties) { + if (prop) { + propName = properties[prop]; + + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); } } } - - var subPrototype = Object.getPrototypeOf(prototype) - if(subPrototype != null) { + var subPrototype = Object.getPrototypeOf(prototype); + if(!!subPrototype) { AdapterJS.forwardEventHandlers(destElem, srcElem, subPrototype); } - } + }; RTCIceCandidate = function (candidate) { if (!candidate.sdpMid) { diff --git a/publish/adapter.min.js b/publish/adapter.min.js index 076c8cf..5980c08 100644 --- a/publish/adapter.min.js +++ b/publish/adapter.min.js @@ -1,2 +1,3 @@ -/*! adapterjs - v0.13.0 - 2016-01-08 */ -function trace(text){if("\n"===text[text.length-1]&&(text=text.substring(0,text.length-1)),window.performance){var now=(window.performance.now()/1e3).toFixed(3);webrtcUtils.log(now+": "+text)}else webrtcUtils.log(text)}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}var AdapterJS=AdapterJS||{};if("undefined"!=typeof exports&&(module.exports=AdapterJS),AdapterJS.options=AdapterJS.options||{},AdapterJS.VERSION="0.13.0",AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){},AdapterJS._onwebrtcreadies=[],AdapterJS.webRTCReady=function(callback){if("function"!=typeof callback)throw new Error("Callback provided is not a function");!0===AdapterJS.onwebrtcreadyDone?callback(null!==AdapterJS.WebRTCPlugin.plugin):AdapterJS._onwebrtcreadies.push(callback)},AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{},AdapterJS.WebRTCPlugin.pluginInfo={prefix:"Tem",plugName:"TemWebRTCPlugin",pluginId:"plugin0",type:"application/x-temwebrtcplugin",onload:"__TemWebRTCReady0",portalLink:"http://skylink.io/plugin/",downloadLink:null,companyName:"Temasys"},navigator.platform.match(/^Mac/i)?AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1n77hco":navigator.platform.match(/^Win/i)&&(AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1kkS4FN"),AdapterJS.WebRTCPlugin.TAGS={NONE:"none",AUDIO:"audio",VIDEO:"video"},AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2),AdapterJS.WebRTCPlugin.plugin=null,AdapterJS.WebRTCPlugin.setLogLevel=null,AdapterJS.WebRTCPlugin.defineWebRTCInterface=null,AdapterJS.WebRTCPlugin.isPluginInstalled=null,AdapterJS.WebRTCPlugin.pluginInjectionInterval=null,AdapterJS.WebRTCPlugin.injectPlugin=null,AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4},AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE,AdapterJS.onwebrtcreadyDone=!1,AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:"NONE",ERROR:"ERROR",WARNING:"WARNING",INFO:"INFO",VERBOSE:"VERBOSE",SENSITIVE:"SENSITIVE"},AdapterJS.WebRTCPlugin.WaitForPluginReady=null,AdapterJS.WebRTCPlugin.callWhenPluginReady=null,__TemWebRTCReady0=function(){webrtcDetectedVersion=AdapterJS.WebRTCPlugin.plugin.version,"complete"===document.readyState?(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady()):AdapterJS.WebRTCPlugin.documentReadyInterval=setInterval(function(){"complete"===document.readyState&&(clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval),AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady())},100)},AdapterJS.maybeThroughWebRTCReady=function(){AdapterJS.onwebrtcreadyDone||(AdapterJS.onwebrtcreadyDone=!0,AdapterJS._onwebrtcreadies.length?AdapterJS._onwebrtcreadies.forEach(function(callback){"function"==typeof callback&&callback(null!==AdapterJS.WebRTCPlugin.plugin)}):"function"==typeof AdapterJS.onwebrtcready&&AdapterJS.onwebrtcready(null!==AdapterJS.WebRTCPlugin.plugin))},AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:"This website requires you to install a WebRTC-enabling plugin to work on this browser.",NOT_SUPPORTED:"Your browser does not support WebRTC.",BUTTON:"Install Now"},REFRESH:{REQUIRE_REFRESH:"Please refresh page",BUTTON:"Refresh Page"}},AdapterJS._iceConnectionStates={starting:"starting",checking:"checking",connected:"connected",completed:"connected",done:"completed",disconnected:"disconnected",failed:"failed",closed:"closed"},AdapterJS._iceConnectionFiredStates=[],AdapterJS.isDefined=null,AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch,checkMatch=navigator.userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];if(/trident/i.test(checkMatch[1])?(hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedBrowser="IE",webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10)):"Chrome"===checkMatch[1]&&(hasMatch=navigator.userAgent.match(/\bOPR\/(\d+)/),null!==hasMatch&&(webrtcDetectedBrowser="opera",webrtcDetectedVersion=parseInt(hasMatch[1],10))),navigator.userAgent.indexOf("Safari")&&("undefined"!=typeof InstallTrigger?webrtcDetectedBrowser="firefox":document.documentMode?webrtcDetectedBrowser="IE":Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0?webrtcDetectedBrowser="safari":window.opera||navigator.userAgent.indexOf(" OPR/")>=0?webrtcDetectedBrowser="opera":window.chrome&&(webrtcDetectedBrowser="chrome")),webrtcDetectedBrowser||(webrtcDetectedVersion=checkMatch[1]),!webrtcDetectedVersion)try{checkMatch=checkMatch[2]?[checkMatch[1],checkMatch[2]]:[navigator.appName,navigator.appVersion,"-?"],null!==(hasMatch=navigator.userAgent.match(/version\/(\d+)/i))&&checkMatch.splice(1,1,hasMatch[1]),webrtcDetectedVersion=parseInt(checkMatch[1],10)}catch(error){}},AdapterJS.maybeFixConfiguration=function(pcConfig){if(null!==pcConfig)for(var i=0;i'+text+""),buttonText&&buttonLink?(c.document.write(''),c.document.close(),AdapterJS.addEvent(c.document.getElementById("okay"),"click",function(e){displayRefreshBar&&AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,"javascript:location.reload()"),window.open(buttonLink,openNewTab?"_blank":"_top"),e.preventDefault();try{event.cancelBubble=!0}catch(error){}var pluginInstallInterval=setInterval(function(){isIE||navigator.plugins.refresh(!1),AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval),AdapterJS.WebRTCPlugin.defineWebRTCInterface()},function(){})},500)}),AdapterJS.addEvent(c.document.getElementById("cancel"),"click",function(e){w.document.body.removeChild(i)})):c.document.close(),setTimeout(function(){"string"==typeof i.style.webkitTransform?i.style.webkitTransform="translateY(40px)":"string"==typeof i.style.transform?i.style.transform="translateY(40px)":i.style.top="0px"},300)}},webrtcDetectedType=null,checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if("function"==typeof callback){var beOfferer=!0,isLocalFirefox="firefox"===webrtcDetectedBrowser,isLocalFirefoxInterop="moz"===webrtcDetectedType&&webrtcDetectedVersion>30,isPeerFirefox="firefox"===peerBrowserAgent;if(isLocalFirefox&&isPeerFirefox||isLocalFirefoxInterop)try{delete constraints.mandatory.MozDontOfferDataChannel}catch(error){}else isLocalFirefox&&!isPeerFirefox&&(constraints.mandatory.MozDontOfferDataChannel=!0);if(!isLocalFirefox)for(var prop in constraints.mandatory)constraints.mandatory.hasOwnProperty(prop)&&-1!==prop.indexOf("Moz")&&delete constraints.mandatory[prop];!isLocalFirefox||isPeerFirefox||isLocalFirefoxInterop||(beOfferer=!1),callback(beOfferer,constraints)}},checkIceConnectionState=function(peerId,iceConnectionState,callback){"function"==typeof callback&&(peerId=peerId?peerId:"peer",AdapterJS._iceConnectionFiredStates[peerId]&&iceConnectionState!==AdapterJS._iceConnectionStates.disconnected&&iceConnectionState!==AdapterJS._iceConnectionStates.failed&&iceConnectionState!==AdapterJS._iceConnectionStates.closed||(AdapterJS._iceConnectionFiredStates[peerId]=[]),iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState],AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0&&(AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState),iceConnectionState===AdapterJS._iceConnectionStates.connected&&setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done),callback(AdapterJS._iceConnectionStates.done)},1e3),callback(iceConnectionState)))},createIceServer=null,createIceServers=null,RTCPeerConnection=null,RTCSessionDescription="function"==typeof RTCSessionDescription?RTCSessionDescription:null,RTCIceCandidate="function"==typeof RTCIceCandidate?RTCIceCandidate:null,getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,navigator.mozGetUserMedia||navigator.webkitGetUserMedia||navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){var getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils={log:function(){"undefined"!=typeof module||"function"==typeof require&&"function"==typeof define},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos])}};if("object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=URL.createObjectURL(stream))}}),getUserMedia=window.navigator&&window.navigator.getUserMedia),attachMediaStream=function(element,stream){element.srcObject=stream},reattachMediaStream=function(to,from){to.srcObject=from.srcObject},"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia&&window.mozRTCPeerConnection){if(webrtcUtils.log("This appears to be Firefox"),webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,window.RTCPeerConnection=function(pcConfig,pcConstraints){if(38>webrtcDetectedVersion&&pcConfig&&pcConfig.iceServers){for(var newIceServers=[],i=0;iwebrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,addEventListener:function(){},removeEventListener:function(){}}),navigator.mediaDevices.enumerateDevices=navigator.mediaDevices.enumerateDevices||function(){return new Promise(function(resolve){var infos=[{kind:"audioinput",deviceId:"default",label:"",groupId:""},{kind:"videoinput",deviceId:"default",label:"",groupId:""}];resolve(infos)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcUtils.log("This appears to be Chrome"),webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38,window.RTCPeerConnection=function(pcConfig,pcConstraints){pcConfig&&pcConfig.iceTransportPolicy&&(pcConfig.iceTransports=pcConfig.iceTransportPolicy);var pc=new webkitRTCPeerConnection(pcConfig,pcConstraints),origGetStats=pc.getStats.bind(pc);return pc.getStats=function(selector,successCallback,errorCallback){var self=this,args=arguments;if(arguments.length>0&&"function"==typeof selector)return origGetStats(selector,successCallback);var fixChromeStats=function(response){var standardReport={},reports=response.result();return reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name)}),standardReport[standardStats.id]=standardStats}),standardReport};if(arguments.length>=2){var successCallbackWrapper=function(response){args[1](fixChromeStats(response))};return origGetStats.apply(this,[successCallbackWrapper,arguments[0]])}return new Promise(function(resolve,reject){1===args.length&&null===selector?origGetStats.apply(self,[function(response){resolve.apply(null,[fixChromeStats(response)])},reject]):origGetStats.apply(self,[resolve,reject])})},pc},["createOffer","createAnswer"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||1===arguments.length&&"object"==typeof arguments[0]){var opts=1===arguments.length?arguments[0]:void 0;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts])})}return nativeMethod.apply(this,arguments)}}),["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments,self=this;return new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],function(){resolve(),args.length>=2&&args[1].apply(null,[])},function(err){reject(err),args.length>=3&&args[2].apply(null,[err])}])})}});var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:"audioinput",video:"videoinput"};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:""}}))})})}}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")}),attachMediaStream=function(element,stream){webrtcDetectedVersion>=43?element.srcObject=stream:"undefined"!=typeof element.src?element.src=URL.createObjectURL(stream):webrtcUtils.log("Error attaching stream to element.")},reattachMediaStream=function(to,from){webrtcDetectedVersion>=43?to.srcObject=from.srcObject:to.src=from.src}}else navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)?(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=12):webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcUtils.log("This does not appear to be a browser"),webrtcDetectedBrowser="not a browser";var webrtcTesting={};try{Object.defineProperty(webrtcTesting,"version",{set:function(version){webrtcDetectedVersion=version}})}catch(e){}navigator.mozGetUserMedia?(MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:"audio",id:"default",label:"",facing:""},{kind:"video",id:"default",label:"",facing:""}];successCb(infos)},0)},createIceServer=function(url,username,password){var iceServer=null,url_parts=url.split(":");if(0===url_parts[0].indexOf("stun"))iceServer={url:url};else if(0===url_parts[0].indexOf("turn"))if(27>webrtcDetectedVersion){var turn_url_parts=url.split("?");(1===turn_url_parts.length||0===turn_url_parts[1].indexOf("transport=udp"))&&(iceServer={url:turn_url_parts[0],credential:password,username:username})}else iceServer={url:url,credential:password,username:username};return iceServer},createIceServers=function(urls,username,password){var iceServers=[];for(i=0;i=34)iceServers={urls:urls,credential:password,username:username};else for(i=0;i=webrtcDetectedVersion){var frag=document.createDocumentFragment();for(AdapterJS.WebRTCPlugin.plugin=document.createElement("div"),AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+"";AdapterJS.WebRTCPlugin.plugin.firstChild;)frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);document.body.appendChild(frag),AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId)}else AdapterJS.WebRTCPlugin.plugin=document.createElement("object"),AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId,isIE?(AdapterJS.WebRTCPlugin.plugin.width="1px",AdapterJS.WebRTCPlugin.plugin.height="1px"):(AdapterJS.WebRTCPlugin.plugin.width="0px",AdapterJS.WebRTCPlugin.plugin.height="0px"),AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type,AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+'',document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED}},AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(isIE){try{new ActiveXObject(comName+"."+plugName)}catch(e){return void notInstalledCb()}installedCb()}else{for(var pluginArray=navigator.plugins,i=0;i=0)return void installedCb();notInstalledCb()}},AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){AdapterJS.WebRTCPlugin.pluginState!==AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY&&(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING,AdapterJS.isDefined=function(variable){return null!==variable&&void 0!==variable},createIceServer=function(url,username,password){var iceServer=null,url_parts=url.split(":");return 0===url_parts[0].indexOf("stun")?iceServer={url:url,hasCredentials:!1}:0===url_parts[0].indexOf("turn")&&(iceServer={url:url,hasCredentials:!0,credential:password,username:username}),iceServer},createIceServers=function(urls,username,password){for(var iceServers=[],i=0;i ';temp.firstChild;)frag.appendChild(temp.firstChild);var height="",width="";element.clientWidth||element.clientHeight?(width=element.clientWidth,height=element.clientHeight):(element.width||element.height)&&(width=element.width,height=element.height),element.parentNode.insertBefore(frag,element),frag=document.getElementById(elementId),frag.width=width,frag.height=height,element.parentNode.removeChild(element)}else{for(var children=element.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){children[i].value=streamId;break}element.setStreamId(streamId)}var newElement=document.getElementById(elementId);return AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element)),newElement}},reattachMediaStream=function(to,from){for(var stream=null,children=from.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){AdapterJS.WebRTCPlugin.WaitForPluginReady(),stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break}return null!==stream?attachMediaStream(to,stream):void 0},AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(prop in properties)propName=properties[prop],"function"==typeof propName.slice&&"on"==propName.slice(0,2)&&null!=srcElem[propName]&&(isIE?destElem.attachEvent(propName,srcElem[propName]):destElem.addEventListener(propName.slice(2),srcElem[propName],!1));var subPrototype=Object.getPrototypeOf(prototype);null!=subPrototype&&AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype)},RTCIceCandidate=function(candidate){return candidate.sdpMid||(candidate.sdpMid=""),AdapterJS.WebRTCPlugin.WaitForPluginReady(),AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate)},AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.injectPlugin),AdapterJS.WebRTCPlugin.injectPlugin())},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv),AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv()},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(!AdapterJS.options.hidePluginInstallPrompt){var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;popupString=AdapterJS.WebRTCPlugin.pluginInfo.portalLink?'This website requires you to install the '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+" WebRTC Plugin to work on this browser.":AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION,AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink)}else AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED)}},AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); +/*! adapterjs - v0.13.1 - 2016-03-15 */ +function trace(text){if("\n"===text[text.length-1]&&(text=text.substring(0,text.length-1)),window.performance){var now=(window.performance.now()/1e3).toFixed(3);webrtcUtils.log(now+": "+text)}else webrtcUtils.log(text)}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}var AdapterJS=AdapterJS||{};if("undefined"!=typeof exports&&(module.exports=AdapterJS),AdapterJS.options=AdapterJS.options||{},AdapterJS.VERSION="0.13.1",AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){},AdapterJS._onwebrtcreadies=[],AdapterJS.webRTCReady=function(callback){if("function"!=typeof callback)throw new Error("Callback provided is not a function");!0===AdapterJS.onwebrtcreadyDone?callback(null!==AdapterJS.WebRTCPlugin.plugin):AdapterJS._onwebrtcreadies.push(callback)},AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{},AdapterJS.WebRTCPlugin.pluginInfo={prefix:"Tem",plugName:"TemWebRTCPlugin",pluginId:"plugin0",type:"application/x-temwebrtcplugin",onload:"__TemWebRTCReady0",portalLink:"http://skylink.io/plugin/",downloadLink:null,companyName:"Temasys"},navigator.platform.match(/^Mac/i)?AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1n77hco":navigator.platform.match(/^Win/i)&&(AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1kkS4FN"),AdapterJS.WebRTCPlugin.TAGS={NONE:"none",AUDIO:"audio",VIDEO:"video"},AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2),AdapterJS.WebRTCPlugin.plugin=null,AdapterJS.WebRTCPlugin.setLogLevel=null,AdapterJS.WebRTCPlugin.defineWebRTCInterface=null,AdapterJS.WebRTCPlugin.isPluginInstalled=null,AdapterJS.WebRTCPlugin.pluginInjectionInterval=null,AdapterJS.WebRTCPlugin.injectPlugin=null,AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4},AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE,AdapterJS.onwebrtcreadyDone=!1,AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:"NONE",ERROR:"ERROR",WARNING:"WARNING",INFO:"INFO",VERBOSE:"VERBOSE",SENSITIVE:"SENSITIVE"},AdapterJS.WebRTCPlugin.WaitForPluginReady=null,AdapterJS.WebRTCPlugin.callWhenPluginReady=null,__TemWebRTCReady0=function(){if("complete"===document.readyState)AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady();else var timer=setInterval(function(){"complete"===document.readyState&&(clearInterval(timer),AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady())},100)},AdapterJS.maybeThroughWebRTCReady=function(){AdapterJS.onwebrtcreadyDone||(AdapterJS.onwebrtcreadyDone=!0,AdapterJS._onwebrtcreadies.length?AdapterJS._onwebrtcreadies.forEach(function(callback){"function"==typeof callback&&callback(null!==AdapterJS.WebRTCPlugin.plugin)}):"function"==typeof AdapterJS.onwebrtcready&&AdapterJS.onwebrtcready(null!==AdapterJS.WebRTCPlugin.plugin))},AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:"This website requires you to install a WebRTC-enabling plugin to work on this browser.",NOT_SUPPORTED:"Your browser does not support WebRTC.",BUTTON:"Install Now"},REFRESH:{REQUIRE_REFRESH:"Please refresh page",BUTTON:"Refresh Page"}},AdapterJS._iceConnectionStates={starting:"starting",checking:"checking",connected:"connected",completed:"connected",done:"completed",disconnected:"disconnected",failed:"failed",closed:"closed"},AdapterJS._iceConnectionFiredStates=[],AdapterJS.isDefined=null,AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch=null;window.opr&&opr.addons||window.opera||navigator.userAgent.indexOf(" OPR/")>=0?(webrtcDetectedBrowser="opera",webrtcDetectedType="webkit",webrtcMinimumVersion=26,hasMatch=/OPR\/(\d+)/i.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1],10)):"undefined"!=typeof InstallTrigger?webrtcDetectedType="moz":Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0?(webrtcDetectedBrowser="safari",webrtcDetectedType="plugin",webrtcMinimumVersion=7,hasMatch=/version\/(\d+)/i.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1],10)):document.documentMode?(webrtcDetectedBrowser="IE",webrtcDetectedType="plugin",webrtcMinimumVersion=9,hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10),webrtcDetectedVersion||(hasMatch=/\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10))):window.StyleMedia?webrtcDetectedType="":window.chrome&&window.chrome.webstore?webrtcDetectedType="webkit":"chrome"!==webrtcDetectedBrowser&&"opera"!==webrtcDetectedBrowser||!window.CSS||(webrtcDetectedBrowser="blink"),window.webrtcDetectedBrowser=webrtcDetectedBrowser,window.webrtcDetectedVersion=webrtcDetectedVersion,window.webrtcMinimumVersion=webrtcMinimumVersion},AdapterJS.addEvent=function(elem,evnt,func){elem.addEventListener?elem.addEventListener(evnt,func,!1):elem.attachEvent?elem.attachEvent("on"+evnt,func):elem[evnt]=func},AdapterJS.renderNotificationBar=function(text,buttonText,buttonLink,openNewTab,displayRefreshBar){if("complete"===document.readyState){var w=window,i=document.createElement("iframe");i.name="adapterjs-alert",i.style.position="fixed",i.style.top="-41px",i.style.left=0,i.style.right=0,i.style.width="100%",i.style.height="40px",i.style.backgroundColor="#ffffe1",i.style.border="none",i.style.borderBottom="1px solid #888888",i.style.zIndex="9999999","string"==typeof i.style.webkitTransition?i.style.webkitTransition="all .5s ease-out":"string"==typeof i.style.transition&&(i.style.transition="all .5s ease-out"),document.body.appendChild(i);var c=i.contentWindow?i.contentWindow:i.contentDocument.document?i.contentDocument.document:i.contentDocument;c.document.open(),c.document.write(''+text+""),buttonText&&buttonLink?(c.document.write(''),c.document.close(),AdapterJS.addEvent(c.document.getElementById("okay"),"click",function(e){displayRefreshBar&&AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,"javascript:location.reload()"),window.open(buttonLink,openNewTab?"_blank":"_top"),e.preventDefault();try{e.cancelBubble=!0}catch(error){}var pluginInstallInterval=setInterval(function(){isIE||navigator.plugins.refresh(!1),AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval),AdapterJS.WebRTCPlugin.defineWebRTCInterface()},function(){})},500)}),AdapterJS.addEvent(c.document.getElementById("cancel"),"click",function(e){w.document.body.removeChild(i)})):c.document.close(),setTimeout(function(){"string"==typeof i.style.webkitTransform?i.style.webkitTransform="translateY(40px)":"string"==typeof i.style.transform?i.style.transform="translateY(40px)":i.style.top="0px"},300)}},webrtcDetectedType=null,checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if("function"==typeof callback){var beOfferer=!0,isLocalFirefox="firefox"===webrtcDetectedBrowser,isLocalFirefoxInterop="moz"===webrtcDetectedType&&webrtcDetectedVersion>30,isPeerFirefox="firefox"===peerBrowserAgent;if(isLocalFirefox&&isPeerFirefox||isLocalFirefoxInterop)try{delete constraints.mandatory.MozDontOfferDataChannel}catch(error){}else isLocalFirefox&&!isPeerFirefox&&(constraints.mandatory.MozDontOfferDataChannel=!0);if(!isLocalFirefox)for(var prop in constraints.mandatory)constraints.mandatory.hasOwnProperty(prop)&&-1!==prop.indexOf("Moz")&&delete constraints.mandatory[prop];!isLocalFirefox||isPeerFirefox||isLocalFirefoxInterop||(beOfferer=!1),callback(beOfferer,constraints)}},checkIceConnectionState=function(peerId,iceConnectionState,callback){"function"==typeof callback&&(peerId=peerId?peerId:"peer",AdapterJS._iceConnectionFiredStates[peerId]&&iceConnectionState!==AdapterJS._iceConnectionStates.disconnected&&iceConnectionState!==AdapterJS._iceConnectionStates.failed&&iceConnectionState!==AdapterJS._iceConnectionStates.closed||(AdapterJS._iceConnectionFiredStates[peerId]=[]),iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState],AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0&&(AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState),iceConnectionState===AdapterJS._iceConnectionStates.connected&&setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done),callback(AdapterJS._iceConnectionStates.done)},1e3),callback(iceConnectionState)))},createIceServer=null,createIceServers=null,RTCPeerConnection=null,RTCSessionDescription="function"==typeof RTCSessionDescription?RTCSessionDescription:null,RTCIceCandidate="function"==typeof RTCIceCandidate?RTCIceCandidate:null,getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,navigator.mozGetUserMedia||navigator.webkitGetUserMedia||navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){var getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils={log:function(){"undefined"!=typeof module||"function"==typeof require&&"function"==typeof define},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10)}};if("object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=URL.createObjectURL(stream))}}),getUserMedia=window.navigator&&window.navigator.getUserMedia),attachMediaStream=function(element,stream){element.srcObject=stream},reattachMediaStream=function(to,from){to.srcObject=from.srcObject},"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia){if(webrtcUtils.log("This appears to be Firefox"),webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,window.RTCPeerConnection||(window.RTCPeerConnection=function(pcConfig,pcConstraints){if(38>webrtcDetectedVersion&&pcConfig&&pcConfig.iceServers){for(var newIceServers=[],i=0;iwebrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,addEventListener:function(){},removeEventListener:function(){}}),navigator.mediaDevices.enumerateDevices=navigator.mediaDevices.enumerateDevices||function(){return new Promise(function(resolve){var infos=[{kind:"audioinput",deviceId:"default",label:"",groupId:""},{kind:"videoinput",deviceId:"default",label:"",groupId:""}];resolve(infos)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcUtils.log("This appears to be Chrome"),webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38,window.RTCPeerConnection=function(pcConfig,pcConstraints){pcConfig&&pcConfig.iceTransportPolicy&&(pcConfig.iceTransports=pcConfig.iceTransportPolicy);var pc=new webkitRTCPeerConnection(pcConfig,pcConstraints),origGetStats=pc.getStats.bind(pc);return pc.getStats=function(selector,successCallback,errorCallback){var self=this,args=arguments;if(arguments.length>0&&"function"==typeof selector)return origGetStats(selector,successCallback);var fixChromeStats=function(response){var standardReport={},reports=response.result();return reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name)}),standardReport[standardStats.id]=standardStats}),standardReport};if(arguments.length>=2){var successCallbackWrapper=function(response){args[1](fixChromeStats(response))};return origGetStats.apply(this,[successCallbackWrapper,arguments[0]])}return new Promise(function(resolve,reject){1===args.length&&null===selector?origGetStats.apply(self,[function(response){resolve.apply(null,[fixChromeStats(response)])},reject]):origGetStats.apply(self,[resolve,reject])})},pc},webkitRTCPeerConnection.generateCertificate&&Object.defineProperty(window.RTCPeerConnection,"generateCertificate",{get:function(){return arguments.length?webkitRTCPeerConnection.generateCertificate.apply(null,arguments):webkitRTCPeerConnection.generateCertificate}}),["createOffer","createAnswer"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||1===arguments.length&&"object"==typeof arguments[0]){var opts=1===arguments.length?arguments[0]:void 0;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts])})}return nativeMethod.apply(this,arguments)}}),["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments,self=this;return new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],function(){resolve(),args.length>=2&&args[1].apply(null,[])},function(err){reject(err),args.length>=3&&args[2].apply(null,[err])}])})}});var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:"audioinput",video:"videoinput"};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:""}}))})})}}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")}),attachMediaStream=function(element,stream){webrtcDetectedVersion>=43?element.srcObject=stream:"undefined"!=typeof element.src?element.src=URL.createObjectURL(stream):webrtcUtils.log("Error attaching stream to element.")},reattachMediaStream=function(to,from){webrtcDetectedVersion>=43?to.srcObject=from.srcObject:to.src=from.src}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){if(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=10547,window.RTCIceGatherer){var generateIdentifier=function(){return Math.random().toString(36).substr(2,10)},localCName=generateIdentifier(),SDPUtils={};SDPUtils.splitLines=function(blob){return blob.trim().split("\n").map(function(line){return line.trim()})},SDPUtils.splitSections=function(blob){var parts=blob.split("\r\nm=");return parts.map(function(part,index){return(index>0?"m="+part:part).trim()+"\r\n"})},SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return 0===line.indexOf(prefix)})},SDPUtils.parseCandidate=function(line){var parts;parts=0===line.indexOf("a=candidate:")?line.substring(12).split(" "):line.substring(10).split(" ");for(var candidate={foundation:parts[0],component:parts[1],protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]},i=8;i-1?(parts.attribute=line.substr(sp+1,colon-sp-1),parts.value=line.substr(colon+1)):parts.attribute=line.substr(sp+1),parts},SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var fpLine=lines.filter(function(line){return 0===line.indexOf("a=fingerprint:")})[0].substr(14),dtlsParameters={role:"auto",fingerprints:[{algorithm:fpLine.split(" ")[0],value:fpLine.split(" ")[1]}]};return dtlsParameters},SDPUtils.writeDtlsParameters=function(params,setupType){var sdp="a=setup:"+setupType+"\r\n";return params.fingerprints.forEach(function(fp){sdp+="a=fingerprint:"+fp.algorithm+" "+fp.value+"\r\n"}),sdp},SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return 0===line.indexOf("a=ice-ufrag:")})[0].substr(12),password:lines.filter(function(line){return 0===line.indexOf("a=ice-pwd:")})[0].substr(10)};return iceParameters},SDPUtils.writeIceParameters=function(params){return"a=ice-ufrag:"+params.usernameFragment+"\r\na=ice-pwd:"+params.password+"\r\n"},SDPUtils.parseRtpParameters=function(mediaSection){for(var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]},lines=SDPUtils.splitLines(mediaSection),mline=lines[0].split(" "),i=3;i0?"9":"0",sdp+=" UDP/TLS/RTP/SAVPF ",sdp+=caps.codecs.map(function(codec){return void 0!==codec.preferredPayloadType?codec.preferredPayloadType:codec.payloadType}).join(" ")+"\r\n",sdp+="c=IN IP4 0.0.0.0\r\n",sdp+="a=rtcp:9 IN IP4 0.0.0.0\r\n",caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec),sdp+=SDPUtils.writeFtmp(codec),sdp+=SDPUtils.writeRtcpFb(codec)}),sdp+="a=rtcp-mux\r\n"},SDPUtils.writeSessionBoilerplate=function(){return"v=0\r\no=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"},SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);if(sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters()),sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),"offer"===type?"actpass":"active"),sdp+="a=mid:"+transceiver.mid+"\r\n",sdp+=transceiver.rtpSender&&transceiver.rtpReceiver?"a=sendrecv\r\n":transceiver.rtpSender?"a=sendonly\r\n":transceiver.rtpReceiver?"a=recvonly\r\n":"a=inactive\r\n",transceiver.rtpSender){var msid="msid:"+stream.id+" "+transceiver.rtpSender.track.id+"\r\n";sdp+="a="+msid,sdp+="a=ssrc:"+transceiver.sendSsrc+" "+msid}return sdp+="a=ssrc:"+transceiver.sendSsrc+" cname:"+localCName+"\r\n"},SDPUtils.getDirection=function(mediaSection,sessionpart){for(var lines=SDPUtils.splitLines(mediaSection),i=0;i-1&&(this.localStreams.splice(idx,1),this._maybeFireNegotiationNeeded())},window.RTCPeerConnection.prototype._getCommonCapabilities=function(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};return localCapabilities.codecs.forEach(function(lCodec){for(var i=0;i0,!1)}})}switch(this.localDescription=description, +description.type){case"offer":this._updateSignalingState("have-local-offer");break;case"answer":this._updateSignalingState("stable");break;default:throw new TypeError('unsupported type "'+description.type+'"')}var hasCallback=arguments.length>1&&"function"==typeof arguments[1];if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb(),self._emitBufferedCandidates()},0)}var p=Promise.resolve();return p.then(function(){hasCallback||window.setTimeout(self._emitBufferedCandidates.bind(self),0)}),p},window.RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this,stream=new MediaStream,sections=SDPUtils.splitSections(description.sdp),sessionpart=sections.shift();switch(sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver,iceGatherer,iceTransport,dtlsTransport,rtpSender,rtpReceiver,sendSsrc,recvSsrc,localCapabilities,remoteIceParameters,remoteDtlsParameters,lines=SDPUtils.splitLines(mediaSection),mline=lines[0].substr(2).split(" "),kind=mline[0],rejected="0"===mline[1],direction=SDPUtils.getDirection(mediaSection,sessionpart),remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);rejected||(remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart),remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart));var cname,mid=SDPUtils.matchPrefix(mediaSection,"a=mid:")[0].substr(6),remoteSsrc=SDPUtils.matchPrefix(mediaSection,"a=ssrc:").map(function(line){return SDPUtils.parseSsrcMedia(line)}).filter(function(obj){return"cname"===obj.attribute})[0];if(remoteSsrc&&(recvSsrc=parseInt(remoteSsrc.ssrc,10),cname=remoteSsrc.value),"offer"===description.type){var transports=self._createIceAndDtlsTransports(mid,sdpMLineIndex);if(localCapabilities=RTCRtpReceiver.getCapabilities(kind),sendSsrc=1001*(2*sdpMLineIndex+2),rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind),stream.addTrack(rtpReceiver.track),self.localStreams.length>0&&self.localStreams[0].getTracks().length>=sdpMLineIndex){var localtrack=self.localStreams[0].getTracks()[sdpMLineIndex];rtpSender=new RTCRtpSender(localtrack,transports.dtlsTransport)}self.transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:remoteCapabilities,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,cname:cname,sendSsrc:sendSsrc,recvSsrc:recvSsrc},self._transceive(self.transceivers[sdpMLineIndex],!1,"sendrecv"===direction||"sendonly"===direction)}else"answer"!==description.type||rejected||(transceiver=self.transceivers[sdpMLineIndex],iceGatherer=transceiver.iceGatherer,iceTransport=transceiver.iceTransport,dtlsTransport=transceiver.dtlsTransport,rtpSender=transceiver.rtpSender,rtpReceiver=transceiver.rtpReceiver,sendSsrc=transceiver.sendSsrc,localCapabilities=transceiver.localCapabilities,self.transceivers[sdpMLineIndex].recvSsrc=recvSsrc,self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities,self.transceivers[sdpMLineIndex].cname=cname,iceTransport.start(iceGatherer,remoteIceParameters,"controlling"),dtlsTransport.start(remoteDtlsParameters),self._transceive(transceiver,"sendrecv"===direction||"recvonly"===direction,"sendrecv"===direction||"sendonly"===direction),!rtpReceiver||"sendrecv"!==direction&&"sendonly"!==direction?delete transceiver.rtpReceiver:stream.addTrack(rtpReceiver.track))}),this.remoteDescription=description,description.type){case"offer":this._updateSignalingState("have-remote-offer");break;case"answer":this._updateSignalingState("stable");break;default:throw new TypeError('unsupported type "'+description.type+'"')}return window.setTimeout(function(){null!==self.onaddstream&&stream.getTracks().length&&(self.remoteStreams.push(stream),window.setTimeout(function(){self.onaddstream({stream:stream})},0))},0),arguments.length>1&&"function"==typeof arguments[1]&&window.setTimeout(arguments[1],0),Promise.resolve()},window.RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){transceiver.iceTransport&&transceiver.iceTransport.stop(),transceiver.dtlsTransport&&transceiver.dtlsTransport.stop(),transceiver.rtpSender&&transceiver.rtpSender.stop(),transceiver.rtpReceiver&&transceiver.rtpReceiver.stop()}),this._updateSignalingState("closed")},window.RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState,null!==this.onsignalingstatechange&&this.onsignalingstatechange()},window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){null!==this.onnegotiationneeded&&this.onnegotiationneeded()},window.RTCPeerConnection.prototype._updateConnectionState=function(){var newState,self=this,states={"new":0,closed:0,connecting:0,checking:0,connected:0,completed:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++,states[transceiver.dtlsTransport.state]++}),states.connected+=states.completed,newState="new",states.failed>0?newState="failed":states.connecting>0||states.checking>0?newState="connecting":states.disconnected>0?newState="disconnected":states["new"]>0?newState="new":(states.connecting>0||states.completed>0)&&(newState="connected"),newState!==self.iceConnectionState&&(self.iceConnectionState=newState,null!==this.oniceconnectionstatechange&&this.oniceconnectionstatechange())},window.RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer)throw new Error("createOffer called while there is a pending offer.");var offerOptions;1===arguments.length&&"function"!=typeof arguments[0]?offerOptions=arguments[0]:3===arguments.length&&(offerOptions=arguments[2]);var tracks=[],numAudioTracks=0,numVideoTracks=0;if(this.localStreams.length&&(numAudioTracks=this.localStreams[0].getAudioTracks().length,numVideoTracks=this.localStreams[0].getVideoTracks().length),offerOptions){if(offerOptions.mandatory||offerOptions.optional)throw new TypeError("Legacy mandatory/optional constraints not supported.");void 0!==offerOptions.offerToReceiveAudio&&(numAudioTracks=offerOptions.offerToReceiveAudio),void 0!==offerOptions.offerToReceiveVideo&&(numVideoTracks=offerOptions.offerToReceiveVideo)}for(this.localStreams.length&&this.localStreams[0].getTracks().forEach(function(track){tracks.push({kind:track.kind,track:track,wantReceive:"audio"===track.kind?numAudioTracks>0:numVideoTracks>0}),"audio"===track.kind?numAudioTracks--:"video"===track.kind&&numVideoTracks--});numAudioTracks>0||numVideoTracks>0;)numAudioTracks>0&&(tracks.push({kind:"audio",wantReceive:!0}),numAudioTracks--),numVideoTracks>0&&(tracks.push({kind:"video",wantReceive:!0}),numVideoTracks--);var sdp=SDPUtils.writeSessionBoilerplate(),transceivers=[];tracks.forEach(function(mline,sdpMLineIndex){var rtpSender,rtpReceiver,track=mline.track,kind=mline.kind,mid=generateIdentifier(),transports=self._createIceAndDtlsTransports(mid,sdpMLineIndex),localCapabilities=RTCRtpSender.getCapabilities(kind),sendSsrc=1001*(2*sdpMLineIndex+1);track&&(rtpSender=new RTCRtpSender(track,transports.dtlsTransport)),mline.wantReceive&&(rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind)),transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:null,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,sendSsrc:sendSsrc,recvSsrc:null};var transceiver=transceivers[sdpMLineIndex];sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,"offer",self.localStreams[0])}),this._pendingOffer=transceivers;var desc=new RTCSessionDescription({type:"offer",sdp:sdp});return arguments.length&&"function"==typeof arguments[0]&&window.setTimeout(arguments[0],0,desc),Promise.resolve(desc)},window.RTCPeerConnection.prototype.createAnswer=function(){var answerOptions,self=this;1===arguments.length&&"function"!=typeof arguments[0]?answerOptions=arguments[0]:3===arguments.length&&(answerOptions=arguments[2]);var sdp=SDPUtils.writeSessionBoilerplate();this.transceivers.forEach(function(transceiver){var commonCapabilities=self._getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,"answer",self.localStreams[0])});var desc=new RTCSessionDescription({type:"answer",sdp:sdp});return arguments.length&&"function"==typeof arguments[0]&&window.setTimeout(arguments[0],0,desc),Promise.resolve(desc)},window.RTCPeerConnection.prototype.addIceCandidate=function(candidate){var mLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid)for(var i=0;i0?SDPUtils.parseCandidate(candidate.candidate):{};if("tcp"===cand.protocol&&0===cand.port)return;if("1"!==cand.component)return;"endOfCandidates"===cand.type&&(cand={}),transceiver.iceTransport.addRemoteCandidate(cand)}return arguments.length>1&&"function"==typeof arguments[1]&&window.setTimeout(arguments[1],0),Promise.resolve()},window.RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){["rtpSender","rtpReceiver","iceGatherer","iceTransport","dtlsTransport"].forEach(function(method){transceiver[method]&&promises.push(transceiver[method].getStats())})});var cb=arguments.length>1&&"function"==typeof arguments[1]&&arguments[1];return new Promise(function(resolve){var results={};Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){results[id]=result[id]})}),cb&&window.setTimeout(cb,0,results),resolve(results)})})}}}else webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcUtils.log("This does not appear to be a browser"),webrtcDetectedBrowser="not a browser";var webrtcTesting={};try{Object.defineProperty(webrtcTesting,"version",{set:function(version){webrtcDetectedVersion=version}})}catch(e){}AdapterJS.parseWebrtcDetectedBrowser(),navigator.mozGetUserMedia?(MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:"audio",id:"default",label:"",facing:""},{kind:"video",id:"default",label:"",facing:""}];successCb(infos)},0)},createIceServer=function(url,username,password){var iceServer=null,urlParts=url.split(":");if(0===urlParts[0].indexOf("stun"))iceServer={urls:[url]};else if(0===urlParts[0].indexOf("turn"))if(27>webrtcDetectedVersion){var turnUrlParts=url.split("?");(1===turnUrlParts.length||0===turnUrlParts[1].indexOf("transport=udp"))&&(iceServer={urls:[turnUrlParts[0]],credential:password,username:username})}else iceServer={urls:[url],credential:password,username:username};return iceServer},createIceServers=function(urls,username,password){var iceServers=[];for(i=0;i=34)iceServers={urls:urls,credential:password,username:username};else for(i=0;i=webrtcDetectedVersion){var frag=document.createDocumentFragment();for(AdapterJS.WebRTCPlugin.plugin=document.createElement("div"),AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+"";AdapterJS.WebRTCPlugin.plugin.firstChild;)frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);document.body.appendChild(frag),AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId)}else AdapterJS.WebRTCPlugin.plugin=document.createElement("object"),AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId,isIE?(AdapterJS.WebRTCPlugin.plugin.width="1px",AdapterJS.WebRTCPlugin.plugin.height="1px"):(AdapterJS.WebRTCPlugin.plugin.width="0px",AdapterJS.WebRTCPlugin.plugin.height="0px"),AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type,AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+'',document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED}},AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(isIE){try{new ActiveXObject(comName+"."+plugName)}catch(e){return void notInstalledCb()}installedCb()}else{for(var pluginArray=navigator.plugins,i=0;i=0)return void installedCb();notInstalledCb()}},AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){AdapterJS.WebRTCPlugin.pluginState!==AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY&&(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING,AdapterJS.isDefined=function(variable){return null!==variable&&void 0!==variable},createIceServer=function(url,username,password){var iceServer=null,urlParts=url.split(":");return 0===urlParts[0].indexOf("stun")?iceServer={url:url,hasCredentials:!1}:0===urlParts[0].indexOf("turn")&&(iceServer={url:url,hasCredentials:!0,credential:password,username:username}),iceServer},createIceServers=function(urls,username,password){for(var iceServers=[],i=0;i1)return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers);var iceServers=null;if(servers&&Array.isArray(servers.iceServers)){iceServers=servers.iceServers;for(var i=0;i ';temp.firstChild;)frag.appendChild(temp.firstChild);var height="",width="";element.clientWidth||element.clientHeight?(width=element.clientWidth,height=element.clientHeight):(element.width||element.height)&&(width=element.width,height=element.height),element.parentNode.insertBefore(frag,element),frag=document.getElementById(elementId),frag.width=width,frag.height=height,element.parentNode.removeChild(element)}else{for(var children=element.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){children[i].value=streamId;break}element.setStreamId(streamId)}var newElement=document.getElementById(elementId);return AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element)),newElement}},reattachMediaStream=function(to,from){for(var stream=null,children=from.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){AdapterJS.WebRTCPlugin.WaitForPluginReady(),stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break}return null!==stream?attachMediaStream(to,stream):void 0},window.attachMediaStream=attachMediaStream,window.reattachMediaStream=reattachMediaStream,window.getUserMedia=getUserMedia,AdapterJS.attachMediaStream=attachMediaStream,AdapterJS.reattachMediaStream=reattachMediaStream,AdapterJS.getUserMedia=getUserMedia,AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(var prop in properties)prop&&(propName=properties[prop],"function"==typeof propName.slice&&"on"===propName.slice(0,2)&&"function"==typeof srcElem[propName]&&AdapterJS.addEvent(destElem,propName.slice(2),srcElem[propName]));var subPrototype=Object.getPrototypeOf(prototype);subPrototype&&AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype)},RTCIceCandidate=function(candidate){return candidate.sdpMid||(candidate.sdpMid=""),AdapterJS.WebRTCPlugin.WaitForPluginReady(),AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate)},AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.injectPlugin),AdapterJS.WebRTCPlugin.injectPlugin())},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv),AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv()},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(!AdapterJS.options.hidePluginInstallPrompt){var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;popupString=AdapterJS.WebRTCPlugin.pluginInfo.portalLink?'This website requires you to install the '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+" WebRTC Plugin to work on this browser.":AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION,AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink)}else AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED)}},AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); \ No newline at end of file diff --git a/publish/adapter.screenshare.js b/publish/adapter.screenshare.js index a3b65b9..e4d15b3 100644 --- a/publish/adapter.screenshare.js +++ b/publish/adapter.screenshare.js @@ -1,4 +1,4 @@ -/*! adapterjs - v0.13.0 - 2016-01-08 */ +/*! adapterjs - v0.13.1 - 2016-03-15 */ // Adapter's interface. var AdapterJS = AdapterJS || {}; @@ -17,7 +17,7 @@ AdapterJS.options = AdapterJS.options || {}; // AdapterJS.options.hidePluginInstallPrompt = true; // AdapterJS version -AdapterJS.VERSION = '0.13.0'; +AdapterJS.VERSION = '0.13.1'; // This function will be called when the WebRTC API is ready to be used // Whether it is the native implementation (Chrome, Firefox, Opera) or @@ -58,6 +58,7 @@ AdapterJS.webRTCReady = function (callback) { AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; // The object to store plugin information +/* jshint ignore:start */ AdapterJS.WebRTCPlugin.pluginInfo = { prefix : 'Tem', plugName : 'TemWebRTCPlugin', @@ -74,6 +75,7 @@ if(!!navigator.platform.match(/^Mac/i)) { else if(!!navigator.platform.match(/^Win/i)) { AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; } +/* jshint ignore:end */ AdapterJS.WebRTCPlugin.TAGS = { NONE : 'none', @@ -160,16 +162,14 @@ AdapterJS.WebRTCPlugin.callWhenPluginReady = null; // This function is the only private function that is not encapsulated to // allow the plugin method to be called. __TemWebRTCReady0 = function () { - webrtcDetectedVersion = AdapterJS.WebRTCPlugin.plugin.version; - if (document.readyState === 'complete') { AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } else { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { + var timer = setInterval(function () { if (document.readyState === 'complete') { // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); + clearInterval(timer); AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } @@ -240,65 +240,62 @@ AdapterJS.isDefined = null; // This sets: // - webrtcDetectedBrowser: The browser agent name. // - webrtcDetectedVersion: The browser version. +// - webrtcMinimumVersion: The minimum browser version still supported by AJS. // - webrtcDetectedType: The types of webRTC support. // - 'moz': Mozilla implementation of webRTC. // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. AdapterJS.parseWebrtcDetectedBrowser = function () { - var hasMatch, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + var hasMatch = null; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (typeof InstallTrigger !== 'undefined') { + // Firefox 1.0+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'moz'; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + // Safari + webrtcDetectedBrowser = 'safari'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; + hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (/*@cc_on!@*/false || !!document.documentMode) { + // Internet Explorer 6-11 webrtcDetectedBrowser = 'IE'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 9; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); + if (!webrtcDetectedVersion) { + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } + } else if (!!window.StyleMedia) { + // Edge 20+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = ''; + } else if (!!window.chrome && !!window.chrome.webstore) { + // Chrome 1+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'webkit'; + } else if ((webrtcDetectedBrowser === 'chrome'|| webrtcDetectedBrowser === 'opera') && + !!window.CSS) { + // Blink engine detection + webrtcDetectedBrowser = 'blink'; + // TODO: detected WebRTC version } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; -// To fix configuration as some browsers does not support -// the 'urls' attribute. -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; }; AdapterJS.addEvent = function(elem, evnt, func) { @@ -336,7 +333,7 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe i.style.transition = 'all .5s ease-out'; } document.body.appendChild(i); - c = (i.contentWindow) ? i.contentWindow : + var c = (i.contentWindow) ? i.contentWindow : (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; c.document.open(); c.document.write(' -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; + }; + + // Extracts DTLS parameters from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the fingerprint line as input. See also getIceParameters. + SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. + var fpLine = lines.filter(function(line) { + return line.indexOf('a=fingerprint:') === 0; + })[0].substr(14); + // Note: a=setup line is ignored since we use the 'auto' role. + var dtlsParameters = { + role: 'auto', + fingerprints: [{ + algorithm: fpLine.split(' ')[0], + value: fpLine.split(' ')[1] + }] + }; + return dtlsParameters; + }; + + // Serializes DTLS parameters to SDP. + SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + // Parses ICE information from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the ice-ufrag and ice-pwd lines as input. + SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; + }; + + // Serializes ICE parameters to SDP. + SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; + }; + + // Parses the SDP media section and returns RTCRtpParameters. + SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + } + } + // FIXME: parse headerExtensions, fecMechanisms and rtcp. + return description; + }; + + // Generates parts of the SDP media section describing the capabilities / parameters. + SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFtmp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + // FIXME: add headerExtensions, fecMechanismş and rtcp. + sdp += 'a=rtcp-mux\r\n'; + return sdp; + }; + + SDPUtils.writeSessionBoilerplate = function() { + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + }; + + SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. + if (transceiver.rtpSender) { + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid; + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' + + localCName + '\r\n'; + return sdp; + }; + + // Gets the direction from the mediaSection or the sessionpart. + SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; + }; + + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + + window.RTCPeerConnection = function(config) { + var self = this; + + this.onicecandidate = null; + this.onaddstream = null; + this.onremovestream = null; + this.onsignalingstatechange = null; + this.oniceconnectionstatechange = null; + this.onnegotiationneeded = null; + this.ondatachannel = null; + + this.localStreams = []; + this.remoteStreams = []; + this.getLocalStreams = function() { return self.localStreams; }; + this.getRemoteStreams = function() { return self.remoteStreams; }; + + this.localDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.remoteDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + + this.iceOptions = { + gatherPolicy: 'all', + iceServers: [] + }; + if (config && config.iceTransportPolicy) { + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + this.iceOptions.gatherPolicy = config.iceTransportPolicy; + break; + case 'none': + // FIXME: remove once implementation and spec have added this. + throw new TypeError('iceTransportPolicy "none" not supported'); + } + } + if (config && config.iceServers) { + // Edge does not like + // 1) stun: + // 2) turn: that does not have all of turn:host:port?transport=udp + // 3) an array of urls + config.iceServers.forEach(function(server) { + if (server.urls) { + var url; + if (typeof(server.urls) === 'string') { + url = server.urls; + } else { + url = server.urls[0]; + } + if (url.indexOf('transport=udp') !== -1) { + self.iceServers.push({ + username: server.username, + credential: server.credential, + urls: url + }); + } + } + }); + } + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + // since the iceGatherer is currently created in createOffer but we + // must not emit candidates until after setLocalDescription we buffer + // them in this array. + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { + var self = this; + // FIXME: need to apply ice candidates in a way which is async but in-order + this._localIceCandidatesBuffer.forEach(function(event) { + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + }); + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype.addStream = function(stream) { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + this.localStreams.push(stream.clone()); + this._maybeFireNegotiationNeeded(); + }; + + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this._maybeFireNegotiationNeeded(); + } + }; + + // Determines the intersection of local and remote capabilities. + window.RTCPeerConnection.prototype._getCommonCapabilities = + function(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate && + lCodec.numChannels === rCodec.numChannels) { + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // FIXME: also need to determine intersection between + // .rtcpFeedback and .parameters + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; + }; + + // Create ICE gatherer, ICE transport and DTLS transport. + window.RTCPeerConnection.prototype._createIceAndDtlsTransports = + function(mid, sdpMLineIndex) { + var self = this; + var iceGatherer = new RTCIceGatherer(self.iceOptions); + var iceTransport = new RTCIceTransport(iceGatherer); + iceGatherer.onlocalcandidate = function(evt) { + var event = {}; + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + if (!cand || Object.keys(cand).length === 0) { + // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet. + if (iceGatherer.state === undefined) { + iceGatherer.state = 'completed'; + } + + // Emit a candidate with type endOfCandidates to make the samples work. + // Edge requires addIceCandidate with this empty candidate to start checking. + // The real solution is to signal end-of-candidates to the other side when + // getting the null candidate but some apps (like the samples) don't do that. + event.candidate.candidate = + 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; + } else { + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = iceTransport.component === 'RTCP' ? 2 : 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + // FIXME: update .localDescription with candidate and (potentially) end-of-candidates. + // To make this harder, the gatherer might emit candidates before localdescription + // is set. To make things worse, gather.getLocalCandidates still errors in + // Edge 10547 when no candidates have been gathered yet. + + if (self.onicecandidate !== null) { + // Emit candidate if localDescription is set. + // Also emits null candidate when all gatherers are complete. + if (self.localDescription && self.localDescription.type === '') { + self._localIceCandidatesBuffer.push(event); + if (complete) { + self._localIceCandidatesBuffer.push({}); + } + } else { + self.onicecandidate(event); + if (complete) { + self.onicecandidate({}); + } + } + } + }; + iceTransport.onicestatechange = function() { + self._updateConnectionState(); + }; + + var dtlsTransport = new RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + self._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itself. + dtlsTransport.state = 'failed'; + self._updateConnectionState(); + }; + + return { + iceGatherer: iceGatherer, + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Start the RTP Sender and Receiver for a transceiver. + window.RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = this._getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = [{ + ssrc: transceiver.sendSsrc + }]; + params.rtcp = { + cname: localCName, + ssrc: transceiver.recvSsrc + }; + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver) { + params.encodings = [{ + ssrc: transceiver.recvSsrc + }]; + params.rtcp = { + cname: transceiver.cname, + ssrc: transceiver.sendSsrc + }; + transceiver.rtpReceiver.receive(params); + } + }; + + window.RTCPeerConnection.prototype.setLocalDescription = + function(description) { + var self = this; + if (description.type === 'offer') { + if (!this._pendingOffer) { + } else { + this.transceivers = this._pendingOffer; + delete this._pendingOffer; + } + } else if (description.type === 'answer') { + var sections = SDPUtils.splitSections(self.remoteDescription.sdp); + var sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = self.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + var rejected = mediaSection.split('\n', 1)[0] + .split(' ', 2)[1] === '0'; + + if (!rejected) { + var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + iceTransport.start(iceGatherer, remoteIceParameters, 'controlled'); + + var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + dtlsTransport.start(remoteDtlsParameters); + + // Calculate intersection of capabilities. + var params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this transceiver + // has already been started in setRemoteDescription. + self._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + '"'); + } + + // If a success callback was provided, emit ICE candidates after it has been + // executed. Otherwise, emit callback after the Promise is resolved. + var hasCallback = arguments.length > 1 && + typeof arguments[1] === 'function'; + if (hasCallback) { + var cb = arguments[1]; + window.setTimeout(function() { + cb(); + self._emitBufferedCandidates(); + }, 0); + } + var p = Promise.resolve(); + p.then(function() { + if (!hasCallback) { + window.setTimeout(self._emitBufferedCandidates.bind(self), 0); + } + }); + return p; + }; + + window.RTCPeerConnection.prototype.setRemoteDescription = + function(description) { + var self = this; + var stream = new MediaStream(); + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].substr(2).split(' '); + var kind = mline[0]; + var rejected = mline[1] === '0'; + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpSender; + var rtpReceiver; + var sendSsrc; + var recvSsrc; + var localCapabilities; + + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + } + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6); + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + recvSsrc = parseInt(remoteSsrc.ssrc, 10); + cname = remoteSsrc.value; + } + + if (description.type === 'offer') { + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + localCapabilities = RTCRtpReceiver.getCapabilities(kind); + sendSsrc = (2 * sdpMLineIndex + 2) * 1001; + + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + + // FIXME: not correct when there are multiple streams but that is + // not currently supported in this shim. + stream.addTrack(rtpReceiver.track); + + // FIXME: look at direction. + if (self.localStreams.length > 0 && + self.localStreams[0].getTracks().length >= sdpMLineIndex) { + // FIXME: actually more complicated, needs to match types etc + var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex]; + rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport); + } + + self.transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: remoteCapabilities, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + cname: cname, + sendSsrc: sendSsrc, + recvSsrc: recvSsrc + }; + // Start the RTCRtpReceiver now. The RTPSender is started in setLocalDescription. + self._transceive(self.transceivers[sdpMLineIndex], + false, + direction === 'sendrecv' || direction === 'sendonly'); + } else if (description.type === 'answer' && !rejected) { + transceiver = self.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpSender = transceiver.rtpSender; + rtpReceiver = transceiver.rtpReceiver; + sendSsrc = transceiver.sendSsrc; + //recvSsrc = transceiver.recvSsrc; + localCapabilities = transceiver.localCapabilities; + + self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc; + self.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + self.transceivers[sdpMLineIndex].cname = cname; + + iceTransport.start(iceGatherer, remoteIceParameters, 'controlling'); + dtlsTransport.start(remoteDtlsParameters); + + self._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + stream.addTrack(rtpReceiver.track); + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + this.remoteDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + '"'); + } + window.setTimeout(function() { + if (self.onaddstream !== null && stream.getTracks().length) { + self.remoteStreams.push(stream); + window.setTimeout(function() { + self.onaddstream({stream: stream}); + }, 0); + } + }, 0); + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + window.RTCPeerConnection.prototype._updateSignalingState = + function(newState) { + this.signalingState = newState; + if (this.onsignalingstatechange !== null) { + this.onsignalingstatechange(); + } + }; + + // Determine whether to fire the negotiationneeded event. + window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = + function() { + // Fire away (for now). + if (this.onnegotiationneeded !== null) { + this.onnegotiationneeded(); + } + }; + + // Update the connection state. + window.RTCPeerConnection.prototype._updateConnectionState = + function() { + var self = this; + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connecting > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== self.iceConnectionState) { + self.iceConnectionState = newState; + if (this.oniceconnectionstatechange !== null) { + this.oniceconnectionstatechange(); + } + } + }; + + window.RTCPeerConnection.prototype.createOffer = function() { + var self = this; + if (this._pendingOffer) { + throw new Error('createOffer called while there is a pending offer.'); + } + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; + } + + var tracks = []; + var numAudioTracks = 0; + var numVideoTracks = 0; + // Default to sendrecv. + if (this.localStreams.length) { + numAudioTracks = this.localStreams[0].getAudioTracks().length; + numVideoTracks = this.localStreams[0].getVideoTracks().length; + } + // Determine number of audio and video tracks we need to send/recv. + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + if (offerOptions.offerToReceiveVideo !== undefined) { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + if (this.localStreams.length) { + // Push local streams. + this.localStreams[0].getTracks().forEach(function(track) { + tracks.push({ + kind: track.kind, + track: track, + wantReceive: track.kind === 'audio' ? + numAudioTracks > 0 : numVideoTracks > 0 + }); + if (track.kind === 'audio') { + numAudioTracks--; + } else if (track.kind === 'video') { + numVideoTracks--; + } + }); + } + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + tracks.push({ + kind: 'audio', + wantReceive: true + }); + numAudioTracks--; + } + if (numVideoTracks > 0) { + tracks.push({ + kind: 'video', + wantReceive: true + }); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + var transceivers = []; + tracks.forEach(function(mline, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, dtls transport, + // potentially rtpsender and rtpreceiver. + var track = mline.track; + var kind = mline.kind; + var mid = generateIdentifier(); + + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpSender.getCapabilities(kind); + var rtpSender; + var rtpReceiver; + + // generate an ssrc now, to be used later in rtpSender.send + var sendSsrc = (2 * sdpMLineIndex + 1) * 1001; + if (track) { + rtpSender = new RTCRtpSender(track, transports.dtlsTransport); + } + + if (mline.wantReceive) { + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + } + + transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: null, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendSsrc: sendSsrc, + recvSsrc: null + }; + var transceiver = transceivers[sdpMLineIndex]; + sdp += SDPUtils.writeMediaSection(transceiver, + transceiver.localCapabilities, 'offer', self.localStreams[0]); + }); + + this._pendingOffer = transceivers; + var desc = new RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.createAnswer = function() { + var self = this; + var answerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + answerOptions = arguments[0]; + } else if (arguments.length === 3) { + answerOptions = arguments[2]; + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + this.transceivers.forEach(function(transceiver) { + // Calculate intersection of capabilities. + var commonCapabilities = self._getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, + 'answer', self.localStreams[0]); + }); + + var desc = new RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var mLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + mLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[mLineIndex]; + if (transceiver) { + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && cand.port === 0) { + return; + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component !== '1') { + return; + } + // A dirty hack to make samples work. + if (cand.type === 'endOfCandidates') { + cand = {}; + } + transceiver.iceTransport.addRemoteCandidate(cand); + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + var results = {}; + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + results[id] = result[id]; + }); + }); + if (cb) { + window.setTimeout(cb, 0, results); + } + resolve(results); + }); + }); + }; + } } else { webrtcUtils.log('Browser does not appear to be WebRTC-capable'); } @@ -1074,11 +2256,17 @@ if ( navigator.mozGetUserMedia /* Orginal exports removed in favor of AdapterJS custom export. if (typeof module !== 'undefined') { var RTCPeerConnection; + var RTCIceCandidate; + var RTCSessionDescription; if (typeof window !== 'undefined') { RTCPeerConnection = window.RTCPeerConnection; + RTCIceCandidate = window.RTCIceCandidate; + RTCSessionDescription = window.RTCSessionDescription; } module.exports = { RTCPeerConnection: RTCPeerConnection, + RTCIceCandidate: RTCIceCandidate, + RTCSessionDescription: RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1095,6 +2283,8 @@ if ( navigator.mozGetUserMedia define([], function() { return { RTCPeerConnection: window.RTCPeerConnection, + RTCIceCandidate: window.RTCIceCandidate, + RTCSessionDescription: window.RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1110,13 +2300,16 @@ if ( navigator.mozGetUserMedia } */ +/* jshint ignore:end */ // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// + AdapterJS.parseWebrtcDetectedBrowser(); + /////////////////////////////////////////////////////////////////// // EXTENSION FOR CHROME, FIREFOX AND EDGE - // Includes legacy functions + // Includes legacy functions // -- createIceServer // -- createIceServers // -- MediaStreamTrack.getSources @@ -1142,25 +2335,26 @@ if ( navigator.mozGetUserMedia createIceServer = function (url, username, password) { console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); - + // Note: Google's import of AJS will auto-reverse to 'url': '...' for FF < 38 + var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { url : url }; - } else if (url_parts[0].indexOf('turn') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { urls : [url] }; + } else if (urlParts[0].indexOf('turn') === 0) { if (webrtcDetectedVersion < 27) { - var turn_url_parts = url.split('?'); - if (turn_url_parts.length === 1 || - turn_url_parts[1].indexOf('transport=udp') === 0) { + var turnUrlParts = url.split('?'); + if (turnUrlParts.length === 1 || + turnUrlParts[1].indexOf('transport=udp') === 0) { iceServer = { - url : turn_url_parts[0], + urls : [turnUrlParts[0]], credential : password, username : username }; } } else { iceServer = { - url : url, + urls : [url], credential : password, username : username }; @@ -1184,12 +2378,12 @@ if ( navigator.mozGetUserMedia } else if ( navigator.webkitGetUserMedia ) { createIceServer = function (url, username, password) { console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); - + var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { iceServer = { 'url' : url }; - } else if (url_parts[0].indexOf('turn') === 0) { + } else if (urlParts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'credential' : password, @@ -1225,7 +2419,7 @@ if ( navigator.mozGetUserMedia // attachMediaStream and reattachMediaStream for Egde if (navigator.mediaDevices && navigator.userAgent.match( /Edge\/(\d+).(\d+)$/)) { - window.getUserMedia = navigator.getUserMedia.bind(navigator); + getUserMedia = window.getUserMedia = navigator.getUserMedia.bind(navigator); attachMediaStream = function(element, stream) { element.srcObject = stream; return element; @@ -1236,11 +2430,18 @@ if ( navigator.mozGetUserMedia }; } - // Need to override attachMediaStream and reattachMediaStream + // Need to override attachMediaStream and reattachMediaStream // to support the plugin's logic attachMediaStream_base = attachMediaStream; attachMediaStream = function (element, stream) { - attachMediaStream_base(element, stream); + if ((webrtcDetectedBrowser === 'chrome' || + webrtcDetectedBrowser === 'opera') && + !stream) { + // Chrome does not support "src = null" + element.src = ''; + } else { + attachMediaStream_base(element, stream); + } return element; }; reattachMediaStream_base = reattachMediaStream; @@ -1249,6 +2450,14 @@ if ( navigator.mozGetUserMedia return to; }; + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + // Removed Google defined promises when promise is not defined if (typeof Promise === 'undefined') { requestUserMedia = null; @@ -1305,7 +2514,6 @@ if ( navigator.mozGetUserMedia console.groupEnd = function (arg) {}; /* jshint +W020 */ } - webrtcDetectedType = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; @@ -1431,7 +2639,7 @@ if ( navigator.mozGetUserMedia AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - console.error("AdapterJS - WebRTC interface has already been defined"); + console.error('AdapterJS - WebRTC interface has already been defined'); return; } @@ -1443,13 +2651,13 @@ if ( navigator.mozGetUserMedia createIceServer = function (url, username, password) { var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { iceServer = { 'url' : url, 'hasCredentials' : false }; - } else if (url_parts[0].indexOf('turn') === 0) { + } else if (urlParts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'hasCredentials' : true, @@ -1475,27 +2683,58 @@ if ( navigator.mozGetUserMedia }; RTCPeerConnection = function (servers, constraints) { - var iceServers = null; - if (servers) { - iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - if (iceServers[i].urls && !iceServers[i].url) { - iceServers[i].url = iceServers[i].urls; - } - iceServers[i].hasCredentials = AdapterJS. - isDefined(iceServers[i].username) && - AdapterJS.isDefined(iceServers[i].credential); + // Validate server argumenr + if (!(servers === undefined || + servers === null || + Array.isArray(servers.iceServers))) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); + } + + // Validate constraints argument + if (typeof constraints !== 'undefined' && constraints !== null) { + var invalidConstraits = false; + invalidConstraits |= typeof constraints !== 'object'; + invalidConstraits |= constraints.hasOwnProperty('mandatory') && + constraints.mandatory !== undefined && + constraints.mandatory !== null && + constraints.mandatory.constructor !== Object; + invalidConstraits |= constraints.hasOwnProperty('optional') && + constraints.optional !== undefined && + constraints.optional !== null && + !Array.isArray(constraints.optional); + if (invalidConstraits) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed constraints object'); } } - var mandatory = (constraints && constraints.mandatory) ? - constraints.mandatory : null; - var optional = (constraints && constraints.optional) ? - constraints.optional : null; + // Call relevant PeerConnection constructor according to plugin version AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - PeerConnection(AdapterJS.WebRTCPlugin.pageId, - iceServers, mandatory, optional); + if (AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION && + AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION > 1) { + // RTCPeerConnection prototype from the new spec + return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); + } else { + // RTCPeerConnection prototype from the old spec + var iceServers = null; + if (servers && Array.isArray(servers.iceServers)) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + } }; MediaStreamTrack = {}; @@ -1505,7 +2744,7 @@ if ( navigator.mozGetUserMedia }); }; - window.getUserMedia = function (constraints, successCallback, failureCallback) { + getUserMedia = function (constraints, successCallback, failureCallback) { constraints.audio = constraints.audio || false; constraints.video = constraints.video || false; @@ -1514,11 +2753,16 @@ if ( navigator.mozGetUserMedia getUserMedia(constraints, successCallback, failureCallback); }); }; - window.navigator.getUserMedia = window.getUserMedia; + window.navigator.getUserMedia = getUserMedia; // Defined mediaDevices when promises are available - if ( !navigator.mediaDevices - && typeof Promise !== 'undefined') { + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { + requestUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + }; navigator.mediaDevices = {getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { @@ -1527,6 +2771,7 @@ if ( navigator.mozGetUserMedia resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], + id: device.id, deviceId: device.id, groupId: ''}; })); @@ -1636,31 +2881,32 @@ if ( navigator.mozGetUserMedia } }; - AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { properties = Object.getOwnPropertyNames( prototype ); - - for(prop in properties) { - propName = properties[prop]; - - if (typeof(propName.slice) === 'function') { - if (propName.slice(0,2) == 'on' && srcElem[propName] != null) { - if (isIE) { - destElem.attachEvent(propName,srcElem[propName]); - } else { - destElem.addEventListener(propName.slice(2), srcElem[propName], false) - } - } else { - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? + for(var prop in properties) { + if (prop) { + propName = properties[prop]; + + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); } } } - - var subPrototype = Object.getPrototypeOf(prototype) - if(subPrototype != null) { + var subPrototype = Object.getPrototypeOf(prototype); + if(!!subPrototype) { AdapterJS.forwardEventHandlers(destElem, srcElem, subPrototype); } - } + }; RTCIceCandidate = function (candidate) { if (!candidate.sdpMid) { @@ -1738,10 +2984,14 @@ if ( navigator.mozGetUserMedia }; var clone = function(obj) { - if (null == obj || "object" != typeof obj) return obj; + if (null === obj || 'object' !== typeof obj) { + return obj; + } var copy = obj.constructor(); for (var attr in obj) { - if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; + if (obj.hasOwnProperty(attr)) { + copy[attr] = obj[attr]; + } } return copy; }; @@ -1772,11 +3022,10 @@ if ( navigator.mozGetUserMedia clearInterval(checkIfReady); baseGetUserMedia(updatedConstraints, successCb, function (error) { - if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') { + if (['PermissionDeniedError', 'SecurityError'].indexOf(error.name) > -1 && window.parent.location.protocol === 'https:') { AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF, AdapterJS.TEXT.EXTENSION.BUTTON_FF, - 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true); - //window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname; + 'https://addons.mozilla.org/en-US/firefox/addon/skylink-webrtc-tools/', true, true); } else { failureCb(error); } @@ -1789,7 +3038,12 @@ if ( navigator.mozGetUserMedia } }; - getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + window.getUserMedia(constraints, resolve, reject); + }); + }; } else if (window.navigator.webkitGetUserMedia) { baseGetUserMedia = window.navigator.getUserMedia; @@ -1869,7 +3123,12 @@ if ( navigator.mozGetUserMedia } }; - getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + window.getUserMedia(constraints, resolve, reject); + }); + }; } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // nothing here because edge does not support screensharing @@ -1906,7 +3165,9 @@ if ( navigator.mozGetUserMedia } }; - getUserMedia = window.navigator.getUserMedia; + AdapterJS.getUserMedia = getUserMedia = + window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = requestUserMedia; } // For chrome, use an iframe to load the screensharing extension @@ -1924,7 +3185,7 @@ if ( navigator.mozGetUserMedia (document.body || document.documentElement).appendChild(iframe); - var postFrameMessage = function (object) { + var postFrameMessage = function (object) { // jshint ignore:line object = object || {}; if (!iframe.isLoaded) { diff --git a/publish/adapter.screenshare.min.js b/publish/adapter.screenshare.min.js index 3b90802..26ace6a 100644 --- a/publish/adapter.screenshare.min.js +++ b/publish/adapter.screenshare.min.js @@ -1,3 +1,3 @@ -/*! adapterjs - v0.13.0 - 2016-01-08 */ -function trace(text){if("\n"===text[text.length-1]&&(text=text.substring(0,text.length-1)),window.performance){var now=(window.performance.now()/1e3).toFixed(3);webrtcUtils.log(now+": "+text)}else webrtcUtils.log(text)}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}var AdapterJS=AdapterJS||{};if("undefined"!=typeof exports&&(module.exports=AdapterJS),AdapterJS.options=AdapterJS.options||{},AdapterJS.VERSION="0.13.0",AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){},AdapterJS._onwebrtcreadies=[],AdapterJS.webRTCReady=function(callback){if("function"!=typeof callback)throw new Error("Callback provided is not a function");!0===AdapterJS.onwebrtcreadyDone?callback(null!==AdapterJS.WebRTCPlugin.plugin):AdapterJS._onwebrtcreadies.push(callback)},AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{},AdapterJS.WebRTCPlugin.pluginInfo={prefix:"Tem",plugName:"TemWebRTCPlugin",pluginId:"plugin0",type:"application/x-temwebrtcplugin",onload:"__TemWebRTCReady0",portalLink:"http://skylink.io/plugin/",downloadLink:null,companyName:"Temasys"},navigator.platform.match(/^Mac/i)?AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1n77hco":navigator.platform.match(/^Win/i)&&(AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1kkS4FN"),AdapterJS.WebRTCPlugin.TAGS={NONE:"none",AUDIO:"audio",VIDEO:"video"},AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2),AdapterJS.WebRTCPlugin.plugin=null,AdapterJS.WebRTCPlugin.setLogLevel=null,AdapterJS.WebRTCPlugin.defineWebRTCInterface=null,AdapterJS.WebRTCPlugin.isPluginInstalled=null,AdapterJS.WebRTCPlugin.pluginInjectionInterval=null,AdapterJS.WebRTCPlugin.injectPlugin=null,AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4},AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE,AdapterJS.onwebrtcreadyDone=!1,AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:"NONE",ERROR:"ERROR",WARNING:"WARNING",INFO:"INFO",VERBOSE:"VERBOSE",SENSITIVE:"SENSITIVE"},AdapterJS.WebRTCPlugin.WaitForPluginReady=null,AdapterJS.WebRTCPlugin.callWhenPluginReady=null,__TemWebRTCReady0=function(){webrtcDetectedVersion=AdapterJS.WebRTCPlugin.plugin.version,"complete"===document.readyState?(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady()):AdapterJS.WebRTCPlugin.documentReadyInterval=setInterval(function(){"complete"===document.readyState&&(clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval),AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady())},100)},AdapterJS.maybeThroughWebRTCReady=function(){AdapterJS.onwebrtcreadyDone||(AdapterJS.onwebrtcreadyDone=!0,AdapterJS._onwebrtcreadies.length?AdapterJS._onwebrtcreadies.forEach(function(callback){"function"==typeof callback&&callback(null!==AdapterJS.WebRTCPlugin.plugin)}):"function"==typeof AdapterJS.onwebrtcready&&AdapterJS.onwebrtcready(null!==AdapterJS.WebRTCPlugin.plugin))},AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:"This website requires you to install a WebRTC-enabling plugin to work on this browser.",NOT_SUPPORTED:"Your browser does not support WebRTC.",BUTTON:"Install Now"},REFRESH:{REQUIRE_REFRESH:"Please refresh page",BUTTON:"Refresh Page"}},AdapterJS._iceConnectionStates={starting:"starting",checking:"checking",connected:"connected",completed:"connected",done:"completed",disconnected:"disconnected",failed:"failed",closed:"closed"},AdapterJS._iceConnectionFiredStates=[],AdapterJS.isDefined=null,AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch,checkMatch=navigator.userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];if(/trident/i.test(checkMatch[1])?(hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedBrowser="IE",webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10)):"Chrome"===checkMatch[1]&&(hasMatch=navigator.userAgent.match(/\bOPR\/(\d+)/),null!==hasMatch&&(webrtcDetectedBrowser="opera",webrtcDetectedVersion=parseInt(hasMatch[1],10))),navigator.userAgent.indexOf("Safari")&&("undefined"!=typeof InstallTrigger?webrtcDetectedBrowser="firefox":document.documentMode?webrtcDetectedBrowser="IE":Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0?webrtcDetectedBrowser="safari":window.opera||navigator.userAgent.indexOf(" OPR/")>=0?webrtcDetectedBrowser="opera":window.chrome&&(webrtcDetectedBrowser="chrome")),webrtcDetectedBrowser||(webrtcDetectedVersion=checkMatch[1]),!webrtcDetectedVersion)try{checkMatch=checkMatch[2]?[checkMatch[1],checkMatch[2]]:[navigator.appName,navigator.appVersion,"-?"],null!==(hasMatch=navigator.userAgent.match(/version\/(\d+)/i))&&checkMatch.splice(1,1,hasMatch[1]),webrtcDetectedVersion=parseInt(checkMatch[1],10)}catch(error){}},AdapterJS.maybeFixConfiguration=function(pcConfig){if(null!==pcConfig)for(var i=0;i'+text+""),buttonText&&buttonLink?(c.document.write(''),c.document.close(),AdapterJS.addEvent(c.document.getElementById("okay"),"click",function(e){displayRefreshBar&&AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,"javascript:location.reload()"),window.open(buttonLink,openNewTab?"_blank":"_top"),e.preventDefault();try{event.cancelBubble=!0}catch(error){}var pluginInstallInterval=setInterval(function(){isIE||navigator.plugins.refresh(!1),AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval),AdapterJS.WebRTCPlugin.defineWebRTCInterface()},function(){})},500)}),AdapterJS.addEvent(c.document.getElementById("cancel"),"click",function(e){w.document.body.removeChild(i)})):c.document.close(),setTimeout(function(){"string"==typeof i.style.webkitTransform?i.style.webkitTransform="translateY(40px)":"string"==typeof i.style.transform?i.style.transform="translateY(40px)":i.style.top="0px"},300)}},webrtcDetectedType=null,checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if("function"==typeof callback){var beOfferer=!0,isLocalFirefox="firefox"===webrtcDetectedBrowser,isLocalFirefoxInterop="moz"===webrtcDetectedType&&webrtcDetectedVersion>30,isPeerFirefox="firefox"===peerBrowserAgent;if(isLocalFirefox&&isPeerFirefox||isLocalFirefoxInterop)try{delete constraints.mandatory.MozDontOfferDataChannel}catch(error){}else isLocalFirefox&&!isPeerFirefox&&(constraints.mandatory.MozDontOfferDataChannel=!0);if(!isLocalFirefox)for(var prop in constraints.mandatory)constraints.mandatory.hasOwnProperty(prop)&&-1!==prop.indexOf("Moz")&&delete constraints.mandatory[prop];!isLocalFirefox||isPeerFirefox||isLocalFirefoxInterop||(beOfferer=!1),callback(beOfferer,constraints)}},checkIceConnectionState=function(peerId,iceConnectionState,callback){"function"==typeof callback&&(peerId=peerId?peerId:"peer",AdapterJS._iceConnectionFiredStates[peerId]&&iceConnectionState!==AdapterJS._iceConnectionStates.disconnected&&iceConnectionState!==AdapterJS._iceConnectionStates.failed&&iceConnectionState!==AdapterJS._iceConnectionStates.closed||(AdapterJS._iceConnectionFiredStates[peerId]=[]),iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState],AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0&&(AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState),iceConnectionState===AdapterJS._iceConnectionStates.connected&&setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done),callback(AdapterJS._iceConnectionStates.done)},1e3),callback(iceConnectionState)))},createIceServer=null,createIceServers=null,RTCPeerConnection=null,RTCSessionDescription="function"==typeof RTCSessionDescription?RTCSessionDescription:null,RTCIceCandidate="function"==typeof RTCIceCandidate?RTCIceCandidate:null,getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,navigator.mozGetUserMedia||navigator.webkitGetUserMedia||navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){var getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils={log:function(){"undefined"!=typeof module||"function"==typeof require&&"function"==typeof define},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos])}};if("object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=URL.createObjectURL(stream))}}),getUserMedia=window.navigator&&window.navigator.getUserMedia),attachMediaStream=function(element,stream){element.srcObject=stream},reattachMediaStream=function(to,from){to.srcObject=from.srcObject},"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia&&window.mozRTCPeerConnection){if(webrtcUtils.log("This appears to be Firefox"),webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,window.RTCPeerConnection=function(pcConfig,pcConstraints){if(38>webrtcDetectedVersion&&pcConfig&&pcConfig.iceServers){for(var newIceServers=[],i=0;iwebrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,addEventListener:function(){},removeEventListener:function(){}}),navigator.mediaDevices.enumerateDevices=navigator.mediaDevices.enumerateDevices||function(){return new Promise(function(resolve){var infos=[{kind:"audioinput",deviceId:"default",label:"",groupId:""},{kind:"videoinput",deviceId:"default",label:"",groupId:""}];resolve(infos)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcUtils.log("This appears to be Chrome"),webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38,window.RTCPeerConnection=function(pcConfig,pcConstraints){pcConfig&&pcConfig.iceTransportPolicy&&(pcConfig.iceTransports=pcConfig.iceTransportPolicy);var pc=new webkitRTCPeerConnection(pcConfig,pcConstraints),origGetStats=pc.getStats.bind(pc);return pc.getStats=function(selector,successCallback,errorCallback){var self=this,args=arguments;if(arguments.length>0&&"function"==typeof selector)return origGetStats(selector,successCallback);var fixChromeStats=function(response){var standardReport={},reports=response.result();return reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name)}),standardReport[standardStats.id]=standardStats}),standardReport};if(arguments.length>=2){var successCallbackWrapper=function(response){args[1](fixChromeStats(response))};return origGetStats.apply(this,[successCallbackWrapper,arguments[0]])}return new Promise(function(resolve,reject){1===args.length&&null===selector?origGetStats.apply(self,[function(response){resolve.apply(null,[fixChromeStats(response)])},reject]):origGetStats.apply(self,[resolve,reject])})},pc},["createOffer","createAnswer"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||1===arguments.length&&"object"==typeof arguments[0]){var opts=1===arguments.length?arguments[0]:void 0;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts])})}return nativeMethod.apply(this,arguments)}}),["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments,self=this;return new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],function(){resolve(),args.length>=2&&args[1].apply(null,[])},function(err){reject(err),args.length>=3&&args[2].apply(null,[err])}])})}});var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:"audioinput",video:"videoinput"};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:""}}))})})}}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")}),attachMediaStream=function(element,stream){webrtcDetectedVersion>=43?element.srcObject=stream:"undefined"!=typeof element.src?element.src=URL.createObjectURL(stream):webrtcUtils.log("Error attaching stream to element.")},reattachMediaStream=function(to,from){webrtcDetectedVersion>=43?to.srcObject=from.srcObject:to.src=from.src}}else navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)?(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=12):webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcUtils.log("This does not appear to be a browser"),webrtcDetectedBrowser="not a browser";var webrtcTesting={};try{Object.defineProperty(webrtcTesting,"version",{set:function(version){webrtcDetectedVersion=version}})}catch(e){}navigator.mozGetUserMedia?(MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:"audio",id:"default",label:"",facing:""},{kind:"video",id:"default",label:"",facing:""}];successCb(infos)},0)},createIceServer=function(url,username,password){var iceServer=null,url_parts=url.split(":");if(0===url_parts[0].indexOf("stun"))iceServer={url:url};else if(0===url_parts[0].indexOf("turn"))if(27>webrtcDetectedVersion){var turn_url_parts=url.split("?");(1===turn_url_parts.length||0===turn_url_parts[1].indexOf("transport=udp"))&&(iceServer={url:turn_url_parts[0],credential:password,username:username})}else iceServer={url:url,credential:password,username:username};return iceServer},createIceServers=function(urls,username,password){var iceServers=[];for(i=0;i=34)iceServers={urls:urls,credential:password,username:username};else for(i=0;i=webrtcDetectedVersion){var frag=document.createDocumentFragment();for(AdapterJS.WebRTCPlugin.plugin=document.createElement("div"),AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+"";AdapterJS.WebRTCPlugin.plugin.firstChild;)frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);document.body.appendChild(frag),AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId)}else AdapterJS.WebRTCPlugin.plugin=document.createElement("object"),AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId,isIE?(AdapterJS.WebRTCPlugin.plugin.width="1px",AdapterJS.WebRTCPlugin.plugin.height="1px"):(AdapterJS.WebRTCPlugin.plugin.width="0px",AdapterJS.WebRTCPlugin.plugin.height="0px"),AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type,AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+'',document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED}},AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(isIE){try{new ActiveXObject(comName+"."+plugName)}catch(e){return void notInstalledCb()}installedCb()}else{for(var pluginArray=navigator.plugins,i=0;i=0)return void installedCb();notInstalledCb()}},AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){AdapterJS.WebRTCPlugin.pluginState!==AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY&&(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING,AdapterJS.isDefined=function(variable){return null!==variable&&void 0!==variable},createIceServer=function(url,username,password){var iceServer=null,url_parts=url.split(":");return 0===url_parts[0].indexOf("stun")?iceServer={url:url,hasCredentials:!1}:0===url_parts[0].indexOf("turn")&&(iceServer={url:url,hasCredentials:!0,credential:password,username:username}),iceServer},createIceServers=function(urls,username,password){for(var iceServers=[],i=0;i ';temp.firstChild;)frag.appendChild(temp.firstChild);var height="",width="";element.clientWidth||element.clientHeight?(width=element.clientWidth,height=element.clientHeight):(element.width||element.height)&&(width=element.width,height=element.height),element.parentNode.insertBefore(frag,element),frag=document.getElementById(elementId),frag.width=width,frag.height=height,element.parentNode.removeChild(element)}else{for(var children=element.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){children[i].value=streamId;break}element.setStreamId(streamId)}var newElement=document.getElementById(elementId);return AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element)),newElement}},reattachMediaStream=function(to,from){for(var stream=null,children=from.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){AdapterJS.WebRTCPlugin.WaitForPluginReady(),stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break}return null!==stream?attachMediaStream(to,stream):void 0},AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(prop in properties)propName=properties[prop],"function"==typeof propName.slice&&"on"==propName.slice(0,2)&&null!=srcElem[propName]&&(isIE?destElem.attachEvent(propName,srcElem[propName]):destElem.addEventListener(propName.slice(2),srcElem[propName],!1));var subPrototype=Object.getPrototypeOf(prototype);null!=subPrototype&&AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype)},RTCIceCandidate=function(candidate){return candidate.sdpMid||(candidate.sdpMid=""),AdapterJS.WebRTCPlugin.WaitForPluginReady(),AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate)},AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.injectPlugin),AdapterJS.WebRTCPlugin.injectPlugin())},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv),AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv()},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(!AdapterJS.options.hidePluginInstallPrompt){var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;popupString=AdapterJS.WebRTCPlugin.pluginInfo.portalLink?'This website requires you to install the '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+" WebRTC Plugin to work on this browser.":AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION,AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink)}else AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED)}},AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); -!function(){"use strict";var baseGetUserMedia=null;AdapterJS.TEXT.EXTENSION={REQUIRE_INSTALLATION_FF:"To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.",REQUIRE_INSTALLATION_CHROME:"To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.",REQUIRE_REFRESH:"Please refresh this page after the Skylink WebRTC tools extension has been installed.",BUTTON_FF:"Install Now",BUTTON_CHROME:"Go to Chrome Web Store"};var clone=function(obj){if(null==obj||"object"!=typeof obj)return obj;var copy=obj.constructor();for(var attr in obj)obj.hasOwnProperty(attr)&&(copy[attr]=obj[attr]);return copy};if(window.navigator.mozGetUserMedia?(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){if("screen"!==constraints.video.mediaSource&&"window"!==constraints.video.mediaSource)return void failureCb(new Error('GetUserMedia: Only "screen" and "window" are supported as mediaSource constraints'));var updatedConstraints=clone(constraints);updatedConstraints.video.mozMediaSource=updatedConstraints.video.mediaSource;var checkIfReady=setInterval(function(){"complete"===document.readyState&&(clearInterval(checkIfReady),baseGetUserMedia(updatedConstraints,successCb,function(error){"PermissionDeniedError"===error.name&&"https:"===window.parent.location.protocol?AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF,AdapterJS.TEXT.EXTENSION.BUTTON_FF,"http://skylink.io/screensharing/ff_addon.php?domain="+window.location.hostname,!1,!0):failureCb(error)}))},1)}else baseGetUserMedia(constraints,successCb,failureCb)},getUserMedia=navigator.getUserMedia):window.navigator.webkitGetUserMedia?(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){if("chrome"!==window.webrtcDetectedBrowser)return void failureCb(new Error("Current browser does not support screensharing"));var updatedConstraints=clone(constraints),chromeCallback=function(error,sourceId){error?failureCb("permission-denied"===error?new Error("Permission denied for screen retrieval"):new Error("Failed retrieving selected screen")):(updatedConstraints.video.mandatory=updatedConstraints.video.mandatory||{},updatedConstraints.video.mandatory.chromeMediaSource="desktop",updatedConstraints.video.mandatory.maxWidth=window.screen.width>1920?window.screen.width:1920,updatedConstraints.video.mandatory.maxHeight=window.screen.height>1080?window.screen.height:1080,sourceId&&(updatedConstraints.video.mandatory.chromeMediaSourceId=sourceId),delete updatedConstraints.video.mediaSource,baseGetUserMedia(updatedConstraints,successCb,failureCb))},onIFrameCallback=function(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?chromeCallback("permission-denied"):chromeCallback(null,event.data.chromeMediaSourceId)),event.data.chromeExtensionStatus&&("not-installed"===event.data.chromeExtensionStatus?AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME,AdapterJS.TEXT.EXTENSION.BUTTON_CHROME,event.data.data,!0,!0):chromeCallback(event.data.chromeExtensionStatus,null)),window.removeEventListener("message",onIFrameCallback))};window.addEventListener("message",onIFrameCallback),postFrameMessage({captureSourceId:!0})}else baseGetUserMedia(constraints,successCb,failureCb)},getUserMedia=navigator.getUserMedia):navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)||(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){var updatedConstraints=clone(constraints);AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){return AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature&&AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable?(updatedConstraints.video.optional=updatedConstraints.video.optional||[],updatedConstraints.video.optional.push({sourceId:AdapterJS.WebRTCPlugin.plugin.screensharingKey||"Screensharing"}),delete updatedConstraints.video.mediaSource,void baseGetUserMedia(updatedConstraints,successCb,failureCb)):void failureCb(new Error("Your version of the WebRTC plugin does not support screensharing"))})}else baseGetUserMedia(constraints,successCb,failureCb)},getUserMedia=window.navigator.getUserMedia),"chrome"===window.webrtcDetectedBrowser){var iframe=document.createElement("iframe");iframe.onload=function(){iframe.isLoaded=!0},iframe.src="https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe);var postFrameMessage=function(object){return object=object||{},iframe.isLoaded?void iframe.contentWindow.postMessage(object,"*"):void setTimeout(function(){iframe.contentWindow.postMessage(object,"*")},100)}}else"opera"===window.webrtcDetectedBrowser}(); +/*! adapterjs - v0.13.1 - 2016-03-15 */ +function trace(text){if("\n"===text[text.length-1]&&(text=text.substring(0,text.length-1)),window.performance){var now=(window.performance.now()/1e3).toFixed(3);webrtcUtils.log(now+": "+text)}else webrtcUtils.log(text)}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}var AdapterJS=AdapterJS||{};if("undefined"!=typeof exports&&(module.exports=AdapterJS),AdapterJS.options=AdapterJS.options||{},AdapterJS.VERSION="0.13.1",AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){},AdapterJS._onwebrtcreadies=[],AdapterJS.webRTCReady=function(callback){if("function"!=typeof callback)throw new Error("Callback provided is not a function");!0===AdapterJS.onwebrtcreadyDone?callback(null!==AdapterJS.WebRTCPlugin.plugin):AdapterJS._onwebrtcreadies.push(callback)},AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{},AdapterJS.WebRTCPlugin.pluginInfo={prefix:"Tem",plugName:"TemWebRTCPlugin",pluginId:"plugin0",type:"application/x-temwebrtcplugin",onload:"__TemWebRTCReady0",portalLink:"http://skylink.io/plugin/",downloadLink:null,companyName:"Temasys"},navigator.platform.match(/^Mac/i)?AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1n77hco":navigator.platform.match(/^Win/i)&&(AdapterJS.WebRTCPlugin.pluginInfo.downloadLink="http://bit.ly/1kkS4FN"),AdapterJS.WebRTCPlugin.TAGS={NONE:"none",AUDIO:"audio",VIDEO:"video"},AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2),AdapterJS.WebRTCPlugin.plugin=null,AdapterJS.WebRTCPlugin.setLogLevel=null,AdapterJS.WebRTCPlugin.defineWebRTCInterface=null,AdapterJS.WebRTCPlugin.isPluginInstalled=null,AdapterJS.WebRTCPlugin.pluginInjectionInterval=null,AdapterJS.WebRTCPlugin.injectPlugin=null,AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4},AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE,AdapterJS.onwebrtcreadyDone=!1,AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:"NONE",ERROR:"ERROR",WARNING:"WARNING",INFO:"INFO",VERBOSE:"VERBOSE",SENSITIVE:"SENSITIVE"},AdapterJS.WebRTCPlugin.WaitForPluginReady=null,AdapterJS.WebRTCPlugin.callWhenPluginReady=null,__TemWebRTCReady0=function(){if("complete"===document.readyState)AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady();else var timer=setInterval(function(){"complete"===document.readyState&&(clearInterval(timer),AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,AdapterJS.maybeThroughWebRTCReady())},100)},AdapterJS.maybeThroughWebRTCReady=function(){AdapterJS.onwebrtcreadyDone||(AdapterJS.onwebrtcreadyDone=!0,AdapterJS._onwebrtcreadies.length?AdapterJS._onwebrtcreadies.forEach(function(callback){"function"==typeof callback&&callback(null!==AdapterJS.WebRTCPlugin.plugin)}):"function"==typeof AdapterJS.onwebrtcready&&AdapterJS.onwebrtcready(null!==AdapterJS.WebRTCPlugin.plugin))},AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:"This website requires you to install a WebRTC-enabling plugin to work on this browser.",NOT_SUPPORTED:"Your browser does not support WebRTC.",BUTTON:"Install Now"},REFRESH:{REQUIRE_REFRESH:"Please refresh page",BUTTON:"Refresh Page"}},AdapterJS._iceConnectionStates={starting:"starting",checking:"checking",connected:"connected",completed:"connected",done:"completed",disconnected:"disconnected",failed:"failed",closed:"closed"},AdapterJS._iceConnectionFiredStates=[],AdapterJS.isDefined=null,AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch=null;window.opr&&opr.addons||window.opera||navigator.userAgent.indexOf(" OPR/")>=0?(webrtcDetectedBrowser="opera",webrtcDetectedType="webkit",webrtcMinimumVersion=26,hasMatch=/OPR\/(\d+)/i.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1],10)):"undefined"!=typeof InstallTrigger?webrtcDetectedType="moz":Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0?(webrtcDetectedBrowser="safari",webrtcDetectedType="plugin",webrtcMinimumVersion=7,hasMatch=/version\/(\d+)/i.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1],10)):document.documentMode?(webrtcDetectedBrowser="IE",webrtcDetectedType="plugin",webrtcMinimumVersion=9,hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10),webrtcDetectedVersion||(hasMatch=/\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent)||[],webrtcDetectedVersion=parseInt(hasMatch[1]||"0",10))):window.StyleMedia?webrtcDetectedType="":window.chrome&&window.chrome.webstore?webrtcDetectedType="webkit":"chrome"!==webrtcDetectedBrowser&&"opera"!==webrtcDetectedBrowser||!window.CSS||(webrtcDetectedBrowser="blink"),window.webrtcDetectedBrowser=webrtcDetectedBrowser,window.webrtcDetectedVersion=webrtcDetectedVersion,window.webrtcMinimumVersion=webrtcMinimumVersion},AdapterJS.addEvent=function(elem,evnt,func){elem.addEventListener?elem.addEventListener(evnt,func,!1):elem.attachEvent?elem.attachEvent("on"+evnt,func):elem[evnt]=func},AdapterJS.renderNotificationBar=function(text,buttonText,buttonLink,openNewTab,displayRefreshBar){if("complete"===document.readyState){var w=window,i=document.createElement("iframe");i.name="adapterjs-alert",i.style.position="fixed",i.style.top="-41px",i.style.left=0,i.style.right=0,i.style.width="100%",i.style.height="40px",i.style.backgroundColor="#ffffe1",i.style.border="none",i.style.borderBottom="1px solid #888888",i.style.zIndex="9999999","string"==typeof i.style.webkitTransition?i.style.webkitTransition="all .5s ease-out":"string"==typeof i.style.transition&&(i.style.transition="all .5s ease-out"),document.body.appendChild(i);var c=i.contentWindow?i.contentWindow:i.contentDocument.document?i.contentDocument.document:i.contentDocument;c.document.open(),c.document.write(''+text+""),buttonText&&buttonLink?(c.document.write(''),c.document.close(),AdapterJS.addEvent(c.document.getElementById("okay"),"click",function(e){displayRefreshBar&&AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,"javascript:location.reload()"),window.open(buttonLink,openNewTab?"_blank":"_top"),e.preventDefault();try{e.cancelBubble=!0}catch(error){}var pluginInstallInterval=setInterval(function(){isIE||navigator.plugins.refresh(!1),AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval),AdapterJS.WebRTCPlugin.defineWebRTCInterface()},function(){})},500)}),AdapterJS.addEvent(c.document.getElementById("cancel"),"click",function(e){w.document.body.removeChild(i)})):c.document.close(),setTimeout(function(){"string"==typeof i.style.webkitTransform?i.style.webkitTransform="translateY(40px)":"string"==typeof i.style.transform?i.style.transform="translateY(40px)":i.style.top="0px"},300)}},webrtcDetectedType=null,checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if("function"==typeof callback){var beOfferer=!0,isLocalFirefox="firefox"===webrtcDetectedBrowser,isLocalFirefoxInterop="moz"===webrtcDetectedType&&webrtcDetectedVersion>30,isPeerFirefox="firefox"===peerBrowserAgent;if(isLocalFirefox&&isPeerFirefox||isLocalFirefoxInterop)try{delete constraints.mandatory.MozDontOfferDataChannel}catch(error){}else isLocalFirefox&&!isPeerFirefox&&(constraints.mandatory.MozDontOfferDataChannel=!0);if(!isLocalFirefox)for(var prop in constraints.mandatory)constraints.mandatory.hasOwnProperty(prop)&&-1!==prop.indexOf("Moz")&&delete constraints.mandatory[prop];!isLocalFirefox||isPeerFirefox||isLocalFirefoxInterop||(beOfferer=!1),callback(beOfferer,constraints)}},checkIceConnectionState=function(peerId,iceConnectionState,callback){"function"==typeof callback&&(peerId=peerId?peerId:"peer",AdapterJS._iceConnectionFiredStates[peerId]&&iceConnectionState!==AdapterJS._iceConnectionStates.disconnected&&iceConnectionState!==AdapterJS._iceConnectionStates.failed&&iceConnectionState!==AdapterJS._iceConnectionStates.closed||(AdapterJS._iceConnectionFiredStates[peerId]=[]),iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState],AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0&&(AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState),iceConnectionState===AdapterJS._iceConnectionStates.connected&&setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done),callback(AdapterJS._iceConnectionStates.done)},1e3),callback(iceConnectionState)))},createIceServer=null,createIceServers=null,RTCPeerConnection=null,RTCSessionDescription="function"==typeof RTCSessionDescription?RTCSessionDescription:null,RTCIceCandidate="function"==typeof RTCIceCandidate?RTCIceCandidate:null,getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,navigator.mozGetUserMedia||navigator.webkitGetUserMedia||navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){var getUserMedia=null,attachMediaStream=null,reattachMediaStream=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils={log:function(){"undefined"!=typeof module||"function"==typeof require&&"function"==typeof define},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10)}};if("object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=URL.createObjectURL(stream))}}),getUserMedia=window.navigator&&window.navigator.getUserMedia),attachMediaStream=function(element,stream){element.srcObject=stream},reattachMediaStream=function(to,from){to.srcObject=from.srcObject},"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia){if(webrtcUtils.log("This appears to be Firefox"),webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,window.RTCPeerConnection||(window.RTCPeerConnection=function(pcConfig,pcConstraints){if(38>webrtcDetectedVersion&&pcConfig&&pcConfig.iceServers){for(var newIceServers=[],i=0;iwebrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,addEventListener:function(){},removeEventListener:function(){}}),navigator.mediaDevices.enumerateDevices=navigator.mediaDevices.enumerateDevices||function(){return new Promise(function(resolve){var infos=[{kind:"audioinput",deviceId:"default",label:"",groupId:""},{kind:"videoinput",deviceId:"default",label:"",groupId:""}];resolve(infos)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcUtils.log("This appears to be Chrome"),webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38,window.RTCPeerConnection=function(pcConfig,pcConstraints){pcConfig&&pcConfig.iceTransportPolicy&&(pcConfig.iceTransports=pcConfig.iceTransportPolicy);var pc=new webkitRTCPeerConnection(pcConfig,pcConstraints),origGetStats=pc.getStats.bind(pc);return pc.getStats=function(selector,successCallback,errorCallback){var self=this,args=arguments;if(arguments.length>0&&"function"==typeof selector)return origGetStats(selector,successCallback);var fixChromeStats=function(response){var standardReport={},reports=response.result();return reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name)}),standardReport[standardStats.id]=standardStats}),standardReport};if(arguments.length>=2){var successCallbackWrapper=function(response){args[1](fixChromeStats(response))};return origGetStats.apply(this,[successCallbackWrapper,arguments[0]])}return new Promise(function(resolve,reject){1===args.length&&null===selector?origGetStats.apply(self,[function(response){resolve.apply(null,[fixChromeStats(response)])},reject]):origGetStats.apply(self,[resolve,reject])})},pc},webkitRTCPeerConnection.generateCertificate&&Object.defineProperty(window.RTCPeerConnection,"generateCertificate",{get:function(){return arguments.length?webkitRTCPeerConnection.generateCertificate.apply(null,arguments):webkitRTCPeerConnection.generateCertificate}}),["createOffer","createAnswer"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||1===arguments.length&&"object"==typeof arguments[0]){var opts=1===arguments.length?arguments[0]:void 0;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts])})}return nativeMethod.apply(this,arguments)}}),["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments,self=this;return new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],function(){resolve(),args.length>=2&&args[1].apply(null,[])},function(err){reject(err),args.length>=3&&args[2].apply(null,[err])}])})}});var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:"audioinput",video:"videoinput"};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:""}}))})})}}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")}),attachMediaStream=function(element,stream){webrtcDetectedVersion>=43?element.srcObject=stream:"undefined"!=typeof element.src?element.src=URL.createObjectURL(stream):webrtcUtils.log("Error attaching stream to element.")},reattachMediaStream=function(to,from){webrtcDetectedVersion>=43?to.srcObject=from.srcObject:to.src=from.src}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){if(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=10547,window.RTCIceGatherer){var generateIdentifier=function(){return Math.random().toString(36).substr(2,10)},localCName=generateIdentifier(),SDPUtils={};SDPUtils.splitLines=function(blob){return blob.trim().split("\n").map(function(line){return line.trim()})},SDPUtils.splitSections=function(blob){var parts=blob.split("\r\nm=");return parts.map(function(part,index){return(index>0?"m="+part:part).trim()+"\r\n"})},SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return 0===line.indexOf(prefix)})},SDPUtils.parseCandidate=function(line){var parts;parts=0===line.indexOf("a=candidate:")?line.substring(12).split(" "):line.substring(10).split(" ");for(var candidate={foundation:parts[0],component:parts[1],protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]},i=8;i-1?(parts.attribute=line.substr(sp+1,colon-sp-1),parts.value=line.substr(colon+1)):parts.attribute=line.substr(sp+1),parts},SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var fpLine=lines.filter(function(line){return 0===line.indexOf("a=fingerprint:")})[0].substr(14),dtlsParameters={role:"auto",fingerprints:[{algorithm:fpLine.split(" ")[0],value:fpLine.split(" ")[1]}]};return dtlsParameters},SDPUtils.writeDtlsParameters=function(params,setupType){var sdp="a=setup:"+setupType+"\r\n";return params.fingerprints.forEach(function(fp){sdp+="a=fingerprint:"+fp.algorithm+" "+fp.value+"\r\n"}),sdp},SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return 0===line.indexOf("a=ice-ufrag:")})[0].substr(12),password:lines.filter(function(line){return 0===line.indexOf("a=ice-pwd:")})[0].substr(10)};return iceParameters},SDPUtils.writeIceParameters=function(params){return"a=ice-ufrag:"+params.usernameFragment+"\r\na=ice-pwd:"+params.password+"\r\n"},SDPUtils.parseRtpParameters=function(mediaSection){for(var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]},lines=SDPUtils.splitLines(mediaSection),mline=lines[0].split(" "),i=3;i0?"9":"0",sdp+=" UDP/TLS/RTP/SAVPF ",sdp+=caps.codecs.map(function(codec){return void 0!==codec.preferredPayloadType?codec.preferredPayloadType:codec.payloadType}).join(" ")+"\r\n",sdp+="c=IN IP4 0.0.0.0\r\n",sdp+="a=rtcp:9 IN IP4 0.0.0.0\r\n",caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec),sdp+=SDPUtils.writeFtmp(codec),sdp+=SDPUtils.writeRtcpFb(codec)}),sdp+="a=rtcp-mux\r\n"},SDPUtils.writeSessionBoilerplate=function(){return"v=0\r\no=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"},SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);if(sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters()),sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),"offer"===type?"actpass":"active"),sdp+="a=mid:"+transceiver.mid+"\r\n",sdp+=transceiver.rtpSender&&transceiver.rtpReceiver?"a=sendrecv\r\n":transceiver.rtpSender?"a=sendonly\r\n":transceiver.rtpReceiver?"a=recvonly\r\n":"a=inactive\r\n",transceiver.rtpSender){var msid="msid:"+stream.id+" "+transceiver.rtpSender.track.id+"\r\n";sdp+="a="+msid,sdp+="a=ssrc:"+transceiver.sendSsrc+" "+msid}return sdp+="a=ssrc:"+transceiver.sendSsrc+" cname:"+localCName+"\r\n"},SDPUtils.getDirection=function(mediaSection,sessionpart){for(var lines=SDPUtils.splitLines(mediaSection),i=0;i-1&&(this.localStreams.splice(idx,1),this._maybeFireNegotiationNeeded())},window.RTCPeerConnection.prototype._getCommonCapabilities=function(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};return localCapabilities.codecs.forEach(function(lCodec){for(var i=0;i0,!1)}})}switch(this.localDescription=description, +description.type){case"offer":this._updateSignalingState("have-local-offer");break;case"answer":this._updateSignalingState("stable");break;default:throw new TypeError('unsupported type "'+description.type+'"')}var hasCallback=arguments.length>1&&"function"==typeof arguments[1];if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb(),self._emitBufferedCandidates()},0)}var p=Promise.resolve();return p.then(function(){hasCallback||window.setTimeout(self._emitBufferedCandidates.bind(self),0)}),p},window.RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this,stream=new MediaStream,sections=SDPUtils.splitSections(description.sdp),sessionpart=sections.shift();switch(sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver,iceGatherer,iceTransport,dtlsTransport,rtpSender,rtpReceiver,sendSsrc,recvSsrc,localCapabilities,remoteIceParameters,remoteDtlsParameters,lines=SDPUtils.splitLines(mediaSection),mline=lines[0].substr(2).split(" "),kind=mline[0],rejected="0"===mline[1],direction=SDPUtils.getDirection(mediaSection,sessionpart),remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);rejected||(remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart),remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart));var cname,mid=SDPUtils.matchPrefix(mediaSection,"a=mid:")[0].substr(6),remoteSsrc=SDPUtils.matchPrefix(mediaSection,"a=ssrc:").map(function(line){return SDPUtils.parseSsrcMedia(line)}).filter(function(obj){return"cname"===obj.attribute})[0];if(remoteSsrc&&(recvSsrc=parseInt(remoteSsrc.ssrc,10),cname=remoteSsrc.value),"offer"===description.type){var transports=self._createIceAndDtlsTransports(mid,sdpMLineIndex);if(localCapabilities=RTCRtpReceiver.getCapabilities(kind),sendSsrc=1001*(2*sdpMLineIndex+2),rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind),stream.addTrack(rtpReceiver.track),self.localStreams.length>0&&self.localStreams[0].getTracks().length>=sdpMLineIndex){var localtrack=self.localStreams[0].getTracks()[sdpMLineIndex];rtpSender=new RTCRtpSender(localtrack,transports.dtlsTransport)}self.transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:remoteCapabilities,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,cname:cname,sendSsrc:sendSsrc,recvSsrc:recvSsrc},self._transceive(self.transceivers[sdpMLineIndex],!1,"sendrecv"===direction||"sendonly"===direction)}else"answer"!==description.type||rejected||(transceiver=self.transceivers[sdpMLineIndex],iceGatherer=transceiver.iceGatherer,iceTransport=transceiver.iceTransport,dtlsTransport=transceiver.dtlsTransport,rtpSender=transceiver.rtpSender,rtpReceiver=transceiver.rtpReceiver,sendSsrc=transceiver.sendSsrc,localCapabilities=transceiver.localCapabilities,self.transceivers[sdpMLineIndex].recvSsrc=recvSsrc,self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities,self.transceivers[sdpMLineIndex].cname=cname,iceTransport.start(iceGatherer,remoteIceParameters,"controlling"),dtlsTransport.start(remoteDtlsParameters),self._transceive(transceiver,"sendrecv"===direction||"recvonly"===direction,"sendrecv"===direction||"sendonly"===direction),!rtpReceiver||"sendrecv"!==direction&&"sendonly"!==direction?delete transceiver.rtpReceiver:stream.addTrack(rtpReceiver.track))}),this.remoteDescription=description,description.type){case"offer":this._updateSignalingState("have-remote-offer");break;case"answer":this._updateSignalingState("stable");break;default:throw new TypeError('unsupported type "'+description.type+'"')}return window.setTimeout(function(){null!==self.onaddstream&&stream.getTracks().length&&(self.remoteStreams.push(stream),window.setTimeout(function(){self.onaddstream({stream:stream})},0))},0),arguments.length>1&&"function"==typeof arguments[1]&&window.setTimeout(arguments[1],0),Promise.resolve()},window.RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){transceiver.iceTransport&&transceiver.iceTransport.stop(),transceiver.dtlsTransport&&transceiver.dtlsTransport.stop(),transceiver.rtpSender&&transceiver.rtpSender.stop(),transceiver.rtpReceiver&&transceiver.rtpReceiver.stop()}),this._updateSignalingState("closed")},window.RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState,null!==this.onsignalingstatechange&&this.onsignalingstatechange()},window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){null!==this.onnegotiationneeded&&this.onnegotiationneeded()},window.RTCPeerConnection.prototype._updateConnectionState=function(){var newState,self=this,states={"new":0,closed:0,connecting:0,checking:0,connected:0,completed:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++,states[transceiver.dtlsTransport.state]++}),states.connected+=states.completed,newState="new",states.failed>0?newState="failed":states.connecting>0||states.checking>0?newState="connecting":states.disconnected>0?newState="disconnected":states["new"]>0?newState="new":(states.connecting>0||states.completed>0)&&(newState="connected"),newState!==self.iceConnectionState&&(self.iceConnectionState=newState,null!==this.oniceconnectionstatechange&&this.oniceconnectionstatechange())},window.RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer)throw new Error("createOffer called while there is a pending offer.");var offerOptions;1===arguments.length&&"function"!=typeof arguments[0]?offerOptions=arguments[0]:3===arguments.length&&(offerOptions=arguments[2]);var tracks=[],numAudioTracks=0,numVideoTracks=0;if(this.localStreams.length&&(numAudioTracks=this.localStreams[0].getAudioTracks().length,numVideoTracks=this.localStreams[0].getVideoTracks().length),offerOptions){if(offerOptions.mandatory||offerOptions.optional)throw new TypeError("Legacy mandatory/optional constraints not supported.");void 0!==offerOptions.offerToReceiveAudio&&(numAudioTracks=offerOptions.offerToReceiveAudio),void 0!==offerOptions.offerToReceiveVideo&&(numVideoTracks=offerOptions.offerToReceiveVideo)}for(this.localStreams.length&&this.localStreams[0].getTracks().forEach(function(track){tracks.push({kind:track.kind,track:track,wantReceive:"audio"===track.kind?numAudioTracks>0:numVideoTracks>0}),"audio"===track.kind?numAudioTracks--:"video"===track.kind&&numVideoTracks--});numAudioTracks>0||numVideoTracks>0;)numAudioTracks>0&&(tracks.push({kind:"audio",wantReceive:!0}),numAudioTracks--),numVideoTracks>0&&(tracks.push({kind:"video",wantReceive:!0}),numVideoTracks--);var sdp=SDPUtils.writeSessionBoilerplate(),transceivers=[];tracks.forEach(function(mline,sdpMLineIndex){var rtpSender,rtpReceiver,track=mline.track,kind=mline.kind,mid=generateIdentifier(),transports=self._createIceAndDtlsTransports(mid,sdpMLineIndex),localCapabilities=RTCRtpSender.getCapabilities(kind),sendSsrc=1001*(2*sdpMLineIndex+1);track&&(rtpSender=new RTCRtpSender(track,transports.dtlsTransport)),mline.wantReceive&&(rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind)),transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:null,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,sendSsrc:sendSsrc,recvSsrc:null};var transceiver=transceivers[sdpMLineIndex];sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,"offer",self.localStreams[0])}),this._pendingOffer=transceivers;var desc=new RTCSessionDescription({type:"offer",sdp:sdp});return arguments.length&&"function"==typeof arguments[0]&&window.setTimeout(arguments[0],0,desc),Promise.resolve(desc)},window.RTCPeerConnection.prototype.createAnswer=function(){var answerOptions,self=this;1===arguments.length&&"function"!=typeof arguments[0]?answerOptions=arguments[0]:3===arguments.length&&(answerOptions=arguments[2]);var sdp=SDPUtils.writeSessionBoilerplate();this.transceivers.forEach(function(transceiver){var commonCapabilities=self._getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,"answer",self.localStreams[0])});var desc=new RTCSessionDescription({type:"answer",sdp:sdp});return arguments.length&&"function"==typeof arguments[0]&&window.setTimeout(arguments[0],0,desc),Promise.resolve(desc)},window.RTCPeerConnection.prototype.addIceCandidate=function(candidate){var mLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid)for(var i=0;i0?SDPUtils.parseCandidate(candidate.candidate):{};if("tcp"===cand.protocol&&0===cand.port)return;if("1"!==cand.component)return;"endOfCandidates"===cand.type&&(cand={}),transceiver.iceTransport.addRemoteCandidate(cand)}return arguments.length>1&&"function"==typeof arguments[1]&&window.setTimeout(arguments[1],0),Promise.resolve()},window.RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){["rtpSender","rtpReceiver","iceGatherer","iceTransport","dtlsTransport"].forEach(function(method){transceiver[method]&&promises.push(transceiver[method].getStats())})});var cb=arguments.length>1&&"function"==typeof arguments[1]&&arguments[1];return new Promise(function(resolve){var results={};Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){results[id]=result[id]})}),cb&&window.setTimeout(cb,0,results),resolve(results)})})}}}else webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcUtils.log("This does not appear to be a browser"),webrtcDetectedBrowser="not a browser";var webrtcTesting={};try{Object.defineProperty(webrtcTesting,"version",{set:function(version){webrtcDetectedVersion=version}})}catch(e){}AdapterJS.parseWebrtcDetectedBrowser(),navigator.mozGetUserMedia?(MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:"audio",id:"default",label:"",facing:""},{kind:"video",id:"default",label:"",facing:""}];successCb(infos)},0)},createIceServer=function(url,username,password){var iceServer=null,urlParts=url.split(":");if(0===urlParts[0].indexOf("stun"))iceServer={urls:[url]};else if(0===urlParts[0].indexOf("turn"))if(27>webrtcDetectedVersion){var turnUrlParts=url.split("?");(1===turnUrlParts.length||0===turnUrlParts[1].indexOf("transport=udp"))&&(iceServer={urls:[turnUrlParts[0]],credential:password,username:username})}else iceServer={urls:[url],credential:password,username:username};return iceServer},createIceServers=function(urls,username,password){var iceServers=[];for(i=0;i=34)iceServers={urls:urls,credential:password,username:username};else for(i=0;i=webrtcDetectedVersion){var frag=document.createDocumentFragment();for(AdapterJS.WebRTCPlugin.plugin=document.createElement("div"),AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+"";AdapterJS.WebRTCPlugin.plugin.firstChild;)frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);document.body.appendChild(frag),AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId)}else AdapterJS.WebRTCPlugin.plugin=document.createElement("object"),AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId,isIE?(AdapterJS.WebRTCPlugin.plugin.width="1px",AdapterJS.WebRTCPlugin.plugin.height="1px"):(AdapterJS.WebRTCPlugin.plugin.width="0px",AdapterJS.WebRTCPlugin.plugin.height="0px"),AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type,AdapterJS.WebRTCPlugin.plugin.innerHTML=' '+(AdapterJS.options.getAllCams?'':"")+'',document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED}},AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(isIE){try{new ActiveXObject(comName+"."+plugName)}catch(e){return void notInstalledCb()}installedCb()}else{for(var pluginArray=navigator.plugins,i=0;i=0)return void installedCb();notInstalledCb()}},AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){AdapterJS.WebRTCPlugin.pluginState!==AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY&&(AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING,AdapterJS.isDefined=function(variable){return null!==variable&&void 0!==variable},createIceServer=function(url,username,password){var iceServer=null,urlParts=url.split(":");return 0===urlParts[0].indexOf("stun")?iceServer={url:url,hasCredentials:!1}:0===urlParts[0].indexOf("turn")&&(iceServer={url:url,hasCredentials:!0,credential:password,username:username}),iceServer},createIceServers=function(urls,username,password){for(var iceServers=[],i=0;i1)return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers);var iceServers=null;if(servers&&Array.isArray(servers.iceServers)){iceServers=servers.iceServers;for(var i=0;i ';temp.firstChild;)frag.appendChild(temp.firstChild);var height="",width="";element.clientWidth||element.clientHeight?(width=element.clientWidth,height=element.clientHeight):(element.width||element.height)&&(width=element.width,height=element.height),element.parentNode.insertBefore(frag,element),frag=document.getElementById(elementId),frag.width=width,frag.height=height,element.parentNode.removeChild(element)}else{for(var children=element.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){children[i].value=streamId;break}element.setStreamId(streamId)}var newElement=document.getElementById(elementId);return AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element)),newElement}},reattachMediaStream=function(to,from){for(var stream=null,children=from.children,i=0;i!==children.length;++i)if("streamId"===children[i].name){AdapterJS.WebRTCPlugin.WaitForPluginReady(),stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break}return null!==stream?attachMediaStream(to,stream):void 0},window.attachMediaStream=attachMediaStream,window.reattachMediaStream=reattachMediaStream,window.getUserMedia=getUserMedia,AdapterJS.attachMediaStream=attachMediaStream,AdapterJS.reattachMediaStream=reattachMediaStream,AdapterJS.getUserMedia=getUserMedia,AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(var prop in properties)prop&&(propName=properties[prop],"function"==typeof propName.slice&&"on"===propName.slice(0,2)&&"function"==typeof srcElem[propName]&&AdapterJS.addEvent(destElem,propName.slice(2),srcElem[propName]));var subPrototype=Object.getPrototypeOf(prototype);subPrototype&&AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype)},RTCIceCandidate=function(candidate){return candidate.sdpMid||(candidate.sdpMid=""),AdapterJS.WebRTCPlugin.WaitForPluginReady(),AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate)},AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.injectPlugin),AdapterJS.WebRTCPlugin.injectPlugin())},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,"readystatechange",AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv),AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv()},AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(!AdapterJS.options.hidePluginInstallPrompt){var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;popupString=AdapterJS.WebRTCPlugin.pluginInfo.portalLink?'This website requires you to install the '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+" WebRTC Plugin to work on this browser.":AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION,AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink)}else AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED)}},AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);!function(){"use strict";var baseGetUserMedia=null;AdapterJS.TEXT.EXTENSION={REQUIRE_INSTALLATION_FF:"To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.",REQUIRE_INSTALLATION_CHROME:"To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.",REQUIRE_REFRESH:"Please refresh this page after the Skylink WebRTC tools extension has been installed.",BUTTON_FF:"Install Now",BUTTON_CHROME:"Go to Chrome Web Store"};var clone=function(obj){if(null===obj||"object"!=typeof obj)return obj;var copy=obj.constructor();for(var attr in obj)obj.hasOwnProperty(attr)&&(copy[attr]=obj[attr]);return copy};if(window.navigator.mozGetUserMedia?(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){if("screen"!==constraints.video.mediaSource&&"window"!==constraints.video.mediaSource)return void failureCb(new Error('GetUserMedia: Only "screen" and "window" are supported as mediaSource constraints'));var updatedConstraints=clone(constraints);updatedConstraints.video.mozMediaSource=updatedConstraints.video.mediaSource;var checkIfReady=setInterval(function(){"complete"===document.readyState&&(clearInterval(checkIfReady),baseGetUserMedia(updatedConstraints,successCb,function(error){["PermissionDeniedError","SecurityError"].indexOf(error.name)>-1&&"https:"===window.parent.location.protocol?AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF,AdapterJS.TEXT.EXTENSION.BUTTON_FF,"https://addons.mozilla.org/en-US/firefox/addon/skylink-webrtc-tools/",!0,!0):failureCb(error)}))},1)}else baseGetUserMedia(constraints,successCb,failureCb)},AdapterJS.getUserMedia=window.getUserMedia=navigator.getUserMedia,navigator.mediaDevices.getUserMedia=function(constraints){return new Promise(function(resolve,reject){window.getUserMedia(constraints,resolve,reject)})}):window.navigator.webkitGetUserMedia?(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){if("chrome"!==window.webrtcDetectedBrowser)return void failureCb(new Error("Current browser does not support screensharing"));var updatedConstraints=clone(constraints),chromeCallback=function(error,sourceId){error?failureCb("permission-denied"===error?new Error("Permission denied for screen retrieval"):new Error("Failed retrieving selected screen")):(updatedConstraints.video.mandatory=updatedConstraints.video.mandatory||{},updatedConstraints.video.mandatory.chromeMediaSource="desktop",updatedConstraints.video.mandatory.maxWidth=window.screen.width>1920?window.screen.width:1920,updatedConstraints.video.mandatory.maxHeight=window.screen.height>1080?window.screen.height:1080,sourceId&&(updatedConstraints.video.mandatory.chromeMediaSourceId=sourceId),delete updatedConstraints.video.mediaSource,baseGetUserMedia(updatedConstraints,successCb,failureCb))},onIFrameCallback=function(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?chromeCallback("permission-denied"):chromeCallback(null,event.data.chromeMediaSourceId)),event.data.chromeExtensionStatus&&("not-installed"===event.data.chromeExtensionStatus?AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME,AdapterJS.TEXT.EXTENSION.BUTTON_CHROME,event.data.data,!0,!0):chromeCallback(event.data.chromeExtensionStatus,null)),window.removeEventListener("message",onIFrameCallback))};window.addEventListener("message",onIFrameCallback),postFrameMessage({captureSourceId:!0})}else baseGetUserMedia(constraints,successCb,failureCb)},AdapterJS.getUserMedia=window.getUserMedia=navigator.getUserMedia,navigator.mediaDevices.getUserMedia=function(constraints){return new Promise(function(resolve,reject){window.getUserMedia(constraints,resolve,reject)})}):navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)||(baseGetUserMedia=window.navigator.getUserMedia,navigator.getUserMedia=function(constraints,successCb,failureCb){if(constraints&&constraints.video&&constraints.video.mediaSource){var updatedConstraints=clone(constraints);AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){return AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature&&AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable?(updatedConstraints.video.optional=updatedConstraints.video.optional||[],updatedConstraints.video.optional.push({sourceId:AdapterJS.WebRTCPlugin.plugin.screensharingKey||"Screensharing"}),delete updatedConstraints.video.mediaSource,void baseGetUserMedia(updatedConstraints,successCb,failureCb)):void failureCb(new Error("Your version of the WebRTC plugin does not support screensharing"))})}else baseGetUserMedia(constraints,successCb,failureCb)},AdapterJS.getUserMedia=getUserMedia=window.getUserMedia=navigator.getUserMedia,navigator.mediaDevices.getUserMedia=requestUserMedia),"chrome"===window.webrtcDetectedBrowser){var iframe=document.createElement("iframe");iframe.onload=function(){iframe.isLoaded=!0},iframe.src="https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe);var postFrameMessage=function(object){return object=object||{},iframe.isLoaded?void iframe.contentWindow.postMessage(object,"*"):void setTimeout(function(){iframe.contentWindow.postMessage(object,"*")},100)}}else"opera"===window.webrtcDetectedBrowser}(); \ No newline at end of file diff --git a/source/adapter.MediaStream.js b/source/adapter.MediaStream.js deleted file mode 100644 index 56fb138..0000000 --- a/source/adapter.MediaStream.js +++ /dev/null @@ -1,696 +0,0 @@ -// Polyfill all MediaStream objects -var polyfillMediaStream = null; - -// Firefox MediaStream -if (navigator.mozGetUserMedia) { - - /** - * The polyfilled MediaStream class. - * @class MediaStream - * @since 0.10.5 - */ - polyfillMediaStream = function (stream) { - - /** - * The MediaStream object id. - * @attribute id - * @type String - * @readOnly - * @for MediaStream - * @since 0.10.6 - */ - try { - stream.id = stream.id || (new Date()).getTime().toString(); - } catch (error) { - console.warn('Unable to polyfill MediaStream.id'); - } - - /** - * The flag that indicates if a MediaStream object has ended. - * @attribute ended - * @type Boolean - * @readOnly - * @for MediaStream - * @since 0.10.6 - */ - stream.ended = typeof stream.ended === 'boolean' ? stream.ended : false; - - /** - * Event triggered when MediaStream has ended streaming. - * @event onended - * @param {String} type The type of event: "ended". - * @for MediaStream - * @since 0.10.6 - */ - stream.onended = null; - - /** - * Event triggered when MediaStream has added a new track. - * @event onaddtrack - * @param {String} type The type of event: "addtrack". - * @for MediaStream - * @since 0.10.6 - */ - stream.onaddtrack = null; - - /** - * Event triggered when MediaStream has removed an existing track. - * @event onremovetrack - * @param {String} type The type of event: "removetrack". - * @for MediaStream - * @since 0.10.6 - */ - stream.onremovetrack = null; - - - var polyEndedEmitter = function () { - // set the ended as true - stream.ended = true; - - // trigger that it has ended - if (typeof stream.onended === 'function') { - stream.onended({ - type: 'ended', - bubbles: false, - cancelBubble: false, - cancelable: false, - currentTarget: stream, - defaultPrevented: false, - eventPhase: 0, - returnValue: true, - srcElement: stream, - target: stream, - timeStamp: stream.currentTime || (new Date()).getTime() - }); - } - }; - - var polyTrackEndedEmitter = function (track) { - // set the ended as true - track.ended = true; - - // trigger that it has ended - if (typeof track.onended === 'function') { - track.onended({ - type: 'ended', - bubbles: false, - cancelBubble: false, - cancelable: false, - currentTarget: track, - defaultPrevented: false, - eventPhase: 0, - returnValue: true, - srcElement: track, - target: track, - timeStamp: stream.currentTime || (new Date()).getTime() - }); - } - }; - - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - polyfillMediaStreamTrack( audioTracks[i] ); - } - - for (j = 0; j < videoTracks.length; j += 1) { - polyfillMediaStreamTrack( videoTracks[j] ); - } - })(); - - /** - * Stops a MediaStream streaming. - * @method polystop - * @for MediaStream - * @since 0.10.6 - */ - stream.polystop = function () { - if (stream instanceof LocalMediaStream) { - stream.stop(); - - var i, j; - - var outputAudioTracks = stream.polygetAudioTracks(); - var outputVideoTracks = stream.polygetVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - outputAudioTracks[i].ended = true; - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - outputVideoTracks[j].ended = true; - } - - } else { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - for (i = 0; i < audioTracks.length; i += 1) { - audioTracks[i].polystop(); - } - - for (j = 0; j < videoTracks.length; j += 1) { - videoTracks[j].polystop(); - } - } - }; - - /** - * Adds a MediaStreamTrack to an object. - * @method polyaddTrack - * @for MediaStream - * @since 0.10.6 - */ - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - throw error; - } - }; - - /** - * Gets a MediaStreamTrack from a MediaStreamTrack based on the object id provided. - * @method polygetTrackById - * @param {String} trackId The MediaStreamTrack object id. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetTrackById = function (trackId) { - try { - return stream.getTrackById(trackId); - - } catch (error) { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - if (audioTracks[i].id === trackId) { - return audioTracks[i]; - } - } - - for (j = 0; j < videoTracks.length; j += 1) { - if (videoTracks[i].id === trackId) { - return videoTracks[i]; - } - } - - return null; - } - }; - - /** - * Gets all MediaStreamTracks from a MediaStreamTrack. - * @method polygetTracks - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetTracks = function (trackId) { - try { - return stream.getTracks(); - - } catch (error) { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - return audioTracks.concat(videoTracks); - } - }; - - /** - * Removes a MediaStreamTrack from an object. - * @method polyremoveTrack - * @for MediaStream - * @since 0.10.6 - */ - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - throw error; - } - }; - - /** - * Gets the list of audio MediaStreamTracks of a MediaStream. - * @method polygetAudioTracks - * @return {Array} Returns a list of the audio MediaStreamTracks - * available for the MediaStream. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetAudioTracks = stream.getAudioTracks; - - /** - * Gets the list of video MediaStreamTracks of a MediaStream. - * @method polygetVideoTracks - * @return {Array} Returns a list of the video MediaStreamTracks - * available for the MediaStream. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetVideoTracks = stream.getVideoTracks; - - /** - * Listens and waits to check if all MediaStreamTracks of a MediaStream - * has ended. Once ended, this invokes the ended flag of the MediaStream. - * This loops every second. - * @method _polyOnTracksEndedListener - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - stream._polyOnTracksEndedListener = setInterval(function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - var audioEnded = true; - var videoEnded = true; - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - if (audioTracks[i].ended !== true) { - audioEnded = false; - break; - } - } - - for (j = 0; j < videoTracks.length; j += 1) { - if (videoTracks[j].ended !== true) { - videoEnded = false; - break; - } - } - - if (audioEnded && videoEnded) { - clearInterval(stream._polyOnTracksEndedListener); - stream.ended = true; - } - }, 1000); - - /** - * Listens and waits to check if all MediaStream has ended. - * This loops every second. - * @method _polyOnEndedListener - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - if (stream instanceof LocalMediaStream) { - stream._polyOnEndedListener = setInterval(function () { - // If stream has flag ended because of media tracks being stopped - if (stream.ended) { - clearInterval(stream._polyOnEndedListener); - - polyEndedEmitter(); - - return; - } - - if (typeof stream.recordedTime === 'undefined') { - stream.recordedTime = 0; - } - - if (stream.recordedTime === stream.currentTime) { - clearInterval(stream._polyOnEndedListener); - - polyEndedEmitter(); - - return; - - } else { - stream.recordedTime = stream.currentTime; - } - }, 1000); - - } else { - /** - * Stores the attached video element with the existing MediaStream - * This loops every second. - * - This only exists in Firefox browsers. - * @attribute _polyOnEndedListenerObj - * @type DOM - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - // Use a video to attach to check if stream has ended - var video = document.createElement('video'); - - video._polyOnEndedListener = setInterval(function () { - // If stream has flag ended because of media tracks being stopped - if (stream.ended) { - clearInterval(video._polyOnEndedListener); - - polyEndedEmitter(); - - return; - } - - // Check if mozSrcObject is not empty - if (typeof video.mozSrcObject === 'object' && - video.mozSrcObject !== null) { - - if (video.mozSrcObject.ended === true) { - clearInterval(video._polyOnEndedListener); - - polyEndedEmitter(); - - return; - } - } - }, 1000); - - // Bind the video element to MediaStream object - stream._polyOnEndedListenerObj = video; - - window.attachMediaStream(video, stream); - } - }; - - window.navigator.getUserMedia = function (constraints, successCb, failureCb) { - - window.navigator.mozGetUserMedia(constraints, function (stream) { - polyfillMediaStream(stream); - - successCb(stream); - - }, failureCb); - }; - - window.getUserMedia = window.navigator.getUserMedia; - - window.attachMediaStream = function (element, stream) { - // If there's an element used for checking stream stop - // for an instance remote MediaStream for firefox - // reattachmediastream instead - if (typeof stream._polyOnEndedListenerObj !== 'undefined' && - stream instanceof LocalMediaStream === false) { - window.reattachMediaStream(element, bind._polyOnEndedListenerObj); - - // LocalMediaStream - } else { - console.log('Attaching media stream'); - element.mozSrcObject = stream; - } - }; - -// Chrome / Opera MediaStream -} else if (navigator.webkitGetUserMedia) { - - polyfillMediaStream = function (stream) { - - stream.id = stream.id || (new Date()).getTime().toString(); - - stream.ended = typeof stream.ended === 'boolean' ? stream.ended : false; - - stream.onended = null; - - stream.onaddtrack = null; - - stream.onremovetrack = null; - - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - polyfillMediaStreamTrack( audioTracks[i] ); - } - - for (j = 0; j < videoTracks.length; j += 1) { - polyfillMediaStreamTrack( videoTracks[j] ); - } - })(); - - stream.polystop = function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - try { - stream.stop(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - if (audioTracks[i].readyState !== 'ended') { - audioTracks[i].polystop(); - } - } - - for (j = 0; j < videoTracks.length; j += 1) { - if (videoTracks[j].readyState !== 'ended') { - videoTracks[j].polystop(); - } - } - - } catch (error) { - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - audioTracks[i].polystop(); - } - - for (j = 0; j < videoTracks.length; j += 1) { - videoTracks[j].polystop(); - } - } - }; - - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - throw error; - } - }; - - stream.polygetTrackById = function (trackId) { - try { - return stream.getTrackById(trackId); - - } catch (err) { - - console.log(err); - - var i, j; - - var outputAudioTracks = polyStoreMediaTracks.audio; - var outputVideoTracks = polyStoreMediaTracks.video; - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - if (outputAudioTracks[i].id === trackId) { - return outputAudioTracks[i]; - } - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - if (outputVideoTracks[j].id === trackId) { - return outputVideoTracks[j]; - } - } - - return null; - } - }; - - stream.polygetTracks = function (trackId) { - try { - return stream.getTracks(); - - } catch (error) { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - return audioTracks.concat(videoTracks); - } - }; - - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - throw error; - } - }; - - stream.polygetAudioTracks = stream.getAudioTracks; - - stream.polygetVideoTracks = stream.getVideoTracks; - }; - - window.navigator.getUserMedia = function (constraints, successCb, failureCb) { - navigator.webkitGetUserMedia(constraints, function (stream) { - - polyfillMediaStream(stream); - - successCb(stream); - }, failureCb); - - }; - - window.getUserMedia = window.navigator.getUserMedia; - -// Safari MediaStream -} else { - - polyfillMediaStream = function (stream) { - - stream.id = stream.id || (new Date()).getTime().toString(); - - stream.ended = typeof stream.ended === 'boolean' ? stream.ended : false; - - stream.onended = null; - - stream.onaddtrack = null; - - stream.onremovetrack = null; - - // MediaStreamTracks Polyfilled - var polyStoreMediaTracks = { - audio: [], - video: [] - }; - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - var outputAudioTracks = []; - var outputVideoTracks = []; - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - var audioTrack = polyfillMediaStreamTrack( audioTracks[i] ); - outputAudioTracks.push(audioTrack); - } - - for (j = 0; j < videoTracks.length; j += 1) { - var videoTrack = polyfillMediaStreamTrack( videoTracks[j] ); - outputVideoTracks.push(videoTrack); - } - - polyStoreMediaTracks.audio = outputAudioTracks; - polyStoreMediaTracks.video = outputVideoTracks; - })(); - - stream.polystop = function () { - stream.stop(); - - stream.ended = true; - - var i, j; - - var outputAudioTracks = polyStoreMediaTracks.audio; - var outputVideoTracks = polyStoreMediaTracks.video; - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - outputAudioTracks[i].ended = true; - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - outputVideoTracks[j].ended = true; - } - }; - - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - throw error; - } - }; - - stream.polygetTrackById = function (trackId) { - // return stream.getTrackById(trackId); - // for right now, because MediaStreamTrack does not allow overwrites, - // we shall implement the polyfill to return the overwrite-able track. - var i, j; - - var outputAudioTracks = polyStoreMediaTracks.audio; - var outputVideoTracks = polyStoreMediaTracks.video; - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - if (outputAudioTracks[i].id === trackId) { - return outputAudioTracks[i]; - } - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - if (outputVideoTracks[j].id === trackId) { - return outputVideoTracks[j]; - } - } - - return null; - }; - - stream.polygetTracks = function (trackId) { - var outputAudioTracks = polyStoreMediaTracks.audio; - var outputVideoTracks = polyStoreMediaTracks.video; - - return outputAudioTracks.concat(outputVideoTracks); - }; - - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - throw error; - } - }; - - stream.polygetAudioTracks = function () { - return polyStoreMediaTracks.audio; - }; - - stream.polygetVideoTracks = function () { - return polyStoreMediaTracks.video; - }; - }; - - var originalGUM = navigator.getUserMedia; - - window.navigator.getUserMedia = function (constraints, successCb, failureCb) { - originalGUM(constraints, function(stream) { - - polyfillMediaStream(stream); - - successCb(stream); - }, failureCb); - }; - - window.getUserMedia = window.navigator.getUserMedia; -} \ No newline at end of file diff --git a/source/adapter.MediaStreamTrack.js b/source/adapter.MediaStreamTrack.js deleted file mode 100644 index 2c8c059..0000000 --- a/source/adapter.MediaStreamTrack.js +++ /dev/null @@ -1,322 +0,0 @@ -// Polyfill all MediaStream objects -var polyfillMediaStreamTrack = null; - - -if (navigator.mozGetUserMedia) { - - /** - * The polyfilled MediaStreamTrack class. - * @class MediaStreamTrack - * @since 0.10.5 - */ - polyfillMediaStreamTrack = function (track) { - - /** - * The MediaStreamTrack object id. - * @attribute id - * @type String - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - //track.id = track.id || (new Date()).getTime().toString(); - - /** - * The MediaStreamTrack object label. - * @attribute label - * @type String - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - //track.label = track.label || track.kind + '-' + track.id; - - /** - * The flag that indicates if a MediaStreamTrack object has ended. - * @attribute ended - * @type Boolean - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.ended = typeof track.ended === 'boolean' ? track.ended : false; - - /** - * The flag that indicates if a MediaStreamTrack object is a remote stream. - * @attribute remote - * @type Boolean - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.remote = typeof track.remote === 'boolean' ? track.remote : false; - - /** - * The flag that indicates if a MediaStreamTrack object is enabled. - * - Set it to true for enabled track stream or set it to - * false for disable track stream. - * @attribute enabled - * @type Boolean - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.enabled = true; - - /** - * The flag that indicates if a MediaStreamTrack object is muted. - * @attribute muted - * @type Boolean - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.muted = typeof track.muted === 'boolean' ? track.muted : false; - - /** - * The ready state status of a MediaStreamTrack object. - * @attribute readyState - * @type String - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.readyState = typeof track.readyState === 'string' ? track.readyState : 'live'; - - /** - * The MediaStreamTrack object type. - * - "audio": The MediaStreamTrack object type is an audio track. - * - "video": The MediaStreamTrack object type is an video track. - * @attribute kind - * @type String - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - //track.kind = track.kind; - - /** - * The status if a MediaStreamTrack object is read only and cannot to be overwritten. - * @attribute readOnly - * @type Boolean - * @readOnly - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.readOnly = typeof track.readOnly === 'boolean' ? track.readOnly : false; - - /** - * Event triggered when MediaStreamTrack has ended streaming. - * @event onended - * @param {String} type The type of event: "ended". - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.onended = null; - - /** - * Event triggered when MediaStreamTrack has started streaming. - * @event onstarted - * @param {String} type The type of event: "started". - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.onstarted = null; - - /** - * Event triggered when MediaStreamTrack has been muted. - * @event onmute - * @param {String} type The type of event: "mute". - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.onmute = null; - - /** - * Event triggered when MediaStreamTrack has been unmuted. - * @event onunmute - * @param {String} type The type of event: "unmute". - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.onunmute = null; - - /** - * Event triggered when MediaStreamTrack is over constrained. - * @event onoverconstrained - * @param {String} type The type of event: "overconstrained". - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.onoverconstrained = null; - - /** - * Listens and waits to check if all MediaStreamTracks of a MediaStream - * has ended. Once ended, this invokes the ended flag of the MediaStream. - * This loops every second. - * @method _polyOnTracksEndedListener - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - track._polyOnEndedListener = setInterval(function () { - if (track.ended) { - - clearInterval(track._polyOnEndedListener); - - // set the readyState to 'ended' - track.readyState = 'ended'; - - // trigger that it has ended - if (typeof track.onended === 'function') { - track.onended({ - type: 'ended', - bubbles: false, - cancelBubble: false, - cancelable: false, - currentTarget: track, - defaultPrevented: false, - eventPhase: 0, - returnValue: true, - srcElement: track, - target: track, - timeStamp: (new Date()).getTime() - }); - } - } - }, 1000); - - /** - * Stops a MediaStreamTrack streaming. - * @method polystop - * @for MediaStreamTrack - * @since 0.10.6 - */ - track.polystop = function () { - track.stop(); - - // set the ended as true - track.ended = true; - }; - }; - - -} else if (navigator.webkitGetUserMedia) { - - polyfillMediaStreamTrack = function (track) { - - //track.id = track.id || (new Date()).getTime().toString(); - - track.label = track.label || track.kind + '-' + track.id; - - track.ended = false; - - track.remote = typeof track.remote === 'boolean' ? track.remote : false; - - track.enabled = true; - - track.muted = typeof track.muted === 'boolean' ? track.muted : false; - - track.readyState = typeof track.readyState === 'string' ? track.readyState : 'live'; - - //track.kind = track.kind; - - track.readOnly = typeof track.readOnly === 'boolean' ? track.readOnly : false; - - track.onended = null; - - track.onstarted = null; - - track.onmute = null; - - track.onunmute = null; - - track.onoverconstrained = null; - - track.polystop = function () { - try { - track.stop(); - - // set the ended state to true - track.ended = true; - - } catch (error) { - throw error; - } - }; - }; - -} else { - - polyfillMediaStreamTrack = function (track) { - - track.id = track.id || (new Date()).getTime().toString(); - - track.label = typeof track.label === 'undefined' ? track.kind + '-' + track.id : track.label; - - track.ended = false; - - track.remote = typeof track.remote === 'boolean' ? track.remote : false; - - track.enabled = true; - - track.muted = typeof track.muted === 'boolean' ? track.muted : false; - - track.readyState = typeof track.readyState === 'string' ? track.readyState : 'live'; - - //track.kind = track.kind; - - track.readOnly = typeof track.readOnly === 'boolean' ? track.readOnly : false; - - track.onended = null; - - track.onstarted = null; - - track.onmute = null; - - track.onunmute = null; - - track.onoverconstrained = null; - - track._polyOnEndedListener = setInterval(function () { - if (track.ended) { - - clearInterval(track._polyOnEndedListener); - - // set the readyState to 'ended' - track.readyState = 'ended'; - - // trigger that it has ended - if (typeof track.onended === 'function') { - track.onended({ - type: 'ended', - bubbles: false, - cancelBubble: false, - cancelable: false, - currentTarget: track, - defaultPrevented: false, - eventPhase: 0, - returnValue: true, - srcElement: track, - target: track, - timeStamp: (new Date()).getTime() - }); - } - } - }, 1000); - - track.polystop = function () { - try { - track.stop(); - - // set the ended as true - track.ended = true; - - } catch (error) { - throw error; - } - }; - - return track; - }; -} \ No newline at end of file diff --git a/source/adapter.RTCPeerConnection.js b/source/adapter.RTCPeerConnection.js deleted file mode 100644 index 2d9a5c3..0000000 --- a/source/adapter.RTCPeerConnection.js +++ /dev/null @@ -1,630 +0,0 @@ -// Polyfill all MediaStream objects -var polyfillRTCPeerConnection = null; - -// Return the event payload -var returnEventPayloadFn = function (stream) { - return { - bubbles: false, - cancelBubble: false, - cancelable: false, - currentTarget: stream, - defaultPrevented: false, - eventPhase: 0, - returnValue: true, - srcElement: stream, - target: stream, - timeStamp: stream.currentTime || (new Date()).getTime() - }; -}; - -// MediaStreamTracks Polyfilled -var storePolyfillMediaStreamTracks = {}; - -// Firefox MediaStream -if (navigator.mozGetUserMedia) { - - /** - * The polyfilled RTCPeerConnection class. - * @class RTCPeerConnection - * @since 0.10.5 - */ - polyfillRTCPeerConnection = function (stream) { - - /** - * The MediaStream object id. - * @attribute id - * @type String - * @readOnly - * @for MediaStream - * @since 0.10.6 - */ - stream.id = stream.id || (new Date()).getTime().toString(); - - /** - * The flag that indicates if a MediaStream object has ended. - * @attribute ended - * @type Boolean - * @readOnly - * @for MediaStream - * @since 0.10.6 - */ - stream.ended = false; - - /** - * Event triggered when MediaStream has ended streaming. - * @event onended - * @param {String} type The type of event: "ended". - * @for MediaStream - * @since 0.10.6 - */ - stream.onended = null; - - /** - * Event triggered when MediaStream has added a new track. - * @event onaddtrack - * @param {String} type The type of event: "addtrack". - * @for MediaStream - * @since 0.10.6 - */ - stream.onaddtrack = null; - - /** - * Event triggered when MediaStream has removed an existing track. - * @event onremovetrack - * @param {String} type The type of event: "removetrack". - * @for MediaStream - * @since 0.10.6 - */ - stream.onremovetrack = null; - - /** - * Event triggered when a feature in the MediaStream is not supported - * but used. - * @event onunsupported - * @param {String} feature The feature that is not supported. Eg. "addTrack". - * @param {Object} error The error received natively. - * @param {String} type The type of event: "unsupported". - * @for MediaStream - * @since 0.10.6 - */ - stream.onunsupported = null; - - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - polyfillMediaStreamTrack( audioTracks[i] ); - } - - for (j = 0; j < videoTracks.length; j += 1) { - polyfillMediaStreamTrack( videoTracks[j] ); - } - })(); - - /** - * Stops a MediaStream streaming. - * @method polystop - * @for MediaStream - * @since 0.10.6 - */ - stream.polystop = function () { - if (stream instanceof LocalMediaStream) { - stream.stop(); - - } else { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - for (i = 0; i < audioTracks.length; i += 1) { - audioTracks[i].polystop(); - } - - for (j = 0; j < videoTracks.length; j += 1) { - videoTracks[j].polystop(); - } - } - }; - - /** - * Adds a MediaStreamTrack to an object. - * @method polyaddTrack - * @for MediaStream - * @since 0.10.6 - */ - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'addTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - /** - * Gets a MediaStreamTrack from a MediaStreamTrack based on the object id provided. - * @method polygetTrackById - * @param {String} trackId The MediaStreamTrack object id. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetTrackById = function (trackId) { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - if (audioTracks[i].id === trackId) { - return audioTracks[i]; - } - } - - for (j = 0; j < videoTracks.length; j += 1) { - if (videoTracks[i].id === trackId) { - return videoTracks[i]; - } - } - - return null; - }; - - /** - * Removes a MediaStreamTrack from an object. - * @method polyremoveTrack - * @for MediaStream - * @since 0.10.6 - */ - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'removeTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - /** - * Gets the list of audio MediaStreamTracks of a MediaStream. - * @method polygetAudioTracks - * @return {Array} Returns a list of the audio MediaStreamTracks - * available for the MediaStream. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetAudioTracks = stream.getAudioTracks; - - /** - * Gets the list of video MediaStreamTracks of a MediaStream. - * @method polygetVideoTracks - * @return {Array} Returns a list of the video MediaStreamTracks - * available for the MediaStream. - * @for MediaStream - * @since 0.10.6 - */ - stream.polygetVideoTracks = stream.getVideoTracks; - - /** - * Listens and waits to check if all MediaStreamTracks of a MediaStream - * has ended. Once ended, this invokes the ended flag of the MediaStream. - * This loops every second. - * @method _polyOnTracksEndedListener - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - stream._polyOnTracksEndedListener = setInterval(function () { - var i, j; - - var audios = stream.getAudioTracks(); - var videos = stream.getVideoTracks(); - - var audioEnded = true; - var videoEnded = true; - - // Check for all tracks if ended - for (i = 0; i < audios.length; i += 1) { - if (audios[i].ended !== true) { - audioEnded = false; - break; - } - } - - for (j = 0; j < videos.length; j += 1) { - if (videos[j].ended !== true) { - videoEnded = false; - break; - } - } - - if (audioEnded && videoEnded) { - clearInterval(stream._polyOnTracksEndedListener); - stream.ended = true; - } - }, 1000); - - /** - * Listens and waits to check if all MediaStream has ended. - * This loops every second. - * @method _polyOnEndedListener - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - if (stream instanceof LocalMediaStream) { - stream._polyOnEndedListener = setInterval(function () { - // If stream has flag ended because of media tracks being stopped - if (stream.ended) { - clearInterval(stream._polyOnEndedListener); - - // trigger that it has ended - if (typeof stream.onended === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'ended'; - stream.onended(eventPayload); - } - } - - if (typeof stream.recordedTime === 'undefined') { - stream.recordedTime = 0; - } - - if (stream.recordedTime === stream.currentTime) { - clearInterval(stream._polyOnEndedListener); - - stream.ended = true; - - // trigger that it has ended - if (typeof stream.onended === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'ended'; - stream.onended(eventPayload); - } - - } else { - stream.recordedTime = stream.currentTime; - } - }, 1000); - - } else { - /** - * Stores the attached video element with the existing MediaStream - * This loops every second. - * - This only exists in Firefox browsers. - * @attribute _polyOnEndedListenerObj - * @type DOM - * @private - * @optional - * @for MediaStream - * @since 0.10.6 - */ - // Use a video to attach to check if stream has ended - var video = document.createElement('video'); - - video._polyOnEndedListener = setInterval(function () { - // If stream has flag ended because of media tracks being stopped - if (stream.ended) { - clearInterval(video._polyOnEndedListener); - - // trigger that it has ended - if (typeof stream.onended === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'ended'; - stream.onended(eventPayload); - } - } - - // Check if mozSrcObject is not empty - if (typeof video.mozSrcObject === 'object' && - video.mozSrcObject !== null) { - - if (video.mozSrcObject.ended === true) { - clearInterval(video._polyOnEndedListener); - - stream.ended = true; - - // trigger that it has ended - if (typeof stream.onended === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'ended'; - stream.onended(eventPayload); - } - } - } - }, 1000); - - // Bind the video element to MediaStream object - stream._polyOnEndedListenerObj = video; - - window.attachMediaStream(video, stream); - } - }; - - window.getUserMedia = function (constraints, successCb, failureCb) { - - navigator.mozGetUserMedia(constraints, function (stream) { - polyfillMediaStream(stream); - - successCb(stream); - - }, failureCb); - }; - - window.attachMediaStream = function (element, stream) { - // If there's an element used for checking stream stop - // for an instance remote MediaStream for firefox - // reattachmediastream instead - if (typeof stream._polyOnEndedListenerObj !== 'undefined' && - stream instanceof LocalMediaStream === false) { - window.reattachMediaStream(element, bind._polyOnEndedListenerObj); - - // LocalMediaStream - } else { - console.log('Attaching media stream'); - element.mozSrcObject = stream; - } - }; - -// Chrome / Opera MediaStream -} else if (navigator.webkitGetUserMedia) { - - polyfillRTCPeerConnection = function (stream) { - - stream.onended = null; - - stream.onaddtrack = null; - - stream.onremovetrack = null; - - stream.onunsupported = null; - - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - polyfillMediaStreamTrack( audioTracks[i] ); - } - - for (j = 0; j < videoTracks.length; j += 1) { - polyfillMediaStreamTrack( videoTracks[j] ); - } - })(); - - stream.polystop = function () { - stream.stop(); - }; - - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'addTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - stream.polygetTrackById = stream.getTrackById; - - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'removeTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - stream.polygetAudioTracks = stream.getAudioTracks; - - stream.polygetVideoTracks = stream.getVideoTracks; - }; - - window.getUserMedia = function (constraints, successCb, failureCb) { - navigator.webkitGetUserMedia(constraints, function (stream) { - - polyfillMediaStream(stream); - - successCb(stream); - }, failureCb); - - }; - -// Safari MediaStream -} else { - - polyfillRTCPeerConnection = function (stream) { - - /** - * Stores the store Id to store MediaStreamTrack functions. - * - This only exists in Safari / IE (Plugin-enabled) browsers. - * @attribute _polyStoreId - * @type String - * @optional - * @private - * @for MediaStream - * @since 0.10.6 - */ - stream._polyStoreId = (new Date()).getTime().toString(); - - stream.ended = typeof stream.ended === 'boolean' ? stream.ended : false; - - stream.onended = null; - - stream.onaddtrack = null; - - stream.onremovetrack = null; - - stream.onunsupported = null; - - (function () { - var i, j; - - var audioTracks = stream.getAudioTracks(); - var videoTracks = stream.getVideoTracks(); - - var outputAudioTracks = []; - var outputVideoTracks = []; - - // Check for all tracks if ended - for (i = 0; i < audioTracks.length; i += 1) { - var track = polyfillMediaStreamTrack( audioTracks[i] ); - outputAudioTracks.push(track); - } - - for (j = 0; j < videoTracks.length; j += 1) { - var track = polyfillMediaStreamTrack( videoTracks[j] ); - outputVideoTracks.push(track); - } - - storePolyfillMediaStreamTracks[stream._polyStoreId] = { - audio: outputAudioTracks, - video: outputVideoTracks - }; - })(); - - stream.polystop = function () { - stream.stop(); - - var i, j; - - var outputAudioTracks = storePolyfillMediaStreamTracks[stream._polyStoreId].audio; - var outputVideoTracks = storePolyfillMediaStreamTracks[stream._polyStoreId].video; - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - var track = outputAudioTracks[i]; - track.ended = true; - - if (typeof track.onended === 'function') { - var eventPayload = returnEventPayloadFn(track); - eventPayload.type = 'ended'; - - if (typeof track.onended === 'function') { - track.onended(eventPayload); - } - } - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - var track = outputVideoTracks[j]; - track.ended = true; - - if (typeof track.onended === 'function') { - var eventPayload = returnEventPayloadFn(track); - eventPayload.type = 'ended'; - - if (typeof track.onended === 'function') { - track.onended(eventPayload); - } - } - } - }; - - stream.polyaddTrack = function (track) { - try { - stream.addTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'addTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - stream.polygetTrackById = function (trackId) { - var i, j; - - var outputAudioTracks = storePolyfillMediaStreamTracks[stream._polyStoreId].audio; - var outputVideoTracks = storePolyfillMediaStreamTracks[stream._polyStoreId].video; - - // Check for all tracks if ended - for (i = 0; i < outputAudioTracks.length; i += 1) { - if (outputAudioTracks[i].id === trackId) { - return outputAudioTracks[i]; - } - } - - for (j = 0; j < outputVideoTracks.length; j += 1) { - if (outputVideoTracks[j].id === trackId) { - return outputVideoTracks[j]; - } - } - - return null; - }; - - stream.polyremoveTrack = function (track) { - try { - stream.removeTrack(track); - } catch (error) { - // trigger that it has ended - if (typeof stream.onunsupported === 'function') { - var eventPayload = returnEventPayloadFn(stream); - eventPayload.type = 'unsupported'; - eventPayload.error = error; - eventPayload.feature = 'removeTrack'; - stream.onunsupported(eventPayload); - } - } - }; - - stream.polygetAudioTracks = function () { - return storePolyfillMediaStreamTracks[stream._polyStoreId].audio; - }; - - stream.polygetVideoTracks = function () { - return storePolyfillMediaStreamTracks[stream._polyStoreId].video; - }; - }; - - window.getUserMedia = function (constraints, successCb, failureCb) { - navigator.getUserMedia(constraints, function(stream) { - - polyfillMediaStream(stream); - - successCb(stream); - }, failureCb); - }; -} \ No newline at end of file diff --git a/source/adapter.js b/source/adapter.js index 84c7753..46b8868 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -56,7 +56,9 @@ AdapterJS.webRTCReady = function (callback) { AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; // The object to store plugin information -@@include('source/pluginInfo.js', {}) +/* jshint ignore:start */ +@Tem@include('pluginInfo.js', {}) +/* jshint ignore:end */ AdapterJS.WebRTCPlugin.TAGS = { NONE : 'none', @@ -143,16 +145,14 @@ AdapterJS.WebRTCPlugin.callWhenPluginReady = null; // This function is the only private function that is not encapsulated to // allow the plugin method to be called. __TemWebRTCReady0 = function () { - webrtcDetectedVersion = AdapterJS.WebRTCPlugin.plugin.version; - if (document.readyState === 'complete') { AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } else { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { + var timer = setInterval(function () { if (document.readyState === 'complete') { // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); + clearInterval(timer); AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } @@ -223,65 +223,62 @@ AdapterJS.isDefined = null; // This sets: // - webrtcDetectedBrowser: The browser agent name. // - webrtcDetectedVersion: The browser version. +// - webrtcMinimumVersion: The minimum browser version still supported by AJS. // - webrtcDetectedType: The types of webRTC support. // - 'moz': Mozilla implementation of webRTC. // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. AdapterJS.parseWebrtcDetectedBrowser = function () { - var hasMatch, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + var hasMatch = null; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (typeof InstallTrigger !== 'undefined') { + // Firefox 1.0+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'moz'; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + // Safari + webrtcDetectedBrowser = 'safari'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; + hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (/*@cc_on!@*/false || !!document.documentMode) { + // Internet Explorer 6-11 webrtcDetectedBrowser = 'IE'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 9; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); + if (!webrtcDetectedVersion) { + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } + } else if (!!window.StyleMedia) { + // Edge 20+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = ''; + } else if (!!window.chrome && !!window.chrome.webstore) { + // Chrome 1+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'webkit'; + } else if ((webrtcDetectedBrowser === 'chrome'|| webrtcDetectedBrowser === 'opera') && + !!window.CSS) { + // Blink engine detection + webrtcDetectedBrowser = 'blink'; + // TODO: detected WebRTC version } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; -// To fix configuration as some browsers does not support -// the 'urls' attribute. -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; }; AdapterJS.addEvent = function(elem, evnt, func) { @@ -319,7 +316,7 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe i.style.transition = 'all .5s ease-out'; } document.body.appendChild(i); - c = (i.contentWindow) ? i.contentWindow : + var c = (i.contentWindow) ? i.contentWindow : (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; c.document.open(); c.document.write(' 1) { + // RTCPeerConnection prototype from the new spec + return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); + } else { + // RTCPeerConnection prototype from the old spec + var iceServers = null; + if (servers && Array.isArray(servers.iceServers)) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + } }; MediaStreamTrack = {}; @@ -938,7 +988,7 @@ if ( navigator.mozGetUserMedia }); }; - window.getUserMedia = function (constraints, successCallback, failureCallback) { + getUserMedia = function (constraints, successCallback, failureCallback) { constraints.audio = constraints.audio || false; constraints.video = constraints.video || false; @@ -947,11 +997,16 @@ if ( navigator.mozGetUserMedia getUserMedia(constraints, successCallback, failureCallback); }); }; - window.navigator.getUserMedia = window.getUserMedia; + window.navigator.getUserMedia = getUserMedia; // Defined mediaDevices when promises are available - if ( !navigator.mediaDevices - && typeof Promise !== 'undefined') { + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { + requestUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + }; navigator.mediaDevices = {getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { @@ -960,6 +1015,7 @@ if ( navigator.mozGetUserMedia resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], + id: device.id, deviceId: device.id, groupId: ''}; })); @@ -1069,31 +1125,32 @@ if ( navigator.mozGetUserMedia } }; - AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { properties = Object.getOwnPropertyNames( prototype ); - - for(prop in properties) { - propName = properties[prop]; - - if (typeof(propName.slice) === 'function') { - if (propName.slice(0,2) == 'on' && srcElem[propName] != null) { - if (isIE) { - destElem.attachEvent(propName,srcElem[propName]); - } else { - destElem.addEventListener(propName.slice(2), srcElem[propName], false) - } - } else { - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? + for(var prop in properties) { + if (prop) { + propName = properties[prop]; + + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); } } } - - var subPrototype = Object.getPrototypeOf(prototype) - if(subPrototype != null) { + var subPrototype = Object.getPrototypeOf(prototype); + if(!!subPrototype) { AdapterJS.forwardEventHandlers(destElem, srcElem, subPrototype); } - } + }; RTCIceCandidate = function (candidate) { if (!candidate.sdpMid) { diff --git a/source/adapter.plugin.js b/source/adapter.plugin.js deleted file mode 100644 index a9f89cd..0000000 --- a/source/adapter.plugin.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * The Temasys AdapterJS Plugin interface. - * @class WebRTCPlugin - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; - -/** - * Contains the plugin information. - * @property pluginInfo - * @param {String} prefix The plugin prefix name. - * @param {String} plugName The plugin object name. - * @param {String} pluginId The plugin object id. - * @param {String} type The plugin object type. - * @param {String} onload The Javascript function to trigger when - * the plugin object has loaded. - * @param {String} portalLink The plugin website url. - * @param {String} downloadLink The link to download new versions - * of the plugin. - * @param {String} companyName The plugin company name. - * @type JSON - * @private - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.pluginInfo = { - prefix : 'Tem', - plugName : 'TemWebRTCPlugin', - pluginId : 'plugin0', - type : 'application/x-temwebrtcplugin', - onload : '__TemWebRTCReady0', - portalLink : 'http://skylink.io/plugin/', - downloadLink : (function () { - // Placed on-top to return the url string directly instead - if (!!navigator.platform.match(/^Mac/i)) { - return 'http://bit.ly/1n77hco'; - } else if (!!navigator.platform.match(/^Win/i)) { - return 'http://bit.ly/1kkS4FN'; - } - return null; - })(), - companyName: 'Temasys' -}; - -/** - * Contains the unique identifier of each opened page - * @property pageId - * @type String - * @private - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); - -/** - * Use this whenever you want to call the plugin. - * @property plugin - * @type Object - * @private - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.plugin = null; - -/** - * Sets log level for the plugin once it is ready. - * This is an asynchronous function that will run when the plugin is ready - * @property setLogLevel - * @type Function - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.setLogLevel = null; //function (logLevel) {}; - -/** - * Defines webrtc's JS interface according to the plugin's implementation. - * Define plugin Browsers as WebRTC Interface. - * @property defineWebRTCInterface - * @type Function - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; //function () { }; - -/** - * This function detects whether or not a plugin is installed. - * we're running IE and do something. If not it is not supported. - * @property isPluginInstalled - * @type Function - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.isPluginInstalled = null; // function () { }; - -/** - * Lets adapter.js wait until the the document is ready before injecting the plugin. - * @property pluginInjectionInterval - * @type Object - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; - -/** - * Injects the HTML DOM object element into the page. - * @property injectPlugin - * @type Function - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.injectPlugin = null; - -/** - * States of readiness that the plugin goes through when being injected and stated. - * @property PLUGIN_STATES - * @param {Integer} NONE No plugin use - * @param {Integer} INITIALIZING Detected need for plugin - * @param {Integer} INJECTING Injecting plugin - * @param {Integer} INJECTED Plugin element injected but not usable yet - * @param {Integer} READY Plugin ready to be used - * @type JSON - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.PLUGIN_STATES = { - NONE : 0, - INITIALIZING : 1, - INJECTING : 2, - INJECTED: 3, - READY: 4 -}; - -/** - * Current state of the plugin. You cannot use the plugin before this is - * equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY. - * @property pluginState - * @type Integer - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; - -/** - * True is AdapterJS.onwebrtcready was already called, false otherwise. - * Used to make sure AdapterJS.onwebrtcready is only called once. - * @property onwebrtcreadyDone - * @type Boolean - * @readOnly - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.onwebrtcreadyDone = false; - -/** - * Log levels for the plugin. - * Log outputs are prefixed in some cases. - * From the least verbose to the most verbose - * @property PLUGIN_LOG_LEVELS - * @param {String} NONE No log level. - * @param {String} ERROR Errors originating from within the plugin. - * @param {String} INFO Information reported by the plugin. - * @param {String} VERBOSE Verbose mode. - * @param {String} SENSITIVE Sensitive mode. - * @type JSON - * @readOnly - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { - NONE : 'NONE', - ERROR : 'ERROR', - WARNING : 'WARNING', - INFO: 'INFO', - VERBOSE: 'VERBOSE', - SENSITIVE: 'SENSITIVE' -}; - -/** - * Does a waiting check before proceeding to load the plugin. - * @property WaitForPluginReady - * @type Function - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.WaitForPluginReady = null; - -/** - * This method will use an interval to wait for the plugin to be ready. - * @property callWhenPluginReady - * @type Function - * @private - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.callWhenPluginReady = null; - -/** - * This function will be called if the plugin is needed (browser different - * from Chrome or Firefox), but the plugin is not installed. - * Override it according to your application logic. - * @property pluginNeededButNotInstalledCb - * @type Function - * @for WebRTCPlugin - * @since 0.10.5 - */ -AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = null; - -/** - * !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! - * This function will be called when plugin is ready. It sends necessary - * details to the plugin. - * The function will wait for the document to be ready and the set the - * plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, - * indicating that it can start being requested. - * This function is not in the IE/Safari condition brackets so that - * TemPluginLoaded function might be called on Chrome/Firefox. - * This function is the only private function that is not encapsulated to - * allow the plugin method to be called. - * @method pluginNeededButNotInstalledCb - * @private - * @global true - * @for WebRTCPlugin - * @since 0.10.5 - */ -window.__TemWebRTCReady0 = function () { - if (document.readyState === 'complete') { - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; - - AdapterJS.maybeThroughWebRTCReady(); - } else { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { - if (document.readyState === 'complete') { - // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; - - AdapterJS.maybeThroughWebRTCReady(); - } - }, 100); - } -}; \ No newline at end of file diff --git a/source/adapter.plugin.rtc.adapter.js b/source/adapter.plugin.rtc.adapter.js deleted file mode 100644 index 159edef..0000000 --- a/source/adapter.plugin.rtc.adapter.js +++ /dev/null @@ -1,410 +0,0 @@ -if (!navigator.mozGetUserMedia && !navigator.webkitGetUserMedia) { - // IE 9 is not offering an implementation of console.log until you open a console - if (typeof console !== 'object' || typeof console.log !== 'function') { - /* jshint -W020 */ - console = {} || console; - // Implemented based on console specs from MDN - // You may override these functions - console.log = function (arg) {}; - console.info = function (arg) {}; - console.error = function (arg) {}; - console.dir = function (arg) {}; - console.exception = function (arg) {}; - console.trace = function (arg) {}; - console.warn = function (arg) {}; - console.count = function (arg) {}; - console.debug = function (arg) {}; - console.count = function (arg) {}; - console.time = function (arg) {}; - console.timeEnd = function (arg) {}; - console.group = function (arg) {}; - console.groupCollapsed = function (arg) {}; - console.groupEnd = function (arg) {}; - /* jshint +W020 */ - } - webrtcDetectedType = 'plugin'; - webrtcDetectedDCSupport = 'plugin'; - AdapterJS.parseWebrtcDetectedBrowser(); - var isIE = webrtcDetectedBrowser === 'IE'; - - /* jshint -W035 */ - AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { - while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - /* empty because it needs to prevent the function from running. */ - } - }; - /* jshint +W035 */ - - AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { - if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - // Call immediately if possible - // Once the plugin is set, the code will always take this path - callback(); - } else { - // otherwise start a 100ms interval - var checkPluginReadyState = setInterval(function () { - if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - clearInterval(checkPluginReadyState); - callback(); - } - }, 100); - } - }; - - AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); - }); - }; - - AdapterJS.WebRTCPlugin.injectPlugin = function () { - // only inject once the page is ready - if (document.readyState !== 'complete') { - return; - } - - // Prevent multiple injections - if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { - return; - } - - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; - - if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { - var frag = document.createDocumentFragment(); - AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); - AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + - ' ' + - ' ' + - ' ' + - '' + - // uncomment to be able to use virtual cams - (AdapterJS.options.getAllCams ? '':'') + - - ''; - while (AdapterJS.WebRTCPlugin.plugin.firstChild) { - frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild); - } - document.body.appendChild(frag); - - // Need to re-fetch the plugin - AdapterJS.WebRTCPlugin.plugin = - document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId); - } else { - // Load Plugin - AdapterJS.WebRTCPlugin.plugin = document.createElement('object'); - AdapterJS.WebRTCPlugin.plugin.id = - AdapterJS.WebRTCPlugin.pluginInfo.pluginId; - // IE will only start the plugin if it's ACTUALLY visible - if (isIE) { - AdapterJS.WebRTCPlugin.plugin.width = '1px'; - AdapterJS.WebRTCPlugin.plugin.height = '1px'; - } - AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type; - AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + - '' + - ' ' + - (AdapterJS.options.getAllCams ? '':'') + - ''; - document.body.appendChild(AdapterJS.WebRTCPlugin.plugin); - } - - - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED; - }; - - AdapterJS.WebRTCPlugin.isPluginInstalled = - function (comName, plugName, installedCb, notInstalledCb) { - if (!isIE) { - var pluginArray = navigator.plugins; - for (var i = 0; i < pluginArray.length; i++) { - if (pluginArray[i].name.indexOf(plugName) >= 0) { - installedCb(); - return; - } - } - notInstalledCb(); - } else { - try { - var axo = new ActiveXObject(comName + '.' + plugName); - } catch (e) { - notInstalledCb(); - return; - } - installedCb(); - } - }; - - AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; - - AdapterJS.isDefined = function (variable) { - return variable !== null && variable !== undefined; - }; - - window.createIceServer = function (url, username, password) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { - 'url' : url, - 'hasCredentials' : false - }; - } else if (url_parts[0].indexOf('turn') === 0) { - iceServer = { - 'url' : url, - 'hasCredentials' : true, - 'credential' : password, - 'username' : username - }; - } - return iceServer; - }; - - window.createIceServers = function (urls, username, password) { - var iceServers = []; - for (var i = 0; i < urls.length; ++i) { - iceServers.push(createIceServer(urls[i], username, password)); - } - return iceServers; - }; - - window.RTCSessionDescription = function (info) { - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - ConstructSessionDescription(info.type, info.sdp); - }; - - RTCPeerConnection = function (servers, constraints) { - var iceServers = null; - if (servers) { - iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - if (iceServers[i].urls && !iceServers[i].url) { - iceServers[i].url = iceServers[i].urls; - } - iceServers[i].hasCredentials = AdapterJS. - isDefined(iceServers[i].username) && - AdapterJS.isDefined(iceServers[i].credential); - } - } - var mandatory = (constraints && constraints.mandatory) ? - constraints.mandatory : null; - var optional = (constraints && constraints.optional) ? - constraints.optional : null; - - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - PeerConnection(AdapterJS.WebRTCPlugin.pageId, - iceServers, mandatory, optional); - }; - - window.MediaStreamTrack = {}; - MediaStreamTrack.getSources = function (callback) { - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin.GetSources(callback); - }); - }; - - getUserMedia = function (constraints, successCallback, failureCallback) { - if (!constraints.audio) { - constraints.audio = false; - } - - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin. - getUserMedia(constraints, successCallback, failureCallback); - }); - }; - navigator.getUserMedia = getUserMedia; - - attachMediaStream = function (element, stream) { - stream.enableSoundTracks(true); - if (element.nodeName.toLowerCase() !== 'audio') { - var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; - if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) { - var frag = document.createDocumentFragment(); - var temp = document.createElement('div'); - var classHTML = (element.className) ? 'class="' + element.className + '" ' : ''; - temp.innerHTML = '' + - ' ' + - ' ' + - ' ' + - ' ' + - ''; - while (temp.firstChild) { - frag.appendChild(temp.firstChild); - } - var rectObject = element.getBoundingClientRect(); - element.parentNode.insertBefore(frag, element); - frag = document.getElementById(elementId); - frag.width = rectObject.width + 'px'; - frag.height = rectObject.height + 'px'; - element.parentNode.removeChild(element); - } else { - var children = element.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - children[i].value = stream.id; - break; - } - } - element.setStreamId(stream.id); - } - var newElement = document.getElementById(elementId); - newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {}; - if (isIE) { // on IE the event needs to be plugged manually - newElement.attachEvent('onplaying', newElement.onplaying); - newElement.onclick = (element.onclick) ? element.onclick : function (arg) {}; - newElement._TemOnClick = function (id) { - var arg = { - srcElement : document.getElementById(id) - }; - newElement.onclick(arg); - }; - } - return newElement; - } else { - return element; - } - }; - - reattachMediaStream = function (to, from) { - var stream = null; - var children = from.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - stream = AdapterJS.WebRTCPlugin.plugin - .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value); - break; - } - } - if (stream !== null) { - return attachMediaStream(to, stream); - } else { - console.log('Could not find the stream associated with this element'); - } - }; - - window.RTCIceCandidate = function (candidate) { - if (!candidate.sdpMid) { - candidate.sdpMid = ''; - } - - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate( - candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate - ); - }; - - // inject plugin - AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin); - AdapterJS.WebRTCPlugin.injectPlugin(); - }; - - AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb || - function() { - AdapterJS.addEvent(document, - 'readystatechange', - AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv); - AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv(); - }; - - AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () { - if (AdapterJS.options.hidePluginInstallPrompt) { - return; - } - - var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink; - if(downloadLink) { // if download link - var popupString; - if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link - popupString = 'This website requires you to install the ' + - ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName + - ' WebRTC Plugin' + - ' to work on this browser.'; - } else { // no portal link, just print a generic explanation - popupString = 'This website requires you to install a WebRTC-enabling plugin ' + - 'to work on this browser.'; - } - - AdapterJS.WebRTCPlugin.renderNotificationBar(popupString, 'Install Now', downloadLink); - } else { // no download link, just print a generic explanation - AdapterJS.WebRTCPlugin.renderNotificationBar('Your browser does not support WebRTC.'); - } - }; - - AdapterJS.WebRTCPlugin.renderNotificationBar = function (text, buttonText, buttonLink) { - // only inject once the page is ready - if (document.readyState !== 'complete') { - return; - } - - var w = window; - var i = document.createElement('iframe'); - i.name = 'adapterjs-alert'; - i.style.position = 'fixed'; - i.style.top = '-41px'; - i.style.left = 0; - i.style.right = 0; - i.style.width = '100%'; - i.style.height = '40px'; - i.style.backgroundColor = '#ffffe1'; - i.style.border = 'none'; - i.style.borderBottom = '1px solid #888888'; - i.style.zIndex = '9999999'; - if(typeof i.style.webkitTransition === 'string') { - i.style.webkitTransition = 'all .5s ease-out'; - } else if(typeof i.style.transition === 'string') { - i.style.transition = 'all .5s ease-out'; - } - document.body.appendChild(i); - c = (i.contentWindow) ? i.contentWindow : - (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; - c.document.open(); - c.document.write('' + text + ''); - if(buttonText && buttonLink) { - c.document.write(''); - c.document.close(); - AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { - window.open(buttonLink, '_top'); - e.preventDefault(); - try { - event.cancelBubble = true; - } catch(error) { } - }); - } - else { - c.document.close(); - } - AdapterJS.addEvent(c.document, 'click', function() { - w.document.body.removeChild(i); - }); - setTimeout(function() { - if(typeof i.style.webkitTransform === 'string') { - i.style.webkitTransform = 'translateY(40px)'; - } else if(typeof i.style.transform === 'string') { - i.style.transform = 'translateY(40px)'; - } else { - i.style.top = '0px'; - } - }, 300); - }; - // Try to detect the plugin and act accordingly - AdapterJS.WebRTCPlugin.isPluginInstalled( - AdapterJS.WebRTCPlugin.pluginInfo.prefix, - AdapterJS.WebRTCPlugin.pluginInfo.plugName, - AdapterJS.WebRTCPlugin.defineWebRTCInterface, - AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); -} diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js index 0f80647..4084d79 100644 --- a/source/adapter.screenshare.js +++ b/source/adapter.screenshare.js @@ -13,10 +13,14 @@ }; var clone = function(obj) { - if (null == obj || "object" != typeof obj) return obj; + if (null === obj || 'object' !== typeof obj) { + return obj; + } var copy = obj.constructor(); for (var attr in obj) { - if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; + if (obj.hasOwnProperty(attr)) { + copy[attr] = obj[attr]; + } } return copy; }; @@ -47,11 +51,10 @@ clearInterval(checkIfReady); baseGetUserMedia(updatedConstraints, successCb, function (error) { - if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') { + if (['PermissionDeniedError', 'SecurityError'].indexOf(error.name) > -1 && window.parent.location.protocol === 'https:') { AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF, AdapterJS.TEXT.EXTENSION.BUTTON_FF, - 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true); - //window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname; + 'https://addons.mozilla.org/en-US/firefox/addon/skylink-webrtc-tools/', true, true); } else { failureCb(error); } @@ -64,7 +67,12 @@ } }; - getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + window.getUserMedia(constraints, resolve, reject); + }); + }; } else if (window.navigator.webkitGetUserMedia) { baseGetUserMedia = window.navigator.getUserMedia; @@ -144,7 +152,12 @@ } }; - getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + window.getUserMedia(constraints, resolve, reject); + }); + }; } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // nothing here because edge does not support screensharing @@ -181,7 +194,9 @@ } }; - getUserMedia = window.navigator.getUserMedia; + AdapterJS.getUserMedia = getUserMedia = + window.getUserMedia = navigator.getUserMedia; + navigator.mediaDevices.getUserMedia = requestUserMedia; } // For chrome, use an iframe to load the screensharing extension @@ -199,7 +214,7 @@ (document.body || document.documentElement).appendChild(iframe); - var postFrameMessage = function (object) { + var postFrameMessage = function (object) { // jshint ignore:line object = object || {}; if (!iframe.isLoaded) { @@ -214,4 +229,4 @@ } else if (window.webrtcDetectedBrowser === 'opera') { console.warn('Opera does not support screensharing feature in getUserMedia'); } -})(); \ No newline at end of file +})(); diff --git a/source/adapter.utils.js b/source/adapter.utils.js deleted file mode 100644 index 5f7e386..0000000 --- a/source/adapter.utils.js +++ /dev/null @@ -1,341 +0,0 @@ -/** - * The Temasys AdapterJS interface. - * @class AdapterJS - * @since 0.10.5 - */ -window.AdapterJS = typeof window.AdapterJS !== 'undefined' ? window.AdapterJS : {}; - -/** - * Contains the options of the Temasys Plugin. - * @property options - * @param getAllCams {Boolean} Option to get virtual cameras. - * Override this value here. - * @param hidePluginInstallPrompt {Boolean} Option to prevent - * the install prompt when the plugin in not yet installed. - * Override this value here. - * @type JSON - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.options = { - getAllCams: false, - hidePluginInstallPrompt: false -}; - -/** - * The current version of the Temasys AdapterJS. - * @property VERSION. - * @type String - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.VERSION = '@@version'; - -/** - * The event function that will be called when the WebRTC API is - * ready to be used in cross-browsers. - * If you decide not to override use this synchronisation, it may result in - * an extensive CPU usage on the plugin start (once per tab loaded). - * Override this function to synchronise the start of your application - * with the WebRTC API being ready. - * @property onwebrtcready - * @return {Boolean} Returns a boolean in the event function that - * indicates if the WebRTC plugin is being used, false otherwise. - * @type Function - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function (isUsingPlugin) {}; - -/** - * Checks if maybe WebRTC is already ready. - * @property maybeThroughWebRTCReady - * @type Function - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.maybeThroughWebRTCReady = function () { - if (!AdapterJS.onwebrtcreadyDone) { - AdapterJS.onwebrtcreadyDone = true; - - if (typeof AdapterJS.onwebrtcready === 'function') { - AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); - } - } -}; - -/** - * The result of ICE connection states. - * @property _iceConnectionStates - * @param {String} starting ICE connection is starting. - * @param {String} checking ICE connection is checking. - * @param {String} connected ICE connection is connected. - * @param {String} completed ICE connection is connected. - * @param {String} done ICE connection has been completed. - * @param {String} disconnected ICE connection has been disconnected. - * @param {String} failed ICE connection has failed. - * @param {String} closed ICE connection is closed. - * @type JSON - * @readOnly - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS._iceConnectionStates = { - starting : 'starting', - checking : 'checking', - connected : 'connected', - completed : 'connected', - done : 'completed', - disconnected : 'disconnected', - failed : 'failed', - closed : 'closed' -}; - -/** - * The IceConnection states that has been fired for each peer. - * @property _iceConnectionFiredStates - * @param {Array} (#peerId) The ICE connection fired states for this peerId. - * @type Array - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS._iceConnectionFiredStates = []; - -/** - * Check if WebRTC Interface is defined. - * @property isDefined - * @type Boolean - * @readOnly - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.isDefined = null; - -/** - * This function helps to retrieve the webrtc detected browser information. - * This sets: - * webrtcDetectedBrowser: The browser agent name. - * - webrtcDetectedVersion: The browser version. - * - webrtcDetectedType: The types of webRTC support. - * - 'moz': Mozilla implementation of webRTC. - * - 'webkit': WebKit implementation of webRTC. - * - 'plugin': Using the plugin implementation. - * @property parseWebrtcDetectedBrowser - * @type Function - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.parseWebrtcDetectedBrowser = function () { - var hasMatch, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; - webrtcDetectedBrowser = 'IE'; - webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); - } - } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; - -/** - * To fix configuration as some browsers does not support - * the 'urls' attribute. - * @property maybeFixConfiguration - * @type Function - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } -}; - -/** - * Adds an event listener for Temasys plugin objects. - * @property addEvent - * @type Function - * @private - * @for AdapterJS - * @since 0.10.5 - */ -AdapterJS.addEvent = function(elem, evnt, func) { - if (elem.addEventListener) { // W3C DOM - elem.addEventListener(evnt, func, false); - } else if (elem.attachEvent) {// OLD IE DOM - elem.attachEvent('on' + evnt, func); - } else { // No much to do - elem[evnt] = func; - } -}; - -/** - * Detected webrtc implementation. Types are: - * - 'moz': Mozilla implementation of webRTC. - * - 'webkit': WebKit implementation of webRTC. - * - 'plugin': Using the plugin implementation. - * @property webrtcDetectedType - * @type String - * @readOnly - * @for AdapterJS - * @since 0.10.5 - */ -window.webrtcDetectedType = null; - -/** - * Detected webrtc datachannel support. Types are: - * - 'SCTP': SCTP datachannel support. - * - 'RTP': RTP datachannel support. - * @property webrtcDetectedType - * @type String - * @readOnly - * @for AdapterJS - * @since 0.10.5 - */ -window.webrtcDetectedDCSupport = null; - -/** - * Set the settings for creating DataChannels, MediaStream for - * Cross-browser compability. This is only for SCTP based support browsers. - * @method checkMediaDataChannelSettings - * @param {String} peerBrowserAgent The browser agent name. - * @param {Integer} peerBrowserVersion The browser agent version. - * @param {Function} callback The callback that gets fired once the function is - * completed. - * @param {JSON} constraints The RTCOfferOptions. - * @return {Boolean & JSON} (beOfferer, updatedConstraints) - * Returns a flag beOfferer if the peer should be the offer and also the updated unified - * RTCOfferOptions constraints. - * @readOnly - * @global true - * @for AdapterJS - * @since 0.10.5 - */ -window.checkMediaDataChannelSettings = function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { - if (typeof callback !== 'function') { - return; - } - var beOfferer = true; - var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; - // Nightly version does not require MozDontOfferDataChannel for interop - var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; - var isPeerFirefox = peerBrowserAgent === 'firefox'; - var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && - ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); - - // Resends an updated version of constraints for MozDataChannel to work - // If other userAgent is firefox and user is firefox, remove MozDataChannel - if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { - try { - delete constraints.mandatory.MozDontOfferDataChannel; - } catch (error) { - console.error('Failed deleting MozDontOfferDataChannel'); - console.error(error); - } - } else if ((isLocalFirefox && !isPeerFirefox)) { - constraints.mandatory.MozDontOfferDataChannel = true; - } - if (!isLocalFirefox) { - // temporary measure to remove Moz* constraints in non Firefox browsers - for (var prop in constraints.mandatory) { - if (constraints.mandatory.hasOwnProperty(prop)) { - if (prop.indexOf('Moz') !== -1) { - delete constraints.mandatory[prop]; - } - } - } - } - // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the - // interopability of the media stream - if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { - beOfferer = false; - } - callback(beOfferer, constraints); -}; - -/** - * Handles the differences for all browsers ice connection state output. - * Tested outcomes are: - * - Chrome (offerer) : 'checking' > 'completed' > 'completed' - * - Chrome (answerer) : 'checking' > 'connected' - * - Firefox (offerer) : 'checking' > 'connected' - * - Firefox (answerer): 'checking' > 'connected' - * @method checkIceConnectionState - * @param {String} peerId The peerId of the peer to check. - * @param {String} iceConnectionState The peer's current ICE connection state. - * @param {String} callback The callback that returns the updated connected state. - * @return {String} (state) - * Returns the updated ICE connection state. - * @for AdapterJS - * @since 0.10.5 - */ -window.checkIceConnectionState = function (peerId, iceConnectionState, callback) { - if (typeof callback !== 'function') { - console.warn('No callback specified in checkIceConnectionState. Aborted.'); - return; - } - peerId = (peerId) ? peerId : 'peer'; - - if (!AdapterJS._iceConnectionFiredStates[peerId] || - iceConnectionState === AdapterJS._iceConnectionStates.disconnected || - iceConnectionState === AdapterJS._iceConnectionStates.failed || - iceConnectionState === AdapterJS._iceConnectionStates.closed) { - AdapterJS._iceConnectionFiredStates[peerId] = []; - } - iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; - if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { - AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); - if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { - setTimeout(function () { - AdapterJS._iceConnectionFiredStates[peerId] - .push(AdapterJS._iceConnectionStates.done); - callback(AdapterJS._iceConnectionStates.done); - }, 1000); - } - callback(iceConnectionState); - } - return; -}; \ No newline at end of file diff --git a/tests/globals.js b/tests/globals.js index d9d7ee4..22176c0 100644 --- a/tests/globals.js +++ b/tests/globals.js @@ -94,10 +94,8 @@ var printJSON = function (obj, spaces) { if (typeof val === 'object') { outputStr += printJSON(val, spaces + 2); - } else if (typeof val === 'string') { outputStr += '"' + val + '"'; - } else { outputStr += val; } @@ -133,12 +131,13 @@ var printJSON = function (obj, spaces) { }; var isArrayEqual = function(array1, array2) { - if (array1.length != array2.length) - return false + if (array1.length !== array2.length) { + return false; + } return array1.every(function(element, index) { return element === array2[index]; - }) + }); }; // Connect the RTCPeerConnection object @@ -178,6 +177,17 @@ var connect = function (peer1, peer2, offerConstraints) { } }; + // create answer + var peer2AnswerCb = function (a) { + answer = a; + peer2.setLocalDescription(answer, function() {}, function (error) { + throw error; + }); + peer1.setRemoteDescription(answer, function() {}, function (error) { + throw error; + }); + }; + // create offer var peer1OfferCb = function (o) { offer = o; @@ -192,19 +202,12 @@ var connect = function (peer1, peer2, offerConstraints) { }); }; - // create answer - var peer2AnswerCb = function (a) { - answer = a; - peer2.setLocalDescription(answer, function() {}, function (error) { - throw error; - }); - peer1.setRemoteDescription(answer, function() {}, function (error) { - throw error; - }); - }; - // start peer1.createOffer(peer1OfferCb, function (error) { throw error; }, offerConstraints); -}; \ No newline at end of file +}; + +// Plugin functions have different types depending on the interface (NPAPI VS ActiveX) +FUNCTION_TYPE = webrtcDetectedBrowser === 'IE' ? 'object' : 'function'; + diff --git a/tests/karma.conf.js b/tests/karma.conf.js index 7263fe8..115d890 100644 --- a/tests/karma.conf.js +++ b/tests/karma.conf.js @@ -21,6 +21,7 @@ module.exports = function(config) { // tests {pattern: 'unit/*.spec.js', included: false}, + {pattern: 'unit/*.no-spec.js', included: false}, ], @@ -94,5 +95,5 @@ module.exports = function(config) { 'karma-ie-launcher', 'karma-opera-launcher', 'karma-requirejs'] - }) -} + }); +}; diff --git a/tests/unit/MediaStream.prop.spec.js b/tests/unit/MediaStream.prop.spec.js index 06d4681..2b4c995 100644 --- a/tests/unit/MediaStream.prop.spec.js +++ b/tests/unit/MediaStream.prop.spec.js @@ -78,7 +78,7 @@ describe('MediaStream | Properties', function() { it('MediaStream.ended :: boolean', function (done) { this.timeout(testItemTimeout); - assert.typeOf(stream.ended, 'boolean') + assert.typeOf(stream.ended, 'boolean'); done(); }); diff --git a/tests/unit/MediaStreamTrack.prop.spec.js b/tests/unit/MediaStreamTrack.prop.spec.js index 4ee90f3..d171a0b 100644 --- a/tests/unit/MediaStreamTrack.prop.spec.js +++ b/tests/unit/MediaStreamTrack.prop.spec.js @@ -47,7 +47,6 @@ describe('MediaStreamTrack | Properties', function() { }, function (error) { throw error; - done(); }); }); }); @@ -67,23 +66,7 @@ describe('MediaStreamTrack | Properties', function() { assert.typeOf(source1.facing, 'string'); assert.typeOf(source1.label, 'string'); - var constraints = {}; - - constraints[source1.kind] = { - optional: [{ sourceId: source1.id }] - }; - - window.navigator.getUserMedia(constraints, function (checkStream) { - - var checkTrack = source1.kind === 'audio' ? checkStream.getAudioTracks()[0] : - checkStream.getVideoTracks()[0]; - - expect(checkTrack.id).to.equal(source1.id); - done(); - - }, function (error) { - throw error; - }); + done(); }); }); @@ -96,7 +79,7 @@ describe('MediaStreamTrack | Properties', function() { expect(audioTrack.id).to.not.equal(videoTrack.id); }); - it('MediaStreamTrack.ended :: boolean', function () { + it.skip('MediaStreamTrack.ended :: boolean', function () { this.timeout(testItemTimeout); assert.typeOf(audioTrack.ended, 'boolean'); diff --git a/tests/unit/Plugin.ScreenSaver.behaviour.spec.js b/tests/unit/Plugin.ScreenSaver.behaviour.spec.js index c2a82b3..aae6c0d 100644 --- a/tests/unit/Plugin.ScreenSaver.behaviour.spec.js +++ b/tests/unit/Plugin.ScreenSaver.behaviour.spec.js @@ -58,7 +58,7 @@ if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') { document.body.removeChild(video); stream = null; - if(interval != null) { + if(interval !== null) { clearInterval(interval); interval = null; } diff --git a/tests/unit/Plugin.features.no-spec.js b/tests/unit/Plugin.features.no-spec.js new file mode 100644 index 0000000..ebe6796 --- /dev/null +++ b/tests/unit/Plugin.features.no-spec.js @@ -0,0 +1,83 @@ +var expect = chai.expect; +var assert = chai.assert; +var should = chai.should; + +// Test timeouts +var testTimeout = 120000; + +// Get User Media timeout +var gUMTimeout = 15000; + +// Test item timeout +var testItemTimeout = 5000; + +// !!! THIS TEST ONLY APPLIES FOR PLUGIN-BASED BROWSERS !!! +if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') { + + describe('Plugin Features | Out of spec', function() { + this.timeout(testTimeout); + + /* Attributes */ + var video = null; + var stream = null; + + /* WebRTC Object should be initialized in Safari/IE Plugin */ + before(function (done) { + this.timeout(gUMTimeout); + + AdapterJS.webRTCReady(function() { + done(); + }); + }); + + beforeEach(function (done) { + this.timeout(gUMTimeout); + + window.navigator.getUserMedia({ + audio: true, + video: true + + }, function (data) { + stream = data; + video = document.createElement('video'); + document.body.appendChild(video); + done(); + + }, function (error) { + throw error; + }); + + }); + + afterEach(function () { + document.body.removeChild(video); + stream = null; + }); + + it('getFrame', function(done) { + this.timeout(testTimeout); + video.onplay = function(e) { + assert.isNotNull(video.getFrame); + // assert.typeOf(video.getFrame, 'function'); + + var canvas = document.createElement('canvas'); + document.body.appendChild(canvas); + + var base64 = video.getFrame(); + assert.isString(base64); + expect(base64).to.have.length.above(1000); + + var img = new Image(); + img.onload = function () { + canvas.getContext('2d'). + drawImage(img, 0, 0, canvas.width, canvas.height); + done(); + }; + img.setAttribute('src', 'data:image/png;base64,' + base64); + + }; + video = attachMediaStream(video, stream); + }); + + }); // describe('Plugin Object | Stability' +} // if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') diff --git a/tests/unit/Plugin.object.stability.spec.js b/tests/unit/Plugin.object.stability.spec.js index 18291a7..1b30f79 100644 --- a/tests/unit/Plugin.object.stability.spec.js +++ b/tests/unit/Plugin.object.stability.spec.js @@ -61,23 +61,23 @@ if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') { this.timeout(testItemTimeout); var popCount = 0; - var timeout; + var t; var replaceVideoElement = function() { - clearTimeout(timeout); + clearTimeout(t); document.body.removeChild(video); video = document.createElement('video'); document.body.appendChild(video); video.onplay = replaceVideoElement; video = attachMediaStream(video, stream); - timeout = setTimeout(replaceVideoElement, 500); + t = setTimeout(replaceVideoElement, 500); expect(video.valid).to.equal(true); if (++popCount >= POP_REQUESTS) { - clearTimeout(timeout); + clearTimeout(t); done(); } - } + }; replaceVideoElement(); }); diff --git a/tests/unit/RTCDTMFSender.event.spec.js b/tests/unit/RTCDTMFSender.event.spec.js new file mode 100644 index 0000000..ea0e326 --- /dev/null +++ b/tests/unit/RTCDTMFSender.event.spec.js @@ -0,0 +1,90 @@ +var expect = chai.expect; +var assert = chai.assert; +var should = chai.should; + +// Test timeouts +var testTimeout = 3500; + +// Get User Media timeout +var gUMTimeout = 5000; + +// Test item timeout +var testItemTimeout = 2000; + +describe('RTCDTMFSender | event', function() { + this.timeout(testTimeout); + + var pc1 = null; + var pc2 = null; + var audioTrack = null; + var dtmfSender = null; + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + /* WebRTC Object should be initialized in Safari/IE Plugin */ + before(function (done) { + this.timeout(testItemTimeout); + + AdapterJS.webRTCReady(function() { + window.navigator.getUserMedia({ + audio: true, + video: false + }, function (s) { + stream = s; + audioTrack = stream.getAudioTracks()[0]; + done(); + }, function (error) { + throw error; + }); + }); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + beforeEach(function (done) { + this.timeout(gUMTimeout); + + pc1 = new RTCPeerConnection({ iceServers: [] }); + pc2 = new RTCPeerConnection({ iceServers: [] }); + pc1.oniceconnectionstatechange = function (evt) { + if(pc1 && pc1.iceConnectionState === 'connected') { + dtmfSender = pc1.createDTMFSender(audioTrack); + done(); + } + }; + pc1.addStream(stream); + connect(pc1, pc2); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + afterEach(function(done) { + pc1.close(); + pc2.close(); + pc1 = null; + pc2 = null; + dtmfSender = null; + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.ontonechange :: emit', function (done) { + this.timeout(testItemTimeout); + var emitTone = '8'; + + dtmfSender.ontonechange = function(evt) { + assert.isNotNull(evt, 'Event argument missing'); + assert.isNotNull(evt.target, 'Event target missing'); + assert.isNotNull(evt.currentTarget, 'Event currentTarget missing'); + assert.isNotNull(evt.srcElement, 'Event srcElement missing'); + assert.isNotNull(evt.tone, 'Event tone missing'); + assert.equal(evt.tone, emitTone, 'Wrong tone sent'); + + done(); + }; + + dtmfSender.insertDTMF(emitTone, 100, 100); + }); + +}); diff --git a/tests/unit/RTCDTMFSender.prop.spec.js b/tests/unit/RTCDTMFSender.prop.spec.js new file mode 100644 index 0000000..ca988aa --- /dev/null +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -0,0 +1,168 @@ +var expect = chai.expect; +var assert = chai.assert; +var should = chai.should; + +// Test timeouts +var testTimeout = 35000; + +// Get User Media timeout +var gUMTimeout = 5000; + +// Test item timeout +var testItemTimeout = 2000; + +describe('RTCDTMFSender | prop', function() { + this.timeout(testTimeout); + + var pc1 = null; + var pc2 = null; + var audioTrack = null; + var dtmfSender = null; + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + /* WebRTC Object should be initialized in Safari/IE Plugin */ + before(function (done) { + this.timeout(testItemTimeout); + + AdapterJS.webRTCReady(function() { + window.navigator.getUserMedia({ + audio: true, + video: false + }, function (s) { + stream = s; + audioTrack = stream.getAudioTracks()[0]; + done(); + }, function (error) { + throw error; + }); + }); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + beforeEach(function (done) { + this.timeout(gUMTimeout); + + pc1 = new RTCPeerConnection({ iceServers: [] }); + pc2 = new RTCPeerConnection({ iceServers: [] }); + pc1.oniceconnectionstatechange = function (evt) { + if(pc1 && pc1.iceConnectionState === 'connected') { + dtmfSender = pc1.createDTMFSender(audioTrack); + done(); + } + }; + pc1.addStream(stream); + connect(pc1, pc2); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + afterEach(function(done) { + pc1.close(); + pc2.close(); + pc1 = null; + pc2 = null; + dtmfSender = null; + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: function', function (done) { + this.timeout(testItemTimeout); + assert.equal(typeof dtmfSender.insertDTMF, FUNCTION_TYPE); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: success returns true', function (done) { + this.timeout(testItemTimeout); + assert.isTrue(dtmfSender.insertDTMF('', 100, 100)); + assert.isTrue(dtmfSender.insertDTMF('13,1', 100, 100)); + assert.isTrue(dtmfSender.insertDTMF(',,,', 200, 100)); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: failure returns false', function (done) { + this.timeout(testItemTimeout); + assert.isFalse(dtmfSender.insertDTMF('13,1', 10, 100)); + assert.isFalse(dtmfSender.insertDTMF('13,1', 100, 10)); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: edge values', function (done) { + this.timeout(testItemTimeout); + assert.isTrue(dtmfSender.insertDTMF('1', 70, 100), 'on low duration egde'); + assert.isTrue(dtmfSender.insertDTMF('1', 6000, 100), 'on high duration egde'); + assert.isTrue(dtmfSender.insertDTMF('1', 100, 50), 'low gap edge'); + + assert.isFalse(dtmfSender.insertDTMF('1', 69, 100), 'under duration egde'); + assert.isFalse(dtmfSender.insertDTMF('1', 6001, 100), 'over duration egde'); + assert.isFalse(dtmfSender.insertDTMF('1', 100, 49), 'under gap edge'); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: default arguments', function (done) { + this.timeout(testItemTimeout); + var e = /.*/; + assert.doesNotThrow(function(){dtmfSender.insertDTMF('1', 100);}, e, 'default gap, does not throw'); + assert.doesNotThrow(function(){dtmfSender.insertDTMF('1');}, e, 'default duration, does not throw'); + assert.throws(function(){dtmfSender.insertDTMF();}, e, 'Missing tones, throws'); + + assert.isTrue(dtmfSender.insertDTMF('1', 100), 'default gap'); + assert.isTrue(dtmfSender.insertDTMF('1'), 'default duration'); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.canInsertDTMF :: bool', function (done) { + this.timeout(testItemTimeout); + assert.isBoolean(dtmfSender.canInsertDTMF); + assert.isTrue(dtmfSender.canInsertDTMF); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.track :: audioTrack', function (done) { + this.timeout(testItemTimeout); + assert.isDefined(dtmfSender.track); + assert.isNotNull(dtmfSender.track); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.toneBuffer :: string', function (done) { + this.timeout(testItemTimeout); + assert.isString(dtmfSender.toneBuffer); + assert.equal(dtmfSender.toneBuffer, ''); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.duration :: int', function (done) { + this.timeout(testItemTimeout); + assert.isNumber(dtmfSender.duration); + done(); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.interToneGap :: int', function (done) { + this.timeout(testItemTimeout); + assert.isNumber(dtmfSender.interToneGap); + done(); + }); + +}); diff --git a/tests/unit/RTCPeerConnection.constraints.spec.js b/tests/unit/RTCPeerConnection.constraints.spec.js index 60667fc..f8cb092 100644 --- a/tests/unit/RTCPeerConnection.constraints.spec.js +++ b/tests/unit/RTCPeerConnection.constraints.spec.js @@ -14,10 +14,13 @@ var gUMTimeout = 25000; // Test item timeout var testItemTimeout = 2000; +// TODO(J-O): Where are the assertions ? Is this even testing anything ? describe('RTCPeerConnection | RTCConfiguration', function() { this.timeout(testTimeout); + var peer = null; + /* WebRTC Object should be initialized in Safari/IE Plugin */ before(function (done) { this.timeout(testItemTimeout); @@ -27,121 +30,124 @@ describe('RTCPeerConnection | RTCConfiguration', function() { }); }); - (function (constraints) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { - this.timeout(testItemTimeout); - - var peer = new RTCPeerConnection(constraints); - }); - - })({ iceServers: [] }); - - - (function (constraints) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { - this.timeout(testItemTimeout); - - var peer = new RTCPeerConnection(constraints); - }); - - })({ iceServers: [{ url: 'turn:numb.viagenie.ca', username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); - - - (function (constraints) { + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + afterEach(function(done) { + peer = null; + done(); + }); - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { + var makeDesc = function (config, constraints) { + var description = 'new RTCPeerConnection('; + if (config !== undefined) { + description += JSON.stringify(config); + } + if (constraints !== undefined) { + description += ', ' + JSON.stringify(constraints); + } + description += ')'; + + return description; + }; + + var testRTCPCContruct = function (config, constraints) { + var description = makeDesc(config, constraints); + it(description, function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + var peer = new RTCPeerConnection(config, constraints); }); + }; - })({ iceServers: [{ url: 'leticia.choo@temasys.com.sg@turn:numb.viagenie.ca', credential: 'xxxxx' }] }); - - - (function (constraints) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { + var testRTCPCContruct_skip = function (config, constraints) { + var description = makeDesc(config, constraints); + it.skip(description, function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + var peer = new RTCPeerConnection(config, constraints); }); + }; - })({ iceServers: [{ urls: ['turn:numb.viagenie.ca', 'turn:numb.viagenie.ca'], username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); - - - (function (constraints) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { + var testRTCPCContruct_throw = function (config, constraints) { + var description = makeDesc(config, constraints); + description += ' -> Throws Error'; + it(description, function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); - }); - - })({ iceServers: [{ url: 'stun:stun.l.google.com:19302' }] }); - - - (function (constraints) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { - this.timeout(testItemTimeout); - - var peer = new RTCPeerConnection(constraints); - }); - - })({ iceServers: [{ url: 'turn:numb.viagenie.ca', username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }, { url: 'stun:stun.l.google.com:19302' }] }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ bundlePolicy: 'balanced' }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ bundlePolicy: 'max-compat' }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ bundlePolicy: 'max-bundle' }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ iceTransportPolicy: 'none' }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ iceTransportPolicy: 'relay' }); - - - (function (constraints) { - - it.skip('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () {}); - - })({ iceTransportPolicy: 'all' }); - - - (function (constraints, optional) { - - it('new RTCPeerConnection(' + JSON.stringify(constraints) + ', ' + JSON.stringify(optional) + ')', function () { - this.timeout(testItemTimeout); + var fn = function() { + var peer = new RTCPeerConnection(config, constraints); + }; - var peer = new RTCPeerConnection(constraints, optional); + expect(fn).to.throw(Error); }); + }; + + // EXPECT WORKING + testRTCPCContruct(); + testRTCPCContruct(null); + testRTCPCContruct(null, []); + testRTCPCContruct(null, {}); + testRTCPCContruct(null, null); + testRTCPCContruct(null, null, null); + testRTCPCContruct(null, null, 0); + testRTCPCContruct(null, null, 1); + testRTCPCContruct(null, null, true); + testRTCPCContruct(null, null, false); + testRTCPCContruct(null, null, 'test'); + testRTCPCContruct(null, null, {}); + testRTCPCContruct(null, null, []); + testRTCPCContruct(null, null, { test: [] }); + testRTCPCContruct({ iceServers: [] }); + testRTCPCContruct({ iceServers: [{ url: 'turn:numb.viagenie.ca', username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); + testRTCPCContruct({ iceServers: [{ url: 'leticia.choo@temasys.com.sg@turn:numb.viagenie.ca', credential: 'xxxxx' }] }); + testRTCPCContruct({ iceServers: [{ urls: ['turn:numb.viagenie.ca', 'turn:numb.viagenie.ca'], username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); + testRTCPCContruct({ iceServers: [{ url: 'stun:stun.l.google.com:19302' }] }); + testRTCPCContruct({ iceServers: [{ url: 'turn:numb.viagenie.ca', username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }, { url: 'stun:stun.l.google.com:19302' }] }); + testRTCPCContruct({ iceServers: [] }, { optional: [{ DtlsSrtpKeyAgreement: true }] }); + testRTCPCContruct({ iceServers: [] }, {}); + testRTCPCContruct({ iceServers: [] }, { optional: [] }); + testRTCPCContruct({ iceServers: [] }, { optional: undefined }); + testRTCPCContruct({ iceServers: [] }, { optional: null }); + testRTCPCContruct({ iceServers: [] }, { mandatory: {} }); + testRTCPCContruct({ iceServers: [] }, { mandatory: undefined }); + testRTCPCContruct({ iceServers: [] }, { mandatory: null }); + testRTCPCContruct({ iceServers: [] }, { optional: [], mandatory: {} }); + testRTCPCContruct({ iceServers: [] }, { optional: [], mandatory: null }); + + // EXPECT THROWNIG ERROR + testRTCPCContruct_throw(1); + testRTCPCContruct_throw(0); + testRTCPCContruct_throw(true); + testRTCPCContruct_throw(false); + testRTCPCContruct_throw('test'); + testRTCPCContruct_throw({}); + testRTCPCContruct_throw({ iceServers: null }); + testRTCPCContruct_throw({ iceServers: '' }); + testRTCPCContruct_throw({ iceServers: true }); + testRTCPCContruct_throw(null, 1); + testRTCPCContruct_throw(null, 0); + testRTCPCContruct_throw(null, true); + testRTCPCContruct_throw(null, false); + testRTCPCContruct_throw(null, 'test'); + testRTCPCContruct_throw(null, { optional: 1 }); + testRTCPCContruct_throw(null, { optional: 0 }); + testRTCPCContruct_throw(null, { optional: 'test' }); + testRTCPCContruct_throw(null, { optional: true }); + testRTCPCContruct_throw(null, { optional: false }); + testRTCPCContruct_throw(null, { optional: {} }); // Should throw error as there are plugin failures + testRTCPCContruct_throw({ iceServers: [] }, { mandatory: [] }); + testRTCPCContruct_throw({ iceServers: [] }, { mandatory: 'test'}); + testRTCPCContruct_throw({ iceServers: [] }, { mandatory: 1 }); + testRTCPCContruct_throw({ iceServers: [] }, { mandatory: 0 }); + testRTCPCContruct_throw({ iceServers: [] }, { optional: 'test', mandatory: [] }); + testRTCPCContruct_throw({ iceServers: [] }, { optional: [], mandatory: 'test' }); + + // SKIP TEST + testRTCPCContruct_skip({ bundlePolicy: 'balanced' }); + testRTCPCContruct_skip({ bundlePolicy: 'max-compat' }); + testRTCPCContruct_skip({ bundlePolicy: 'max-bundle' }); + testRTCPCContruct_skip({ iceTransportPolicy: 'none' }); + testRTCPCContruct_skip({ iceTransportPolicy: 'relay' }); + testRTCPCContruct_skip({ iceTransportPolicy: 'all' }); - })({ iceServers: [] }, { optional: [{ DtlsSrtpKeyAgreement: true }] }); }); \ No newline at end of file diff --git a/tests/unit/RTCPeerConnection.event.spec.js b/tests/unit/RTCPeerConnection.event.spec.js index b9be996..1159bfb 100644 --- a/tests/unit/RTCPeerConnection.event.spec.js +++ b/tests/unit/RTCPeerConnection.event.spec.js @@ -29,7 +29,15 @@ describe('RTCPeerConnection | EventHandler', function() { this.timeout(testItemTimeout); AdapterJS.webRTCReady(function() { - done(); + window.navigator.getUserMedia({ + audio: true, + video: true + }, function (data) { + stream = data; + done(); + }, function (error) { + throw error; + }); }); }); @@ -38,21 +46,10 @@ describe('RTCPeerConnection | EventHandler', function() { this.slow(1000); this.timeout(gUMTimeout + 1000); - window.navigator.getUserMedia({ - audio: true, - video: true - - }, function (data) { - stream = data; - - peer1 = new RTCPeerConnection({ iceServers: [] }); - peer2 = new RTCPeerConnection({ iceServers: [] }); - - done(); + peer1 = new RTCPeerConnection({ iceServers: [] }); + peer2 = new RTCPeerConnection({ iceServers: [] }); - }, function (error) { - throw error; - }); + done(); }); @@ -92,7 +89,7 @@ describe('RTCPeerConnection | EventHandler', function() { } }; - peer1.onicecandidate = function () { + peer1.onicecandidate = function (event) { var candidate = event.candidate; if (candidate === null) { @@ -107,7 +104,7 @@ describe('RTCPeerConnection | EventHandler', function() { } }; - peer2.onicecandidate = function () { + peer2.onicecandidate = function (event) { var candidate = event.candidate; if (candidate === null) { @@ -134,11 +131,11 @@ describe('RTCPeerConnection | EventHandler', function() { var array2 = []; var checkdone = function() { - if ( isArrayEqual( array1, ['stable', 'have-local-offer', 'stable'] ) - && isArrayEqual( array2, ['stable', 'have-remote-offer', 'stable'] )) { + if ( isArrayEqual( array1, ['stable', 'have-local-offer', 'stable'] ) && + isArrayEqual( array2, ['stable', 'have-remote-offer', 'stable'])) { done(); } - } + }; peer1.onsignalingstatechange = function () { array1.push(peer1.signalingState); @@ -197,11 +194,11 @@ describe('RTCPeerConnection | EventHandler', function() { var array2 = []; var checkdone = function() { - if ( isArrayEqual( array1, ['new', 'checking', 'completed', 'completed'/*, 'closed'*/] ) - && isArrayEqual( array2, ['new', 'checking', 'connected'/*, 'completed', 'closed'*/] )) { + if ( isArrayEqual( array1, ['new', 'checking', 'completed', 'completed'/*, 'closed'*/] ) && + isArrayEqual( array2, ['new', 'checking', 'connected'/*, 'completed', 'closed'*/] )) { done(); } - } + }; peer1.oniceconnectionstatechange = function () { array1.push(peer1.iceConnectionState); @@ -233,11 +230,11 @@ describe('RTCPeerConnection | EventHandler', function() { assert.deepEqual(array1, ['new', 'gathering', 'complete']); assert.deepEqual(array2, ['new', 'gathering', 'complete']); - if ( isArrayEqual( array1, ['new', 'gathering', 'complete'] ) - && isArrayEqual( array2, ['new', 'gathering', 'complete'] )) { + if ( isArrayEqual( array1, ['new', 'gathering', 'complete'] ) && + isArrayEqual( array2, ['new', 'gathering', 'complete'] )) { done(); } - } + }; peer1.onicegatheringstatechange = function () { array1.push(peer1.iceGatheringState); diff --git a/tests/unit/RTCPeerConnection.offerconstraints.spec.js b/tests/unit/RTCPeerConnection.offerconstraints.spec.js index e4b8d6f..38b7623 100644 --- a/tests/unit/RTCPeerConnection.offerconstraints.spec.js +++ b/tests/unit/RTCPeerConnection.offerconstraints.spec.js @@ -52,7 +52,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { iceServers: [] }); - peer1.addStream(stream); + peer2.addStream(stream); done(); }); @@ -76,7 +76,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(0); @@ -96,10 +96,10 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; - expect(remoteStream.getAudioTracks()).to.have.length(1); + expect(remoteStream.getAudioTracks()).to.have.length(0); expect(remoteStream.getVideoTracks()).to.have.length(1); done(); @@ -116,7 +116,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(1); @@ -136,7 +136,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(1); @@ -156,7 +156,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(1); @@ -176,7 +176,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(0); @@ -196,7 +196,7 @@ describe('RTCPeerConnection.createOffer | RTCOfferOptions', function() { it('RTCPeerConnection.createOffer(successCb, failureCb, ' + JSON.stringify(constraints) + ')', function (done) { this.timeout(testItemTimeout); - peer2.onaddstream = function (event) { + peer1.onaddstream = function (event) { var remoteStream = event.stream || event; expect(remoteStream.getAudioTracks()).to.have.length(1); diff --git a/tests/unit/RTCPeerConnection.prop.spec.js b/tests/unit/RTCPeerConnection.prop.spec.js index 2d6ec3d..00b7933 100644 --- a/tests/unit/RTCPeerConnection.prop.spec.js +++ b/tests/unit/RTCPeerConnection.prop.spec.js @@ -241,20 +241,14 @@ describe('RTCPeerConnection | Properties', function() { assert.equal(typeof peer1.getStreamById, FUNCTION_TYPE); assert.equal(typeof peer2.getStreamById, FUNCTION_TYPE); - var result1 = peer1.getStreamById(stream.id); - var result2 = peer2.getStreamById(stream.id); - - expect(result1).to.be.null; - expect(result2).to.be.null; + assert.isNull(peer1.getStreamById(stream.id)); + assert.isNull(peer2.getStreamById(stream.id)); peer1.addStream(stream); peer2.addStream(stream); - var result1 = peer1.getStreamById(stream.id); - var result2 = peer2.getStreamById(stream.id); - - expect(result1).to.equal(stream); - expect(result2).to.equal(stream); + expect(peer1.getStreamById(stream.id)).to.equal(stream); + expect(peer2.getStreamById(stream.id)).to.equal(stream); done(); }); diff --git a/tests/unit/VideoElement.event.spec.js b/tests/unit/VideoElement.event.spec.js index 7c4561d..aaf076b 100644 --- a/tests/unit/VideoElement.event.spec.js +++ b/tests/unit/VideoElement.event.spec.js @@ -68,7 +68,7 @@ describe('VideoElement | EventHandler', function() { video.onplaying = function(event) { done(); - } + }; video = attachMediaStream(video, stream); }); @@ -81,10 +81,10 @@ describe('VideoElement | EventHandler', function() { var now = new Date().getTime(); video.onplaying = function(event) { - expect(event.target).not.to.be.undefined; - expect(event.currentTarget).not.to.be.undefined; - expect(event.srcElement).not.to.be.undefined; - expect(event.timeStamp).not.to.be.undefined; + assert.isDefined(event.target); + assert.isDefined(event.currentTarget); + assert.isDefined(event.srcElement); + assert.isDefined(event.timeStamp); expect(event.timeStamp).to.be.above(0); expect(event.timeStamp).to.be.within(now - timeStampMaxError, now + timeStampMaxError); @@ -105,12 +105,12 @@ describe('VideoElement | EventHandler', function() { var expectedOnplayCaught = 2; video.onplay = function() { - if(++onPlayCaught == expectedOnplayCaught) { + if(++onPlayCaught === expectedOnplayCaught) { done(); } video.pause(); video.play(); - } + }; video = attachMediaStream(video, stream); }); @@ -123,10 +123,10 @@ describe('VideoElement | EventHandler', function() { var now = new Date().getTime(); video.onplay = function(event) { - expect(event.target).not.to.be.undefined; - expect(event.currentTarget).not.to.be.undefined; - expect(event.srcElement).not.to.be.undefined; - expect(event.timeStamp).not.to.be.undefined; + assert.isDefined(event.target); + assert.isDefined(event.currentTarget); + assert.isDefined(event.srcElement); + assert.isDefined(event.timeStamp); expect(event.timeStamp).to.be.above(0); expect(event.timeStamp).to.be.within(now - timeStampMaxError, now + timeStampMaxError); @@ -161,10 +161,10 @@ it('VideoElement.onloadedmetadata :: emit', function (done) { var now = new Date().getTime(); video.onloadedmetadata = function(event) { - expect(event.target).not.to.be.undefined; - expect(event.currentTarget).not.to.be.undefined; - expect(event.srcElement).not.to.be.undefined; - expect(event.timeStamp).not.to.be.undefined; + assert.isDefined(event.target); + assert.isDefined(event.currentTarget); + assert.isDefined(event.srcElement); + assert.isDefined(event.timeStamp); expect(event.timeStamp).to.be.above(0); expect(event.timeStamp).to.be.within(now - timeStampMaxError, now + timeStampMaxError); diff --git a/third_party/adapter b/third_party/adapter index b2c9d00..10ba31e 160000 --- a/third_party/adapter +++ b/third_party/adapter @@ -1 +1 @@ -Subproject commit b2c9d00ccdd0bbe1e39cda24a75aae0bea163474 +Subproject commit 10ba31ea2d874eeee51aba45e6eacced6fc56636