From 3036180690e83289c4173bd1947cab7c4e47a9f8 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 21 Jan 2016 16:40:00 +0800 Subject: [PATCH 01/63] TWP-371: added prop tests for dtmf --- tests/globals.js | 6 +- tests/unit/RTCDTMFSender.prop.spec.js | 122 ++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 tests/unit/RTCDTMFSender.prop.spec.js diff --git a/tests/globals.js b/tests/globals.js index d9d7ee4..3fb4f58 100644 --- a/tests/globals.js +++ b/tests/globals.js @@ -207,4 +207,8 @@ var connect = function (peer1, peer2, offerConstraints) { 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/unit/RTCDTMFSender.prop.spec.js b/tests/unit/RTCDTMFSender.prop.spec.js new file mode 100644 index 0000000..802ceb7 --- /dev/null +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -0,0 +1,122 @@ +var expect = chai.expect; +var assert = chai.assert; +var should = chai.should; + +// Test timeouts +var testTimeout = 35000; + +// Get User Media timeout +var gUMTimeout = 25000; + +// Test item timeout +var testItemTimeout = 2000; + +describe('RTCDTMFSender', 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(testItemTimeout); + + pc1 = new RTCPeerConnection({ iceServers: [] }); + pc2 = new RTCPeerConnection({ iceServers: [] }); + pc1.oniceconnectionstatechange = function (state) { + dtmfSender = pc1.createDTMFSender(audioTrack); + if(pc1.iceConnectionState === 'connected') { + done(); + } + }; + pc1.addStream(stream); + connect(pc1, pc2); + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + afterEach(function() { + pc1.close(); + pc2.close(); + pc1 = null; + pc2 = null; + dtmfSender = null; + }); + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + it('RTCDTMFSender.insertDTMF :: function', function (done) { + this.timeout(testItemTimeout); + assert.equal(typeof dtmfSender.insertDTMF, FUNCTION_TYPE); + 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(); + }); + +}); From 6da3fa20a73b9da7fecfc60972e2873a0ee8b5fa Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 21 Jan 2016 17:05:10 +0800 Subject: [PATCH 02/63] TWP-371: more tests --- tests/unit/RTCDTMFSender.prop.spec.js | 47 ++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/unit/RTCDTMFSender.prop.spec.js b/tests/unit/RTCDTMFSender.prop.spec.js index 802ceb7..1ff2aed 100644 --- a/tests/unit/RTCDTMFSender.prop.spec.js +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -74,6 +74,52 @@ describe('RTCDTMFSender', function() { 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 = 'Error calling method on NPObject.'; + 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(); + }); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// @@ -84,7 +130,6 @@ describe('RTCDTMFSender', function() { done(); }); - ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// it('RTCDTMFSender.track :: audioTrack', function (done) { From 7f21e8544a1e63fdbd68be6b6dab52c94019fc71 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 21 Jan 2016 17:21:44 +0800 Subject: [PATCH 03/63] TWP-371 refactoring --- tests/unit/RTCDTMFSender.prop.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/RTCDTMFSender.prop.spec.js b/tests/unit/RTCDTMFSender.prop.spec.js index 1ff2aed..99a3fd5 100644 --- a/tests/unit/RTCDTMFSender.prop.spec.js +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -111,11 +111,11 @@ describe('RTCDTMFSender', function() { ///////////////////////////////////////////////////////////////////// it('RTCDTMFSender.insertDTMF :: default arguments', function (done) { this.timeout(testItemTimeout); - var e = 'Error calling method on NPObject.'; - 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'); - + 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(); From 4757ef5e1679670dd4beb02ea46796787e7ed287 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 21 Jan 2016 17:58:08 +0800 Subject: [PATCH 04/63] TWP-371: more DTMFSender tests --- tests/unit/RTCDTMFSender.event.spec.js | 90 ++++++++++++++++++++++++++ tests/unit/RTCDTMFSender.prop.spec.js | 11 ++-- 2 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/unit/RTCDTMFSender.event.spec.js diff --git a/tests/unit/RTCDTMFSender.event.spec.js b/tests/unit/RTCDTMFSender.event.spec.js new file mode 100644 index 0000000..32b8abe --- /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(testItemTimeout); + + pc1 = new RTCPeerConnection({ iceServers: [] }); + pc2 = new RTCPeerConnection({ iceServers: [] }); + pc1.oniceconnectionstatechange = function (state) { + if(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 index 99a3fd5..8b4e4a9 100644 --- a/tests/unit/RTCDTMFSender.prop.spec.js +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -6,12 +6,12 @@ var should = chai.should; var testTimeout = 35000; // Get User Media timeout -var gUMTimeout = 25000; +var gUMTimeout = 5000; // Test item timeout var testItemTimeout = 2000; -describe('RTCDTMFSender', function() { +describe('RTCDTMFSender | prop', function() { this.timeout(testTimeout); var pc1 = null; @@ -25,6 +25,8 @@ describe('RTCDTMFSender', function() { before(function (done) { this.timeout(testItemTimeout); + assert.isNotNull(AdapterJS.WebRTCPlugin.plugin); + AdapterJS.webRTCReady(function() { window.navigator.getUserMedia({ audio: true, @@ -47,8 +49,8 @@ describe('RTCDTMFSender', function() { pc1 = new RTCPeerConnection({ iceServers: [] }); pc2 = new RTCPeerConnection({ iceServers: [] }); pc1.oniceconnectionstatechange = function (state) { - dtmfSender = pc1.createDTMFSender(audioTrack); if(pc1.iceConnectionState === 'connected') { + dtmfSender = pc1.createDTMFSender(audioTrack); done(); } }; @@ -58,12 +60,13 @@ describe('RTCDTMFSender', function() { ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// - afterEach(function() { + afterEach(function(done) { pc1.close(); pc2.close(); pc1 = null; pc2 = null; dtmfSender = null; + done(); }); ///////////////////////////////////////////////////////////////////// From bfa5cdefb7aefb8deb068585ad77070f34f4ec9c Mon Sep 17 00:00:00 2001 From: johache Date: Fri, 22 Jan 2016 17:12:14 +0800 Subject: [PATCH 05/63] One includeReplace target per includeDir (include replace doesn't support multiple include dirs) --- Gruntfile.js | 19 ++++++++++++++++--- source/adapter.js | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 61060a3..3a93c25 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -115,10 +115,10 @@ 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) { @@ -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: { diff --git a/source/adapter.js b/source/adapter.js index b023cbd..6ccde54 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -56,7 +56,7 @@ AdapterJS.webRTCReady = function (callback) { AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; // The object to store plugin information -@@include('source/pluginInfo.js', {}) +@Tem@include('pluginInfo.js', {}) AdapterJS.WebRTCPlugin.TAGS = { NONE : 'none', @@ -541,7 +541,7 @@ if ( navigator.mozGetUserMedia /////////////////////////////////////////////////////////////////// // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT -@@include('third_party/adapter/adapter.js', {}) +@Goo@include('third_party/adapter/adapter.js', {}) // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// From 0b5d00c1c15c33c7f25a69abfab508d396550724 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 2 Feb 2016 12:40:29 +0800 Subject: [PATCH 06/63] AJS-242: WIP updated createIceServer for FF43. Does not support retro-compatibility yet --- source/adapter.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 6ccde54..b6e0022 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -576,23 +576,23 @@ if ( navigator.mozGetUserMedia 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) { - 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 turn_urlParts = url.split('?'); + if (turn_urlParts.length === 1 || + turn_urlParts[1].indexOf('transport=udp') === 0) { iceServer = { - url : turn_url_parts[0], + urls : [turn_urlParts[0]], credential : password, username : username }; } } else { iceServer = { - url : url, + urls : [url], credential : password, username : username }; @@ -618,10 +618,10 @@ if ( navigator.mozGetUserMedia 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, @@ -875,13 +875,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, From d1338ce4a58b8ea323fb9abdb80751423b849ec0 Mon Sep 17 00:00:00 2001 From: johache Date: Wed, 10 Feb 2016 17:17:18 +0800 Subject: [PATCH 07/63] TWP-396: Splitting new and old constructors for RTCPeerConnection --- source/adapter.js | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 6ccde54..8e0d426 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -907,27 +907,34 @@ 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; + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + + 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; + 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); } - 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); } - 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); }; MediaStreamTrack = {}; From 40d2a3d9cedd681c9fccf882313a42bff19c1e4c Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 11 Feb 2016 14:22:20 +0800 Subject: [PATCH 08/63] Corrected tests on createOffer --- .../RTCPeerConnection.offerconstraints.spec.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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); From c35587b2630c88f1c02254bfb76563fd427182da Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 11 Feb 2016 16:48:10 +0800 Subject: [PATCH 09/63] Github-152: refactored parseWebrtcDetectedBrowser and calling it after Google's AJS include to have the values set properly on Chrome and FF --- source/adapter.js | 77 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 6ccde54..4fc005b 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -143,8 +143,6 @@ 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(); @@ -228,45 +226,43 @@ AdapterJS.isDefined = null; // - '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) || []; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + var 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'; + var 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'; + var 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 (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) { } + } 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 } }; @@ -546,6 +542,8 @@ if ( navigator.mozGetUserMedia // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// + AdapterJS.parseWebrtcDetectedBrowser(); + /////////////////////////////////////////////////////////////////// // EXTENSION FOR CHROME, FIREFOX AND EDGE // Includes legacy functions @@ -737,7 +735,6 @@ if ( navigator.mozGetUserMedia console.groupEnd = function (arg) {}; /* jshint +W020 */ } - webrtcDetectedType = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; From 45e17331e3d16429bdc2c96095676b357f0c5379 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 11 Feb 2016 17:47:52 +0800 Subject: [PATCH 10/63] AJS-242: Refactor + comment --- source/adapter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index b6e0022..f9e22e5 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -574,16 +574,16 @@ 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 urlParts = url.split(':'); if (urlParts[0].indexOf('stun') === 0) { iceServer = { urls : [url] }; } else if (urlParts[0].indexOf('turn') === 0) { if (webrtcDetectedVersion < 27) { - var turn_urlParts = url.split('?'); - if (turn_urlParts.length === 1 || - turn_urlParts[1].indexOf('transport=udp') === 0) { + var turnUrlParts = url.split('?'); + if (turnUrlParts.length === 1 || + turnUrlParts[1].indexOf('transport=udp') === 0) { iceServer = { urls : [turn_urlParts[0]], credential : password, From f7a4271ab124bbb5b3260bfbb941f6fae4682ad1 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 15 Feb 2016 15:08:07 +0800 Subject: [PATCH 11/63] Refactor: AdapterJS.WebRTCPlugin.documentReadyInterval refactored in a local variable --- source/adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 6ccde54..246bb9b 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -149,10 +149,10 @@ __TemWebRTCReady0 = function () { 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(); } From 6214ca27f9ed7e46bf721e80d682983c428cb31f Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 16 Feb 2016 14:01:27 +0800 Subject: [PATCH 12/63] Removed `grunt dev`. Use `grunt publish` --- Gruntfile.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3a93c25..beed68a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -303,16 +303,6 @@ module.exports = function(grunt) { } }); - grunt.registerTask('dev', [ - 'CheckPluginInfo', - 'versionise', - 'clean:production', - 'concat', - 'replace:production', - 'includereplace:production', - 'uglify' - ]); - grunt.registerTask('publish', [ 'CheckPluginInfo', 'versionise', From fc0167fee50de4d828f17895eb689f2c9ceaf59e Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 16 Feb 2016 14:11:15 +0800 Subject: [PATCH 13/63] #152: added webrtcMinimumVersion for Safari, IE and Opera --- source/adapter.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/adapter.js b/source/adapter.js index 4fc005b..72f08a8 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -221,6 +221,7 @@ 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. @@ -232,6 +233,7 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { // Opera 8.0+ webrtcDetectedBrowser = 'opera'; webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; var hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1], 10); } else if (typeof InstallTrigger !== 'undefined') { @@ -242,12 +244,14 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { // Safari webrtcDetectedBrowser = 'safari'; webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; var 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; var hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } else if (!!window.StyleMedia) { @@ -528,6 +532,9 @@ webrtcDetectedBrowser = null; // Detected browser version. webrtcDetectedVersion = null; +// The minimum browser version still supported by AJS. +webrtcMinimumVersion = null; + // Check for browser types and react accordingly if ( navigator.mozGetUserMedia || navigator.webkitGetUserMedia From 2c368275fb2a93735d5b06b694ca512454cb6bf7 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 16 Feb 2016 16:27:25 +0800 Subject: [PATCH 14/63] Missing argument in test --- tests/unit/RTCPeerConnection.event.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/RTCPeerConnection.event.spec.js b/tests/unit/RTCPeerConnection.event.spec.js index b9be996..d941dc5 100644 --- a/tests/unit/RTCPeerConnection.event.spec.js +++ b/tests/unit/RTCPeerConnection.event.spec.js @@ -92,7 +92,7 @@ describe('RTCPeerConnection | EventHandler', function() { } }; - peer1.onicecandidate = function () { + peer1.onicecandidate = function (event) { var candidate = event.candidate; if (candidate === null) { @@ -107,7 +107,7 @@ describe('RTCPeerConnection | EventHandler', function() { } }; - peer2.onicecandidate = function () { + peer2.onicecandidate = function (event) { var candidate = event.candidate; if (candidate === null) { From aa1026d8f6b8bccfbd4da1e5b1c42d1046cca9fa Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 16 Feb 2016 17:32:35 +0800 Subject: [PATCH 15/63] #152: Handling userAgent on IE < 11 --- source/adapter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/adapter.js b/source/adapter.js index 72f08a8..e44d264 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -254,6 +254,10 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { webrtcMinimumVersion = 9; var hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + if (!webrtcDetectedVersion) { + var 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 From 1717db269dc579574bec83e2703c0ed2432faf16 Mon Sep 17 00:00:00 2001 From: johache Date: Wed, 17 Feb 2016 14:05:23 +0800 Subject: [PATCH 16/63] AJS-243: updated dependency on webrtc-adapter --- Gruntfile.js | 16 +++++++++++++++- source/adapter.js | 2 +- third_party/adapter | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3a93c25..dd1fda0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,7 +19,7 @@ module.exports = function(grunt) { source: 'source', - googleAdapterPath: 'third_party/adapter/adapter.js', + googleAdapterPath: 'third_party/adapter/out/adapter.js', production: 'publish', @@ -303,6 +303,19 @@ module.exports = function(grunt) { } }); + 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('dev', [ 'CheckPluginInfo', 'versionise', @@ -315,6 +328,7 @@ module.exports = function(grunt) { grunt.registerTask('publish', [ 'CheckPluginInfo', + 'webrtc-adapter', 'versionise', 'clean:production', 'concat', diff --git a/source/adapter.js b/source/adapter.js index 246bb9b..f75311f 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -541,7 +541,7 @@ if ( navigator.mozGetUserMedia /////////////////////////////////////////////////////////////////// // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT -@Goo@include('third_party/adapter/adapter.js', {}) +@Goo@include('third_party/adapter/out/adapter.js', {}) // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// diff --git a/third_party/adapter b/third_party/adapter index b2c9d00..1ca170f 160000 --- a/third_party/adapter +++ b/third_party/adapter @@ -1 +1 @@ -Subproject commit b2c9d00ccdd0bbe1e39cda24a75aae0bea163474 +Subproject commit 1ca170f10e5dfe0fa274fe3091687060e505cf22 From 2ce15bb943846e7337bd18e51d2f192b1895baa1 Mon Sep 17 00:00:00 2001 From: johache Date: Wed, 17 Feb 2016 14:48:17 +0800 Subject: [PATCH 17/63] AJS-243: downgrading to webrtc-adapter 0.2.9 --- Gruntfile.js | 5 +++-- source/adapter.js | 4 ++-- third_party/adapter | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index dd1fda0..c99a1ed 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,7 +19,7 @@ module.exports = function(grunt) { source: 'source', - googleAdapterPath: 'third_party/adapter/out/adapter.js', + googleAdapterPath: 'third_party/adapter/adapter.js', production: 'publish', @@ -303,6 +303,7 @@ module.exports = function(grunt) { } }); + // 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(); @@ -328,7 +329,7 @@ module.exports = function(grunt) { grunt.registerTask('publish', [ 'CheckPluginInfo', - 'webrtc-adapter', + // 'webrtc-adapter', 'versionise', 'clean:production', 'concat', diff --git a/source/adapter.js b/source/adapter.js index f75311f..bf26a18 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -541,7 +541,7 @@ if ( navigator.mozGetUserMedia /////////////////////////////////////////////////////////////////// // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT -@Goo@include('third_party/adapter/out/adapter.js', {}) +@Goo@include('third_party/adapter/adapter.js', {}) // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// @@ -951,7 +951,7 @@ if ( navigator.mozGetUserMedia // Defined mediaDevices when promises are available if ( !navigator.mediaDevices && typeof Promise !== 'undefined') { - navigator.mediaDevices = {getUserMedia: requestUserMedia, + navigator.mediaDevices = { //getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { var kinds = {audio: 'audioinput', video: 'videoinput'}; diff --git a/third_party/adapter b/third_party/adapter index 1ca170f..10ba31e 160000 --- a/third_party/adapter +++ b/third_party/adapter @@ -1 +1 @@ -Subproject commit 1ca170f10e5dfe0fa274fe3091687060e505cf22 +Subproject commit 10ba31ea2d874eeee51aba45e6eacced6fc56636 From cdbd7912e9973a4c8c8f7218f070a14252f29372 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 11:12:15 +0800 Subject: [PATCH 18/63] Removed a tone of unused files --- source/adapter.MediaStream.js | 696 --------------------------- source/adapter.MediaStreamTrack.js | 322 ------------- source/adapter.RTCPeerConnection.js | 630 ------------------------ source/adapter.plugin.js | 254 ---------- source/adapter.plugin.rtc.adapter.js | 409 ---------------- source/adapter.screenshare.js | 217 --------- source/adapter.utils.js | 341 ------------- 7 files changed, 2869 deletions(-) delete mode 100644 source/adapter.MediaStream.js delete mode 100644 source/adapter.MediaStreamTrack.js delete mode 100644 source/adapter.RTCPeerConnection.js delete mode 100644 source/adapter.plugin.js delete mode 100644 source/adapter.plugin.rtc.adapter.js delete mode 100644 source/adapter.screenshare.js delete mode 100644 source/adapter.utils.js 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.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 3128a4b..0000000 --- a/source/adapter.plugin.rtc.adapter.js +++ /dev/null @@ -1,409 +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.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); -} \ No newline at end of file diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js deleted file mode 100644 index 0f80647..0000000 --- a/source/adapter.screenshare.js +++ /dev/null @@ -1,217 +0,0 @@ -(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) { - if (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) { - // intercepting screensharing requests - - // Invalid mediaSource for firefox, only "screen" and "window" are supported - if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') { - failureCb(new Error('GetUserMedia: Only "screen" and "window" are supported as mediaSource constraints')); - return; - } - - var updatedConstraints = clone(constraints); - - //constraints.video.mediaSource = constraints.video.mediaSource; - updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource; - - // so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked. - // strange but this works anyway - var checkIfReady = setInterval(function () { - if (document.readyState === 'complete') { - clearInterval(checkIfReady); - - baseGetUserMedia(updatedConstraints, successCb, function (error) { - if (error.name === 'PermissionDeniedError' && 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; - } else { - failureCb(error); - } - }); - } - }, 1); - - } else { // regular GetUserMediaRequest - baseGetUserMedia(constraints, successCb, failureCb); - } - }; - - getUserMedia = navigator.getUserMedia; - - } else if (window.navigator.webkitGetUserMedia) { - baseGetUserMedia = window.navigator.getUserMedia; - - navigator.getUserMedia = function (constraints, successCb, failureCb) { - if (constraints && constraints.video && !!constraints.video.mediaSource) { - if (window.webrtcDetectedBrowser !== 'chrome') { - // This is Opera, which does not support screensharing - failureCb(new Error('Current browser does not support screensharing')); - return; - } - - // would be fine since no methods - var updatedConstraints = clone(constraints); - - var chromeCallback = function(error, sourceId) { - if(!error) { - 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; - - if (sourceId) { - updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId; - } - - delete updatedConstraints.video.mediaSource; - - baseGetUserMedia(updatedConstraints, successCb, failureCb); - - } else { // GUM failed - if (error === 'permission-denied') { - failureCb(new Error('Permission denied for screen retrieval')); - } else { - // NOTE(J-O): I don't think we ever pass in here. - // A failure to capture the screen does not lead here. - failureCb(new Error('Failed retrieving selected screen')); - } - } - }; - - var onIFrameCallback = function (event) { - if (!event.data) { - return; - } - - if (event.data.chromeMediaSourceId) { - if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { - chromeCallback('permission-denied'); - } else { - chromeCallback(null, event.data.chromeMediaSourceId); - } - } - - if (event.data.chromeExtensionStatus) { - if (event.data.chromeExtensionStatus === 'not-installed') { - AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME, - AdapterJS.TEXT.EXTENSION.BUTTON_CHROME, - event.data.data, true, true); - } else { - chromeCallback(event.data.chromeExtensionStatus, null); - } - } - - // this event listener is no more needed - window.removeEventListener('message', onIFrameCallback); - }; - - window.addEventListener('message', onIFrameCallback); - - postFrameMessage({ - captureSourceId: true - }); - - } else { - baseGetUserMedia(constraints, successCb, failureCb); - } - }; - - getUserMedia = navigator.getUserMedia; - - } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { - // nothing here because edge does not support screensharing - console.warn('Edge does not support screensharing feature in getUserMedia'); - - } else { - baseGetUserMedia = window.navigator.getUserMedia; - - navigator.getUserMedia = function (constraints, successCb, failureCb) { - if (constraints && constraints.video && !!constraints.video.mediaSource) { - // would be fine since no methods - var updatedConstraints = clone(constraints); - - // wait for plugin to be ready - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - // check if screensharing feature is available - if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature && - !!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) { - // set the constraints - updatedConstraints.video.optional = updatedConstraints.video.optional || []; - updatedConstraints.video.optional.push({ - sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing' - }); - - delete updatedConstraints.video.mediaSource; - } else { - failureCb(new Error('Your version of the WebRTC plugin does not support screensharing')); - return; - } - baseGetUserMedia(updatedConstraints, successCb, failureCb); - }); - } else { - baseGetUserMedia(constraints, successCb, failureCb); - } - }; - - getUserMedia = window.navigator.getUserMedia; - } - - // For chrome, use an iframe to load the screensharing extension - // in the correct domain. - // Modify here for custom screensharing extension in chrome - if (window.webrtcDetectedBrowser === 'chrome') { - var iframe = document.createElement('iframe'); - - iframe.onload = function() { - iframe.isLoaded = true; - }; - - 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) { - object = object || {}; - - if (!iframe.isLoaded) { - setTimeout(function () { - iframe.contentWindow.postMessage(object, '*'); - }, 100); - return; - } - - iframe.contentWindow.postMessage(object, '*'); - }; - } 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 From 6006a015ac7b05056b00bd14aa6f84718e90caf0 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 11:13:12 +0800 Subject: [PATCH 19/63] Fixed a bunch if things bothering jshint --- Gruntfile.js | 2 +- source/adapter.js | 54 +++++++++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index beed68a..4112671 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -121,7 +121,7 @@ module.exports = function(grunt) { 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 diff --git a/source/adapter.js b/source/adapter.js index bd9ee80..8cca349 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -227,6 +227,7 @@ AdapterJS.isDefined = null; // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. AdapterJS.parseWebrtcDetectedBrowser = function () { + var hasMatch = null; if ((!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { @@ -234,7 +235,7 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { webrtcDetectedBrowser = 'opera'; webrtcDetectedType = 'webkit'; webrtcMinimumVersion = 26; - var hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1], 10); } else if (typeof InstallTrigger !== 'undefined') { // Firefox 1.0+ @@ -245,17 +246,17 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { webrtcDetectedBrowser = 'safari'; webrtcDetectedType = 'plugin'; webrtcMinimumVersion = 7; - var hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + 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; - var hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); if (!webrtcDetectedVersion) { - var hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } } else if (!!window.StyleMedia) { @@ -540,10 +541,10 @@ webrtcDetectedVersion = null; webrtcMinimumVersion = null; // Check for browser types and react accordingly -if ( navigator.mozGetUserMedia - || navigator.webkitGetUserMedia - || (navigator.mediaDevices - && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) ) { +if ( navigator.mozGetUserMedia || + navigator.webkitGetUserMedia || + (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) ) { /////////////////////////////////////////////////////////////////// // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT @@ -871,7 +872,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; } @@ -957,8 +958,8 @@ if ( navigator.mozGetUserMedia window.navigator.getUserMedia = window.getUserMedia; // Defined mediaDevices when promises are available - if ( !navigator.mediaDevices - && typeof Promise !== 'undefined') { + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { navigator.mediaDevices = {getUserMedia: requestUserMedia, enumerateDevices: function() { return new Promise(function(resolve) { @@ -1077,30 +1078,29 @@ if ( navigator.mozGetUserMedia }; 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]); + for(var prop in properties) { + if (prop) { + 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 { - destElem.addEventListener(propName.slice(2), srcElem[propName], false) + //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? } - } else { - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? } } } - - 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) { From a27d6dedf95420ba548b57428921a680b349bcdf Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 11:22:39 +0800 Subject: [PATCH 20/63] jshint ignore include-replace --- source/adapter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/adapter.js b/source/adapter.js index 8cca349..9be5bea 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 +/* jshint ignore:start */ @Tem@include('pluginInfo.js', {}) +/* jshint ignore:end */ AdapterJS.WebRTCPlugin.TAGS = { NONE : 'none', @@ -549,7 +551,9 @@ if ( navigator.mozGetUserMedia || /////////////////////////////////////////////////////////////////// // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT +/* jshint ignore:start */ @Goo@include('third_party/adapter/adapter.js', {}) +/* jshint ignore:end */ // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT /////////////////////////////////////////////////////////////////// From e055df170067d260266a56dc482185a4a490dbc4 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 11:22:56 +0800 Subject: [PATCH 21/63] Removed unused code --- source/adapter.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 9be5bea..6c60204 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -301,7 +301,7 @@ AdapterJS.addEvent = function(elem, evnt, func) { } }; -AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { +AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab) { // only inject once the page is ready if (document.readyState !== 'complete') { return; @@ -337,11 +337,6 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe // On click on okay AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { - if (!!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(); From 13ef8b4259a59c8d327ddc3054379d2c426abdd4 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 12:05:17 +0800 Subject: [PATCH 22/63] jshint checking out tests, and jshint errors corrected --- Gruntfile.js | 3 +- source/adapter.js | 3 +- tests/globals.js | 31 +++++++++---------- tests/karma.conf.js | 4 +-- tests/unit/MediaStream.prop.spec.js | 2 +- tests/unit/MediaStreamTrack.prop.spec.js | 1 - .../unit/Plugin.ScreenSaver.behaviour.spec.js | 2 +- tests/unit/Plugin.object.stability.spec.js | 2 +- tests/unit/RTCDTMFSender.event.spec.js | 6 ++-- tests/unit/RTCDTMFSender.prop.spec.js | 8 ++--- tests/unit/RTCPeerConnection.event.spec.js | 18 +++++------ tests/unit/RTCPeerConnection.prop.spec.js | 14 +++------ tests/unit/VideoElement.event.spec.js | 30 +++++++++--------- 13 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 4112671..f129541 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -172,7 +172,8 @@ module.exports = function(grunt) { node: true }, grunt.file.readJSON('.jshintrc')), src: [ - 'tests/*_test.js' + 'tests/*.js', + 'tests/unit/*.js' ] }, app: { diff --git a/source/adapter.js b/source/adapter.js index 6c60204..d825cfd 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -1089,9 +1089,8 @@ if ( navigator.mozGetUserMedia || } else { destElem.addEventListener(propName.slice(2), srcElem[propName], false); } - } else { - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? } + //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? } } } diff --git a/tests/globals.js b/tests/globals.js index 3fb4f58..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,17 +202,6 @@ 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; diff --git a/tests/karma.conf.js b/tests/karma.conf.js index 7263fe8..60994dc 100644 --- a/tests/karma.conf.js +++ b/tests/karma.conf.js @@ -94,5 +94,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..062c95d 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(); }); }); }); 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.object.stability.spec.js b/tests/unit/Plugin.object.stability.spec.js index 18291a7..bef4a90 100644 --- a/tests/unit/Plugin.object.stability.spec.js +++ b/tests/unit/Plugin.object.stability.spec.js @@ -77,7 +77,7 @@ if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') { clearTimeout(timeout); done(); } - } + }; replaceVideoElement(); }); diff --git a/tests/unit/RTCDTMFSender.event.spec.js b/tests/unit/RTCDTMFSender.event.spec.js index 32b8abe..ea0e326 100644 --- a/tests/unit/RTCDTMFSender.event.spec.js +++ b/tests/unit/RTCDTMFSender.event.spec.js @@ -42,12 +42,12 @@ describe('RTCDTMFSender | event', function() { ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// beforeEach(function (done) { - this.timeout(testItemTimeout); + this.timeout(gUMTimeout); pc1 = new RTCPeerConnection({ iceServers: [] }); pc2 = new RTCPeerConnection({ iceServers: [] }); - pc1.oniceconnectionstatechange = function (state) { - if(pc1.iceConnectionState === 'connected') { + pc1.oniceconnectionstatechange = function (evt) { + if(pc1 && pc1.iceConnectionState === 'connected') { dtmfSender = pc1.createDTMFSender(audioTrack); done(); } diff --git a/tests/unit/RTCDTMFSender.prop.spec.js b/tests/unit/RTCDTMFSender.prop.spec.js index 8b4e4a9..ca988aa 100644 --- a/tests/unit/RTCDTMFSender.prop.spec.js +++ b/tests/unit/RTCDTMFSender.prop.spec.js @@ -25,8 +25,6 @@ describe('RTCDTMFSender | prop', function() { before(function (done) { this.timeout(testItemTimeout); - assert.isNotNull(AdapterJS.WebRTCPlugin.plugin); - AdapterJS.webRTCReady(function() { window.navigator.getUserMedia({ audio: true, @@ -44,12 +42,12 @@ describe('RTCDTMFSender | prop', function() { ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// beforeEach(function (done) { - this.timeout(testItemTimeout); + this.timeout(gUMTimeout); pc1 = new RTCPeerConnection({ iceServers: [] }); pc2 = new RTCPeerConnection({ iceServers: [] }); - pc1.oniceconnectionstatechange = function (state) { - if(pc1.iceConnectionState === 'connected') { + pc1.oniceconnectionstatechange = function (evt) { + if(pc1 && pc1.iceConnectionState === 'connected') { dtmfSender = pc1.createDTMFSender(audioTrack); done(); } diff --git a/tests/unit/RTCPeerConnection.event.spec.js b/tests/unit/RTCPeerConnection.event.spec.js index d941dc5..b593475 100644 --- a/tests/unit/RTCPeerConnection.event.spec.js +++ b/tests/unit/RTCPeerConnection.event.spec.js @@ -134,11 +134,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 +197,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 +233,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.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); From 4a69ba5380ef030f23665fc590c2200678b78560 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 18 Feb 2016 15:16:23 +0800 Subject: [PATCH 23/63] Removed unused method maybeFixConfguration. Logic is duplicated in webrtc-adapter --- source/adapter.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 0b14962..b71112a 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -274,20 +274,6 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { } }; -// 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; - } - } -}; - AdapterJS.addEvent = function(elem, evnt, func) { if (elem.addEventListener) { // W3C DOM elem.addEventListener(evnt, func, false); From 7cf71db2ce2776a5601ffdbff5230f7d5420c016 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 22 Feb 2016 11:29:12 +0800 Subject: [PATCH 24/63] Tests cleaning --- tests/unit/MediaStreamTrack.prop.spec.js | 18 ------------- tests/unit/Plugin.object.stability.spec.js | 8 +++--- .../RTCPeerConnection.constraints.spec.js | 24 ++++++++++++----- tests/unit/RTCPeerConnection.event.spec.js | 27 +++++++++---------- 4 files changed, 33 insertions(+), 44 deletions(-) diff --git a/tests/unit/MediaStreamTrack.prop.spec.js b/tests/unit/MediaStreamTrack.prop.spec.js index 062c95d..95b331c 100644 --- a/tests/unit/MediaStreamTrack.prop.spec.js +++ b/tests/unit/MediaStreamTrack.prop.spec.js @@ -65,24 +65,6 @@ describe('MediaStreamTrack | Properties', function() { assert.typeOf(source1.kind, 'string'); 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; - }); }); }); diff --git a/tests/unit/Plugin.object.stability.spec.js b/tests/unit/Plugin.object.stability.spec.js index bef4a90..1b30f79 100644 --- a/tests/unit/Plugin.object.stability.spec.js +++ b/tests/unit/Plugin.object.stability.spec.js @@ -61,20 +61,20 @@ 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(); } }; diff --git a/tests/unit/RTCPeerConnection.constraints.spec.js b/tests/unit/RTCPeerConnection.constraints.spec.js index 60667fc..97625b4 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,12 +30,19 @@ describe('RTCPeerConnection | RTCConfiguration', function() { }); }); + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + afterEach(function(done) { + peer = null; + done(); + }); + (function (constraints) { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + peer = new RTCPeerConnection(constraints); }); })({ iceServers: [] }); @@ -43,7 +53,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + peer = new RTCPeerConnection(constraints); }); })({ iceServers: [{ url: 'turn:numb.viagenie.ca', username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); @@ -54,7 +64,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + peer = new RTCPeerConnection(constraints); }); })({ iceServers: [{ url: 'leticia.choo@temasys.com.sg@turn:numb.viagenie.ca', credential: 'xxxxx' }] }); @@ -65,7 +75,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + peer = new RTCPeerConnection(constraints); }); })({ iceServers: [{ urls: ['turn:numb.viagenie.ca', 'turn:numb.viagenie.ca'], username: 'leticia.choo@temasys.com.sg', credential: 'xxxxx' }] }); @@ -76,7 +86,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + peer = new RTCPeerConnection(constraints); }); })({ iceServers: [{ url: 'stun:stun.l.google.com:19302' }] }); @@ -87,7 +97,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints); + 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' }] }); @@ -140,7 +150,7 @@ describe('RTCPeerConnection | RTCConfiguration', function() { it('new RTCPeerConnection(' + JSON.stringify(constraints) + ', ' + JSON.stringify(optional) + ')', function () { this.timeout(testItemTimeout); - var peer = new RTCPeerConnection(constraints, optional); + peer = new RTCPeerConnection(constraints, optional); }); })({ iceServers: [] }, { optional: [{ DtlsSrtpKeyAgreement: true }] }); diff --git a/tests/unit/RTCPeerConnection.event.spec.js b/tests/unit/RTCPeerConnection.event.spec.js index b593475..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: [] }); + peer1 = new RTCPeerConnection({ iceServers: [] }); + peer2 = new RTCPeerConnection({ iceServers: [] }); - done(); - - }, function (error) { - throw error; - }); + done(); }); From 99809553558db7cf5817c10142fc5116cc0ac98b Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 22 Feb 2016 16:44:03 +0800 Subject: [PATCH 25/63] AJS-244: bringing back wrongly removed code --- source/adapter.js | 7 +- source/adapter.screenshare.js | 217 ++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 source/adapter.screenshare.js diff --git a/source/adapter.js b/source/adapter.js index d825cfd..57f8bf0 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -301,7 +301,7 @@ AdapterJS.addEvent = function(elem, evnt, func) { } }; -AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab) { +AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { // only inject once the page is ready if (document.readyState !== 'complete') { return; @@ -337,6 +337,11 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe // On click on okay AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { + if (!!displayRefreshBar) { + AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? + AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, + AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); // jshint ignore:line + } window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); e.preventDefault(); diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js new file mode 100644 index 0000000..5be8c9a --- /dev/null +++ b/source/adapter.screenshare.js @@ -0,0 +1,217 @@ +(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) { + if (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) { + // intercepting screensharing requests + + // Invalid mediaSource for firefox, only "screen" and "window" are supported + if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') { + failureCb(new Error('GetUserMedia: Only "screen" and "window" are supported as mediaSource constraints')); + return; + } + + var updatedConstraints = clone(constraints); + + //constraints.video.mediaSource = constraints.video.mediaSource; + updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource; + + // so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked. + // strange but this works anyway + var checkIfReady = setInterval(function () { + if (document.readyState === 'complete') { + clearInterval(checkIfReady); + + baseGetUserMedia(updatedConstraints, successCb, function (error) { + if (error.name === 'PermissionDeniedError' && 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; + } else { + failureCb(error); + } + }); + } + }, 1); + + } else { // regular GetUserMediaRequest + baseGetUserMedia(constraints, successCb, failureCb); + } + }; + + getUserMedia = navigator.getUserMedia; + + } else if (window.navigator.webkitGetUserMedia) { + baseGetUserMedia = window.navigator.getUserMedia; + + navigator.getUserMedia = function (constraints, successCb, failureCb) { + if (constraints && constraints.video && !!constraints.video.mediaSource) { + if (window.webrtcDetectedBrowser !== 'chrome') { + // This is Opera, which does not support screensharing + failureCb(new Error('Current browser does not support screensharing')); + return; + } + + // would be fine since no methods + var updatedConstraints = clone(constraints); + + var chromeCallback = function(error, sourceId) { + if(!error) { + 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; + + if (sourceId) { + updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId; + } + + delete updatedConstraints.video.mediaSource; + + baseGetUserMedia(updatedConstraints, successCb, failureCb); + + } else { // GUM failed + if (error === 'permission-denied') { + failureCb(new Error('Permission denied for screen retrieval')); + } else { + // NOTE(J-O): I don't think we ever pass in here. + // A failure to capture the screen does not lead here. + failureCb(new Error('Failed retrieving selected screen')); + } + } + }; + + var onIFrameCallback = function (event) { + if (!event.data) { + return; + } + + if (event.data.chromeMediaSourceId) { + if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { + chromeCallback('permission-denied'); + } else { + chromeCallback(null, event.data.chromeMediaSourceId); + } + } + + if (event.data.chromeExtensionStatus) { + if (event.data.chromeExtensionStatus === 'not-installed') { + AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME, + AdapterJS.TEXT.EXTENSION.BUTTON_CHROME, + event.data.data, true, true); + } else { + chromeCallback(event.data.chromeExtensionStatus, null); + } + } + + // this event listener is no more needed + window.removeEventListener('message', onIFrameCallback); + }; + + window.addEventListener('message', onIFrameCallback); + + postFrameMessage({ + captureSourceId: true + }); + + } else { + baseGetUserMedia(constraints, successCb, failureCb); + } + }; + + getUserMedia = navigator.getUserMedia; + + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + // nothing here because edge does not support screensharing + console.warn('Edge does not support screensharing feature in getUserMedia'); + + } else { + baseGetUserMedia = window.navigator.getUserMedia; + + navigator.getUserMedia = function (constraints, successCb, failureCb) { + if (constraints && constraints.video && !!constraints.video.mediaSource) { + // would be fine since no methods + var updatedConstraints = clone(constraints); + + // wait for plugin to be ready + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + // check if screensharing feature is available + if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature && + !!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) { + // set the constraints + updatedConstraints.video.optional = updatedConstraints.video.optional || []; + updatedConstraints.video.optional.push({ + sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing' + }); + + delete updatedConstraints.video.mediaSource; + } else { + failureCb(new Error('Your version of the WebRTC plugin does not support screensharing')); + return; + } + baseGetUserMedia(updatedConstraints, successCb, failureCb); + }); + } else { + baseGetUserMedia(constraints, successCb, failureCb); + } + }; + + getUserMedia = window.navigator.getUserMedia; + } + + // For chrome, use an iframe to load the screensharing extension + // in the correct domain. + // Modify here for custom screensharing extension in chrome + if (window.webrtcDetectedBrowser === 'chrome') { + var iframe = document.createElement('iframe'); + + iframe.onload = function() { + iframe.isLoaded = true; + }; + + 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) { + object = object || {}; + + if (!iframe.isLoaded) { + setTimeout(function () { + iframe.contentWindow.postMessage(object, '*'); + }, 100); + return; + } + + iframe.contentWindow.postMessage(object, '*'); + }; + } else if (window.webrtcDetectedBrowser === 'opera') { + console.warn('Opera does not support screensharing feature in getUserMedia'); + } +})(); From e839226cf96633ae5614de36c09c238dddcc3295 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 22 Feb 2016 16:46:01 +0800 Subject: [PATCH 26/63] jshint corrections --- source/adapter.screenshare.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js index 5be8c9a..d8ff143 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; }; @@ -199,7 +203,7 @@ (document.body || document.documentElement).appendChild(iframe); - var postFrameMessage = function (object) { + var postFrameMessage = function (object) { // jshint ignore:line object = object || {}; if (!iframe.isLoaded) { From e47a71935fcbedde3718ab62f8a9ec7e956901c7 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 22 Feb 2016 16:51:54 +0800 Subject: [PATCH 27/63] test corrections --- tests/unit/MediaStreamTrack.prop.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/MediaStreamTrack.prop.spec.js b/tests/unit/MediaStreamTrack.prop.spec.js index 95b331c..d171a0b 100644 --- a/tests/unit/MediaStreamTrack.prop.spec.js +++ b/tests/unit/MediaStreamTrack.prop.spec.js @@ -65,6 +65,8 @@ describe('MediaStreamTrack | Properties', function() { assert.typeOf(source1.kind, 'string'); assert.typeOf(source1.facing, 'string'); assert.typeOf(source1.label, 'string'); + + done(); }); }); @@ -77,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'); From b101211f2e510c93e2c2b955beefc37cdfe77104 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 22 Feb 2016 17:44:15 +0800 Subject: [PATCH 28/63] TWP-396: throw custom error on new RTCPC when wrong argument type --- source/adapter.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 8e0d426..0507ee5 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -907,25 +907,26 @@ if ( navigator.mozGetUserMedia }; RTCPeerConnection = function (servers, constraints) { - AdapterJS.WebRTCPlugin.WaitForPluginReady(); + if (!servers || + typeof servers.iceServers !== 'object') { + throw new Error('new RTCPeerConnection: arg1.iceServers should be a non-null array'); + } + AdapterJS.WebRTCPlugin.WaitForPluginReady(); 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; - 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 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; From 09293fda0f862a176a9fe488a193ba6a86aa6ffd Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 10:17:37 +0800 Subject: [PATCH 29/63] TWP-396: RTCPC(null) is a correct synthax --- source/adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 0507ee5..00229c1 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -907,9 +907,9 @@ if ( navigator.mozGetUserMedia }; RTCPeerConnection = function (servers, constraints) { - if (!servers || + if (servers && typeof servers.iceServers !== 'object') { - throw new Error('new RTCPeerConnection: arg1.iceServers should be a non-null array'); + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); } AdapterJS.WebRTCPlugin.WaitForPluginReady(); From 9b960b16cfff9cc509f53aa08749558ecb577066 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 12:32:25 +0800 Subject: [PATCH 30/63] AJS-245: propagated attachMediaStream and reattachMediaStream to window and AdapterJS --- source/adapter.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/adapter.js b/source/adapter.js index 13bbdc2..25246d4 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -681,6 +681,12 @@ if ( navigator.mozGetUserMedia || return to; }; + // Propagate attachMediaStream in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + // Removed Google defined promises when promise is not defined if (typeof Promise === 'undefined') { requestUserMedia = null; @@ -1067,6 +1073,12 @@ if ( navigator.mozGetUserMedia || } }; + // Propagate attachMediaStream in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { properties = Object.getOwnPropertyNames( prototype ); for(var prop in properties) { From 38d36185666579285f4316d8736aea35a2406e54 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 12:37:42 +0800 Subject: [PATCH 31/63] Correction of previous bad PR: https://github.com/Temasys/AdapterJS/pull/157 --- source/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/adapter.js b/source/adapter.js index 13bbdc2..5fa1939 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -585,7 +585,7 @@ if ( navigator.mozGetUserMedia || if (turnUrlParts.length === 1 || turnUrlParts[1].indexOf('transport=udp') === 0) { iceServer = { - urls : [turn_urlParts[0]], + urls : [turnUrlParts[0]], credential : password, username : username }; From 76ab3a2a8d7c58243aee7172a0301750764bb0eb Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 15:01:33 +0800 Subject: [PATCH 32/63] TWP-396: Check if servers.iceServers is an array, and protect the case server is null --- source/adapter.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 00229c1..48ca83a 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -908,7 +908,7 @@ if ( navigator.mozGetUserMedia RTCPeerConnection = function (servers, constraints) { if (servers && - typeof servers.iceServers !== 'object') { + !Array.isArray(servers.iceServers)) { throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); } @@ -919,14 +919,17 @@ if ( navigator.mozGetUserMedia return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); } else { // RTCPeerConnection prototype from the old spec - var iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - if (iceServers[i].urls && !iceServers[i].url) { - iceServers[i].url = iceServers[i].urls; + var iceServers = null; + if (servers && 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); } - iceServers[i].hasCredentials = AdapterJS. - isDefined(iceServers[i].username) && - AdapterJS.isDefined(iceServers[i].credential); } var mandatory = (constraints && constraints.mandatory) ? constraints.mandatory : null; From eb0fc219f07c398021deb86450e9002386890370 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 15:11:30 +0800 Subject: [PATCH 33/63] TWP-396: check for is array again in old spec block --- source/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/adapter.js b/source/adapter.js index 48ca83a..8dd010d 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -920,7 +920,7 @@ if ( navigator.mozGetUserMedia } else { // RTCPeerConnection prototype from the old spec var iceServers = null; - if (servers && servers.iceServers) { + if (servers && Array.isArray(servers.iceServers)) { iceServers = servers.iceServers; for (var i = 0; i < iceServers.length; i++) { if (iceServers[i].urls && !iceServers[i].url) { From a3cd56cfc888c9c35d8d1b2a2f9c03b7d7c785b9 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 23 Feb 2016 16:15:08 +0800 Subject: [PATCH 34/63] AJS-171: variable declaration and bad variable name fix --- source/adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 8df24ff..c6def99 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -311,7 +311,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(' Date: Tue, 8 Mar 2016 12:47:38 +0800 Subject: [PATCH 50/63] Simple quotes only, modified error message a bit --- Gruntfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index d977e20..9dea07d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -295,7 +295,8 @@ module.exports = function(grunt) { 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') + "; output would be incomplete. Did you remember to initialize submodules?\nex: git submodule update --init"); + 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'); } }); From 9f64b99d1155603f5fc09a8b426192ed5f3cbfc3 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 10 Mar 2016 16:42:04 +0800 Subject: [PATCH 51/63] TWP-13: Adding test for getFrame --- tests/unit/Plugin.features.no-spec.js | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/unit/Plugin.features.no-spec.js diff --git a/tests/unit/Plugin.features.no-spec.js b/tests/unit/Plugin.features.no-spec.js new file mode 100644 index 0000000..b1998de --- /dev/null +++ b/tests/unit/Plugin.features.no-spec.js @@ -0,0 +1,84 @@ +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).not.to.be.empty; + 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') From fbf01cd38db8691e1d639bb73a047b2ccfdd8184 Mon Sep 17 00:00:00 2001 From: johache Date: Thu, 10 Mar 2016 16:52:13 +0800 Subject: [PATCH 52/63] Run no-spec tests --- tests/karma.conf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/karma.conf.js b/tests/karma.conf.js index 60994dc..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}, ], From 2062ae66076002eb6abfbac5f4f1fc66f3a32ad0 Mon Sep 17 00:00:00 2001 From: johache Date: Fri, 11 Mar 2016 10:45:01 +0800 Subject: [PATCH 53/63] Fix getUserMedia() && window.getUserMedia() are null on AJS.debug bowserified --- source/adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index e5e21c9..c944c50 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -976,7 +976,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; @@ -985,7 +985,7 @@ if ( navigator.mozGetUserMedia || getUserMedia(constraints, successCallback, failureCallback); }); }; - AdapterJS.getUserMedia = window.navigator.getUserMedia = window.getUserMedia; + window.navigator.getUserMedia = getUserMedia; // Defined mediaDevices when promises are available if ( !navigator.mediaDevices && From 0a86a3eca0d05db15012c8650f1f6d1c77e073a0 Mon Sep 17 00:00:00 2001 From: johache Date: Fri, 11 Mar 2016 12:01:14 +0800 Subject: [PATCH 54/63] Fix getUserMedia() && window.getUserMedia() screensharing override on AJS bowserified --- source/adapter.screenshare.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js index 8846abf..59660ab 100644 --- a/source/adapter.screenshare.js +++ b/source/adapter.screenshare.js @@ -67,7 +67,7 @@ } }; - AdapterJS.getUserMedia = getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; navigator.mediaDevices.getUserMedia = requestUserMedia; } else if (window.navigator.webkitGetUserMedia) { @@ -148,7 +148,7 @@ } }; - AdapterJS.getUserMedia = getUserMedia = navigator.getUserMedia; + AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; navigator.mediaDevices.getUserMedia = requestUserMedia; } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { From 3bd41e2dcf788718eb1f97cf14c26a7da6c34dc2 Mon Sep 17 00:00:00 2001 From: johache Date: Fri, 11 Mar 2016 12:32:39 +0800 Subject: [PATCH 55/63] Fix navigator.mediaDevices.getUserMedia && window.navigator.mediaDevices.getUserMedia screensharing override with AJS bowserified --- source/adapter.screenshare.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js index 59660ab..d67a6e8 100644 --- a/source/adapter.screenshare.js +++ b/source/adapter.screenshare.js @@ -68,7 +68,11 @@ }; AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; - navigator.mediaDevices.getUserMedia = requestUserMedia; + 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; @@ -149,7 +153,11 @@ }; AdapterJS.getUserMedia = window.getUserMedia = navigator.getUserMedia; - navigator.mediaDevices.getUserMedia = requestUserMedia; + 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 From 01e2d320b4438099c88f2a9b19a0c5513eaf9ba4 Mon Sep 17 00:00:00 2001 From: johache Date: Fri, 11 Mar 2016 16:54:52 +0800 Subject: [PATCH 56/63] bugfix: getUserMedia and window.getUserMedia ended being different. Probably because gum is var in webrtc-adapter --- source/adapter.screenshare.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/adapter.screenshare.js b/source/adapter.screenshare.js index d67a6e8..4084d79 100644 --- a/source/adapter.screenshare.js +++ b/source/adapter.screenshare.js @@ -194,7 +194,8 @@ } }; - AdapterJS.getUserMedia = getUserMedia = window.navigator.getUserMedia; + AdapterJS.getUserMedia = getUserMedia = + window.getUserMedia = navigator.getUserMedia; navigator.mediaDevices.getUserMedia = requestUserMedia; } From 7104c4ac219374e7e7bc0ee3b7506090f2ec3f24 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 14 Mar 2016 12:42:23 +0800 Subject: [PATCH 57/63] github#177 special case for attachMediaStream on Chrome when no stream --- source/adapter.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/adapter.js b/source/adapter.js index 01d9fcf..1fdf7f1 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -673,7 +673,12 @@ if ( navigator.mozGetUserMedia || // to support the plugin's logic attachMediaStream_base = attachMediaStream; attachMediaStream = function (element, stream) { - attachMediaStream_base(element, stream); + if (webrtcDetectedBrowser === 'chrome' && !stream) { + // Chrome does not support "src = null" + element.src = ''; + } else { + attachMediaStream_base(element, stream); + } return element; }; reattachMediaStream_base = reattachMediaStream; From f2de93021507ec14a18b02a92d5f9aacab3e7d69 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 14 Mar 2016 14:51:16 +0800 Subject: [PATCH 58/63] #177 same logic as Chrome but for Opera --- source/adapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/adapter.js b/source/adapter.js index 1fdf7f1..d4a0ecd 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -673,7 +673,9 @@ if ( navigator.mozGetUserMedia || // to support the plugin's logic attachMediaStream_base = attachMediaStream; attachMediaStream = function (element, stream) { - if (webrtcDetectedBrowser === 'chrome' && !stream) { + if ((webrtcDetectedBrowser === 'chrome' || + webrtcDetectedBrowser === 'opera') && + !stream) { // Chrome does not support "src = null" element.src = ''; } else { From 8670dca3e4af8fe879910c4408a1035a3ccb40da Mon Sep 17 00:00:00 2001 From: Leticia Choo Date: Mon, 14 Mar 2016 15:25:02 +0800 Subject: [PATCH 59/63] AJS-253 Fixes for webrtcDetectedBrowser, webrtcDetectedVersion and webrtcMinimumVersion for browserify environment by propagating it to window scope. --- source/adapter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/adapter.js b/source/adapter.js index 01d9fcf..880101e 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -1205,3 +1205,7 @@ if ( navigator.mozGetUserMedia || // END OF WEBRTC PLUGIN SHIM /////////////////////////////////////////////////////////////////// } + +window.webrtcDetectedBrowser = webrtcDetectedBrowser; +window.webrtcDetectedVersion = webrtcDetectedVersion; +window.webrtcMinimumVersion = webrtcMinimumVersion; \ No newline at end of file From 8e9355943bbfa4310d0599b03a2def556dff4ec3 Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 14 Mar 2016 15:47:35 +0800 Subject: [PATCH 60/63] AttachEvent now throws an error on Safari when trying to set an undefined function --- source/adapter.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index d4a0ecd..c9b009a 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -1134,15 +1134,10 @@ if ( navigator.mozGetUserMedia || if (prop) { 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); - } - } - //TODO (http://jira.temasys.com.sg/browse/TWP-328) Forward non-event properties ? + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); } } } From 11541abcdcefc74bbe2e6b328f21f61862965b4d Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 14 Mar 2016 15:56:51 +0800 Subject: [PATCH 61/63] AJS-253 Moved forwarding of webrtcDetectedBrowser, webrtcDetectedVersion and webrtcMinimumVersion up in AdapterJS.parseWebrtcDetectedBrowser --- source/adapter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/adapter.js b/source/adapter.js index 880101e..1179670 100644 --- a/source/adapter.js +++ b/source/adapter.js @@ -275,6 +275,10 @@ AdapterJS.parseWebrtcDetectedBrowser = function () { webrtcDetectedBrowser = 'blink'; // TODO: detected WebRTC version } + + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; }; AdapterJS.addEvent = function(elem, evnt, func) { @@ -1205,7 +1209,3 @@ if ( navigator.mozGetUserMedia || // END OF WEBRTC PLUGIN SHIM /////////////////////////////////////////////////////////////////// } - -window.webrtcDetectedBrowser = webrtcDetectedBrowser; -window.webrtcDetectedVersion = webrtcDetectedVersion; -window.webrtcMinimumVersion = webrtcMinimumVersion; \ No newline at end of file From 31af472eb7abc66b75220d44a1b60ba9d8059c4f Mon Sep 17 00:00:00 2001 From: johache Date: Mon, 14 Mar 2016 18:19:47 +0800 Subject: [PATCH 62/63] Prepping 0.13.1 --- bower.json | 2 +- package.json | 2 +- publish/adapter.debug.js | 1568 ++++++++++++++++++++++++--- publish/adapter.min.js | 5 +- publish/adapter.screenshare.js | 1603 +++++++++++++++++++++++++--- publish/adapter.screenshare.min.js | 6 +- 6 files changed, 2847 insertions(+), 339 deletions(-) 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 3f2b195..68581f7 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-14 */ // 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) { @@ -335,7 +332,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'); } @@ -1073,11 +2255,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, @@ -1094,6 +2282,8 @@ if ( navigator.mozGetUserMedia define([], function() { return { RTCPeerConnection: window.RTCPeerConnection, + RTCIceCandidate: window.RTCIceCandidate, + RTCSessionDescription: window.RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1109,13 +2299,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 @@ -1141,25 +2334,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 }; @@ -1183,12 +2377,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, @@ -1224,7 +2418,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; @@ -1235,11 +2429,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; @@ -1248,6 +2449,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; @@ -1304,7 +2513,6 @@ if ( navigator.mozGetUserMedia console.groupEnd = function (arg) {}; /* jshint +W020 */ } - webrtcDetectedType = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; @@ -1430,7 +2638,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; } @@ -1442,13 +2650,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, @@ -1474,27 +2682,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 = {}; @@ -1504,7 +2743,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; @@ -1513,11 +2752,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) { @@ -1526,6 +2770,7 @@ if ( navigator.mozGetUserMedia resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], + id: device.id, deviceId: device.id, groupId: ''}; })); @@ -1635,31 +2880,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 7b92821..7d81366 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); \ No newline at end of file +/*! adapterjs - v0.13.1 - 2016-03-14 */ +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.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 e8eca00..53c36a8 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-14 */ // 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) { @@ -335,7 +332,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'); } @@ -1073,11 +2255,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, @@ -1094,6 +2282,8 @@ if ( navigator.mozGetUserMedia define([], function() { return { RTCPeerConnection: window.RTCPeerConnection, + RTCIceCandidate: window.RTCIceCandidate, + RTCSessionDescription: window.RTCSessionDescription, getUserMedia: getUserMedia, attachMediaStream: attachMediaStream, reattachMediaStream: reattachMediaStream, @@ -1109,13 +2299,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 @@ -1141,25 +2334,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 }; @@ -1183,12 +2377,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, @@ -1224,7 +2418,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; @@ -1235,11 +2429,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; @@ -1248,6 +2449,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; @@ -1304,7 +2513,6 @@ if ( navigator.mozGetUserMedia console.groupEnd = function (arg) {}; /* jshint +W020 */ } - webrtcDetectedType = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; @@ -1430,7 +2638,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; } @@ -1442,13 +2650,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, @@ -1474,27 +2682,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 = {}; @@ -1504,7 +2743,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; @@ -1513,11 +2752,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) { @@ -1526,6 +2770,7 @@ if ( navigator.mozGetUserMedia resolve(devices.map(function(device) { return {label: device.label, kind: kinds[device.kind], + id: device.id, deviceId: device.id, groupId: ''}; })); @@ -1635,31 +2880,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) { @@ -1737,10 +2983,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; }; @@ -1771,11 +3021,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); } @@ -1788,7 +3037,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; @@ -1868,7 +3122,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 @@ -1905,7 +3164,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 @@ -1923,7 +3184,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) { @@ -1938,4 +3199,4 @@ if ( navigator.mozGetUserMedia } else if (window.webrtcDetectedBrowser === 'opera') { console.warn('Opera does not support screensharing feature in getUserMedia'); } -})(); \ No newline at end of file +})(); diff --git a/publish/adapter.screenshare.min.js b/publish/adapter.screenshare.min.js index b610f5e..630cdeb 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}(); \ No newline at end of file +/*! adapterjs - v0.13.1 - 2016-03-14 */ +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.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 From 79937150ace5e5445e433b466b5b53c030d06b88 Mon Sep 17 00:00:00 2001 From: johache Date: Tue, 15 Mar 2016 10:49:31 +0800 Subject: [PATCH 63/63] jshint correction, removed an unnecessary line (duplicated logic) --- tests/unit/Plugin.features.no-spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/Plugin.features.no-spec.js b/tests/unit/Plugin.features.no-spec.js index b1998de..ebe6796 100644 --- a/tests/unit/Plugin.features.no-spec.js +++ b/tests/unit/Plugin.features.no-spec.js @@ -65,7 +65,6 @@ if(webrtcDetectedBrowser === 'safari' || webrtcDetectedBrowser === 'IE') { var base64 = video.getFrame(); assert.isString(base64); - expect(base64).not.to.be.empty; expect(base64).to.have.length.above(1000); var img = new Image();