test('extend config with iceServers', function(t) {
  var config;
  var testServers = freeice();

  t.plan(2);
  t.ok(config = generators.config({ iceServers: testServers }), 'generated config');
  t.deepEqual(config.iceServers, testServers, 'servers matched');
});
  initialize() {
    this.connectionSettings = {'room': Settings.swarm, iceServers: Freeice(), debug: false}
    this.connection = QuickConnect(Settings.tracker, this.connectionSettings)
    this.swarm = new Swarm()

    //TODO create different data channels for different protocol messages
    this.dataChannel = this.connection.createDataChannel(Settings.swarm)
    this.setupListerners()
  }
Example #3
0
 init: (cfg) => (dispatch, getState) => {
   rtc = new SimpleWebRTC({
     url: __TURN_SERVER__,
     debug: cfg.debug || false,
     peerConnectionConfig: freeice()
   });
   rtc
     .on('connectionReady', (id) => {
       dispatch({type: types.CONNECTION_READY, payload: id})
     })
     .on('createdPeer', (peer) => {
       dispatch({type: types.NEW_PEER_ADDED, payload: peer})
     })
     .on('peerStreamAdded', (peer) => {
       dispatch({type: types.PEER_STREAM_ADDED, payload: peer})
     })
     .on('peerStreamRemoved', (peer) => {
       dispatch({type: types.PEER_STREAM_REMOVED, payload: peer})
     });
   dispatch({type: types.INIT, payload: rtc});
 },
function noop(e){e&&console.error(e)}function trackStop(e){e.stop&&e.stop()}function streamStop(e){e.getTracks().forEach(trackStop)}function bufferizeCandidates(e,n){var t=[];return e.onsignalingstatechange=function(e){if("stable"===this.signalingState)for(;t.length;){var n=t.shift();this.addIceCandidate(n.candidate,n.callback,n.callback)}},function(i,o){switch(o=o||n,e.signalingState){case"closed":o(new Error("PeerConnection object is closed"));break;case"stable":if(e.remoteDescription){e.addIceCandidate(i,o,o);break}default:t.push({candidate:i,callback:o})}}}function removeFIDFromOffer(e){var n=e.indexOf("a=ssrc-group:FID");return n>0?e.slice(0,n):e}function getSimulcastInfo(e){var n=e.getVideoTracks(),t=["a=x-google-flag:conference","a=ssrc-group:SIM 1 2 3","a=ssrc:1 cname:localVideo","a=ssrc:1 msid:"+e.id+" "+n[0].id,"a=ssrc:1 mslabel:"+e.id,"a=ssrc:1 label:"+n[0].id,"a=ssrc:2 cname:localVideo","a=ssrc:2 msid:"+e.id+" "+n[0].id,"a=ssrc:2 mslabel:"+e.id,"a=ssrc:2 label:"+n[0].id,"a=ssrc:3 cname:localVideo","a=ssrc:3 msid:"+e.id+" "+n[0].id,"a=ssrc:3 mslabel:"+e.id,"a=ssrc:3 label:"+n[0].id];return t.push(""),t.join("\n")}function WebRtcPeer(e,n,t){function i(){if(a){var e=p.getRemoteStreams()[0],n=e;attachMediaStream(a,e),console.log("Remote URL:",n)}}function o(){C.emit("streamended",this)}function r(){"closed"===p.signalingState&&t('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'),d&&c&&C.showLocalVideo(),d&&(d.onended=function(e){o()},p.addStream(d)),l&&(l.onended=function(e){o()},p.addStream(l));var n=parser.getBrowser();"sendonly"!==e||"Chrome"!==n.name&&"Chromium"!==n.name||39!==n.major||(e="sendrecv"),t()}function s(e){void 0===e&&(e=MEDIA_CONSTRAINTS),getUserMedia(e,function(e){d=e,r()},t)}if(!(this instanceof WebRtcPeer))return new WebRtcPeer(e,n,t);WebRtcPeer.super_.call(this),n instanceof Function&&(t=n,n=void 0),n=n||{},t=(t||noop).bind(this);var c=n.localVideo,a=n.remoteVideo,d=n.videoStream,l=n.audioStream,u=n.mediaConstraints,f=n.connectionConstraints,p=n.peerConnection,v=n.sendSource||"webcam",h=uuid.v4(),g=recursive({iceServers:freeice()},n.configuration),m=n.onstreamended;m&&this.on("streamended",m);var b=n.onicecandidate;b&&this.on("icecandidate",b);var S=n.oncandidategatheringdone;S&&this.on("candidategatheringdone",S);var R=n.simulcast;p||(p=new RTCPeerConnection(g)),Object.defineProperties(this,{peerConnection:{get:function(){return p}},remoteVideo:{get:function(){return a}},localVideo:{get:function(){return c}},currentFrame:{get:function(){if(a){if(a.readyState<a.HAVE_CURRENT_DATA)throw new Error("No video stream data available");var e=document.createElement("canvas");return e.width=a.videoWidth,e.height=a.videoHeight,e.getContext("2d").drawImage(a,0,0),e}}}});var C=this,w=[],P=!1;p.onicecandidate=function(e){var n=e.candidate;EventEmitter.listenerCount(C,"icecandidate")||EventEmitter.listenerCount(C,"candidategatheringdone")?n?(C.emit("icecandidate",n),P=!1):P||(C.emit("candidategatheringdone"),P=!0):P||(w.push(n),n||(P=!0))},this.on("newListener",function(e,n){if("icecandidate"===e||"candidategatheringdone"===e)for(;w.length;){var t=w.shift();!t==("candidategatheringdone"===e)&&n(t)}});var y=bufferizeCandidates(p);this.addIceCandidate=function(e,n){var t=new RTCIceCandidate(e);console.log("ICE candidate received"),n=(n||noop).bind(this),y(t,n)},this.generateOffer=function(n){n=n.bind(this);var t=parser.getBrowser(),i=!0,o=!0;u&&(i="boolean"==typeof u.audio?u.audio:!0,o="boolean"==typeof u.video?u.video:!0);var r="Firefox"===t.name&&t.version>34?{offerToReceiveAudio:"sendonly"!==e&&i,offerToReceiveVideo:"sendonly"!==e&&o}:{mandatory:{OfferToReceiveAudio:"sendonly"!==e&&i,OfferToReceiveVideo:"sendonly"!==e&&o},optional:[{DtlsSrtpKeyAgreement:!0}]},s=recursive(r,f);console.log("constraints: "+JSON.stringify(s)),p.createOffer(function(e){console.log("Created SDP offer"),R&&("Chrome"===t.name||"Chromium"===t.name?(console.log("Adding multicast info"),e=new RTCSessionDescription({type:e.type,sdp:removeFIDFromOffer(e.sdp)+getSimulcastInfo(d)})):console.warn("Simulcast is only available in Chrome browser.")),p.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp,C.processAnswer.bind(C))},n)},n,s)},this.getLocalSessionDescriptor=function(){return p.localDescription},this.getRemoteSessionDescriptor=function(){return p.remoteDescription},this.showLocalVideo=function(){attachMediaStream(c,d)},this.processAnswer=function(e,n){n=(n||noop).bind(this);var t=new RTCSessionDescription({type:"answer",sdp:e});return console.log("SDP answer received, setting remote description"),"closed"===p.signalingState?n("PeerConnection is closed"):void p.setRemoteDescription(t,function(){i(),n()},n)},this.processOffer=function(e,n){n=n.bind(this);var t=new RTCSessionDescription({type:"offer",sdp:e});return console.log("SDP offer received, setting remote description"),"closed"===p.signalingState?n("PeerConnection is closed"):void p.setRemoteDescription(t,function(){i(),p.createAnswer(function(e){console.log("Created SDP answer"),p.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp)},n)},n)},n)},"recvonly"===e||d||l?setTimeout(r,0):"webcam"===v?s(u):getScreenConstraints(v,function(e,n){return e?t(e):(constraints=[u],constraints.unshift(n),void s(recursive.apply(void 0,constraints)))},h),this.on("_dispose",function(){c&&(c.pause(),c.src="",c.load()),a&&(a.pause(),a.src="",a.load()),C.removeAllListeners(),void 0!==window.cancelChooseDesktopMedia&&window.cancelChooseDesktopMedia(h)})}function createEnableDescriptor(e){var n="get"+e+"Tracks";return{enumerable:!0,get:function(){if(this.peerConnection){var e=this.peerConnection.getLocalStreams();if(e.length){for(var t,i=0;t=e[i];i++)for(var o,r=t[n](),s=0;o=r[s];s++)if(!o.enabled)return!1;return!0}}},set:function(e){function t(n){n.enabled=e}this.peerConnection.getLocalStreams().forEach(function(e){e[n]().forEach(t)})}}}function WebRtcPeerRecvonly(e,n){return this instanceof WebRtcPeerRecvonly?void WebRtcPeerRecvonly.super_.call(this,"recvonly",e,n):new WebRtcPeerRecvonly(e,n)}function WebRtcPeerSendonly(e,n){return this instanceof WebRtcPeerSendonly?void WebRtcPeerSendonly.super_.call(this,"sendonly",e,n):new WebRtcPeerSendonly(e,n)}function WebRtcPeerSendrecv(e,n){return this instanceof WebRtcPeerSendrecv?void WebRtcPeerSendrecv.super_.call(this,"sendrecv",e,n):new WebRtcPeerSendrecv(e,n)}var freeice=require("freeice"),inherits=require("inherits"),UAParser=require("ua-parser-js"),uuid=require("uuid"),EventEmitter=require("events").EventEmitter,recursive=require("merge").recursive.bind(void 0,!0);try{require("kurento-browser-extensions")}catch(error){"undefined"==typeof getScreenConstraints&&(console.warn("screen sharing is not available"),getScreenConstraints=function(e,n){n(new Error("This library is not enabled for screen sharing"))})}var MEDIA_CONSTRAINTS={audio:!0,video:{width:640,framerate:15}},ua=window&&window.navigator?window.navigator.userAgent:"",parser=new UAParser(ua);inherits(WebRtcPeer,EventEmitter),Object.defineProperties(WebRtcPeer.prototype,{enabled:{enumerable:!0,get:function(){return this.audioEnabled&&this.videoEnabled},set:function(e){this.audioEnabled=this.videoEnabled=e}},audioEnabled:createEnableDescriptor("Audio"),videoEnabled:createEnableDescriptor("Video")}),WebRtcPeer.prototype.getLocalStream=function(e){return this.peerConnection?this.peerConnection.getLocalStreams()[e||0]:void 0},WebRtcPeer.prototype.getRemoteStream=function(e){return this.peerConnection?this.peerConnection.getRemoteStreams()[e||0]:void 0},WebRtcPeer.prototype.dispose=function(){console.log("Disposing WebRtcPeer");var e=this.peerConnection;try{if(e){if("closed"===e.signalingState)return;e.getLocalStreams().forEach(streamStop),e.close()}}catch(n){console.warn("Exception disposing webrtc peer "+n)}this.emit("_dispose")},inherits(WebRtcPeerRecvonly,WebRtcPeer),inherits(WebRtcPeerSendonly,WebRtcPeer),inherits(WebRtcPeerSendrecv,WebRtcPeer),exports.bufferizeCandidates=bufferizeCandidates,exports.WebRtcPeerRecvonly=WebRtcPeerRecvonly,exports.WebRtcPeerSendonly=WebRtcPeerSendonly,exports.WebRtcPeerSendrecv=WebRtcPeerSendrecv;
Example #5
0
File: peer.js Project: ancon/bemtv
 connect: function() {
   connection = rtc_quickconnect(BEMTV_SERVER, {room: this.room, iceServers: freeice()});
   console.log("[bemtv] connecting to " + this.room);
   this.createDataChannel(connection);
 },
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
function defaultOnerror(e){e&&console.error(e)}function noop(){}function WebRtcPeer(e,t,r,o,i,s,n){Object.defineProperty(this,"pc",{writable:!0}),this.localVideo=t,this.remoteVideo=r,this.onerror=i||defaultOnerror,this.stream=s,this.audioStream=n,this.mode=e,this.onsdpoffer=o||noop}var freeice=require("freeice");WebRtcPeer.prototype.start=function(){var e=this;this.pc||(this.pc=new RTCPeerConnection(this.server,this.options));var t=this.pc;this.stream&&this.localVideo&&(this.localVideo.src=URL.createObjectURL(this.stream),this.localVideo.muted=!0),this.stream&&t.addStream(this.stream),this.audioStream&&t.addStream(this.audioStream),this.constraints={mandatory:{OfferToReceiveAudio:void 0!==this.remoteVideo,OfferToReceiveVideo:void 0!==this.remoteVideo}},t.createOffer(function(r){console.log("Created SDP offer"),t.setLocalDescription(r,function(){console.log("Local description set")},e.onerror)},this.onerror,this.constraints);var r=!1;t.onicecandidate=function(o){if(o.candidate)return void(r=!1);if(!r){var i=t.localDescription.sdp;console.log("ICE negotiation completed"),e.onsdpoffer(i,e),r=!0}}},WebRtcPeer.prototype.dispose=function(){console.log("Disposing WebRtcPeer"),this.pc&&"closed"!=this.pc.signalingState&&this.pc.close(),this.localVideo&&(this.localVideo.src=""),this.remoteVideo&&(this.remoteVideo.src=""),this.stream&&(this.stream.getAudioTracks().forEach(function(e){e.stop&&e.stop()}),this.stream.getVideoTracks().forEach(function(e){e.stop&&e.stop()}))},WebRtcPeer.prototype.userMediaConstraints={audio:!0,video:{mandatory:{maxWidth:640,maxFrameRate:15,minFrameRate:15}}},WebRtcPeer.prototype.processSdpAnswer=function(e){var t=new RTCSessionDescription({type:"answer",sdp:e});console.log("SDP answer received, setting remote description");var r=this;r.pc.setRemoteDescription(t,function(){if(r.remoteVideo){var e=r.pc.getRemoteStreams()[0];r.remoteVideo.src=URL.createObjectURL(e)}},this.onerror)},WebRtcPeer.prototype.server={iceServers:freeice()},WebRtcPeer.prototype.options={optional:[{DtlsSrtpKeyAgreement:!0}]},WebRtcPeer.start=function(e,t,r,o,i,s,n,c){var a=new WebRtcPeer(e,t,r,o,i,n,c);if("recv"===a.mode||a.stream)a.start();else{var d=s?s:a.userMediaConstraints;getUserMedia(d,function(e){a.stream=e,a.start()},a.onerror)}return a},WebRtcPeer.startRecvOnly=function(e,t,r,o){return WebRtcPeer.start("recv",null,e,t,r,o)},WebRtcPeer.startSendOnly=function(e,t,r,o){return WebRtcPeer.start("send",e,null,t,r,o)},WebRtcPeer.startSendRecv=function(e,t,r,o,i){return WebRtcPeer.start("sendRecv",e,t,r,o,i)},module.exports=WebRtcPeer;
},{"freeice":3}],2:[function(require,module,exports){
Example #7
0
var freeice = require('freeice');
var quickconnect = require('rtc-quickconnect');

var opts = {
  room: 'qcdemo42',
  debug: true,
  OfferToReceiveAudio: false,
  OfferToReceiveVideo: false,
  iceServers: freeice()
};

var connecton = quickconnect('https://switchboard.rtc.io/', opts);
console.log("ID: " + connecton.id);
connecton
  .createDataChannel('test')
  .on('channel:opened:test', function (id, dc) {
    console.log('dc open for peer: ' + id);
  })
  .on('message:candidate', function (data) {
    console.warn(data)
  });
function noop(e){e&&console.error(e)}function trackStop(e){e.stop&&e.stop()}function streamStop(e){e.getTracks().forEach(trackStop)}function bufferizeCandidates(e,n){var t=[];return e.addEventListener("signalingstatechange",function(){if("stable"===this.signalingState)for(;t.length;){var e=t.shift();this.addIceCandidate(e.candidate,e.callback,e.callback)}}),function(i,r){switch(r=r||n,e.signalingState){case"closed":r(new Error("PeerConnection object is closed"));break;case"stable":if(e.remoteDescription){e.addIceCandidate(i,r,r);break}default:t.push({candidate:i,callback:r})}}}function WebRtcPeer(e,n,t){function i(){if(s){var e=p.getRemoteStreams()[0],n=e?URL.createObjectURL(e):"";s.pause(),s.src=n,s.load(),console.log("Remote URL:",n)}}function r(){S.emit("streamended",this)}function o(){"closed"===p.signalingState&&t('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'),d&&a&&S.showLocalVideo(),d&&(d.addEventListener("ended",r),p.addStream(d)),l&&(l.addEventListener("ended",r),p.addStream(l));var n=parser.getBrowser();"sendonly"!==e||"Chrome"!==n.name&&"Chromium"!==n.name||39!==n.major||(e="sendrecv"),t()}function c(e){e=Array.prototype.slice.call(arguments),e.unshift(MEDIA_CONSTRAINTS),getUserMedia(recursive.apply(void 0,e),function(e){d=e,o()},t)}if(!(this instanceof WebRtcPeer))return new WebRtcPeer(e,n,t);WebRtcPeer.super_.call(this),n instanceof Function&&(t=n,n=void 0),n=n||{},t=(t||noop).bind(this);var a=n.localVideo,s=n.remoteVideo,d=n.videoStream,l=n.audioStream,u=n.mediaConstraints,f=n.connectionConstraints,p=n.peerConnection,v=n.sendSource||"webcam",h=uuid.v4(),g=recursive({iceServers:freeice()},n.configuration),b=n.onstreamended;b&&this.on("streamended",b);var m=n.onicecandidate;m&&this.on("icecandidate",m);var R=n.oncandidategatheringdone;R&&this.on("candidategatheringdone",R),p||(p=new RTCPeerConnection(g)),Object.defineProperties(this,{peerConnection:{get:function(){return p}},remoteVideo:{get:function(){return s}},localVideo:{get:function(){return a}},currentFrame:{get:function(){if(s){if(s.readyState<s.HAVE_CURRENT_DATA)throw new Error("No video stream data available");var e=document.createElement("canvas");return e.width=s.videoWidth,e.height=s.videoHeight,e.getContext("2d").drawImage(s,0,0),e}}}});var S=this,P=[],C=!1;p.addEventListener("icecandidate",function(e){var n=e.candidate;EventEmitter.listenerCount(S,"icecandidate")||EventEmitter.listenerCount(S,"candidategatheringdone")?n?(S.emit("icecandidate",n),C=!1):C||(S.emit("candidategatheringdone"),C=!0):C||(P.push(n),n||(C=!0))}),this.on("newListener",function(e,n){if("icecandidate"===e||"candidategatheringdone"===e)for(;P.length;){var t=P.shift();!t==("candidategatheringdone"===e)&&n(t)}});var w=bufferizeCandidates(p);this.addIceCandidate=function(e,n){var t=new RTCIceCandidate(e);console.log("ICE candidate received"),n=(n||noop).bind(this),w(t,n)},this.generateOffer=function(n){n=n.bind(this);var t=parser.getBrowser(),i="Firefox"===t.name&&t.version>34?{offerToReceiveAudio:"sendonly"!==e,offerToReceiveVideo:"sendonly"!==e}:{mandatory:{OfferToReceiveAudio:"sendonly"!==e,OfferToReceiveVideo:"sendonly"!==e},optional:[{DtlsSrtpKeyAgreement:!0}]},r=recursive(i,f);console.log("constraints: "+JSON.stringify(r)),p.createOffer(function(e){console.log("Created SDP offer"),p.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp,S.processAnswer.bind(S))},n)},n,r)},this.getLocalSessionDescriptor=function(){return p.localDescription},this.getRemoteSessionDescriptor=function(){return p.remoteDescription},this.showLocalVideo=function(){a.src=URL.createObjectURL(d),a.muted=!0},this.processAnswer=function(e,n){n=(n||noop).bind(this);var t=new RTCSessionDescription({type:"answer",sdp:e});return console.log("SDP answer received, setting remote description"),"closed"===p.signalingState?n("PeerConnection is closed"):void p.setRemoteDescription(t,function(){i(),n()},n)},this.processOffer=function(e,n){n=n.bind(this);var t=new RTCSessionDescription({type:"offer",sdp:e});return console.log("SDP offer received, setting remote description"),"closed"===p.signalingState?n("PeerConnection is closed"):void p.setRemoteDescription(t,function(){i(),p.createAnswer(function(e){console.log("Created SDP answer"),p.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp)},n)},n)},n)},"recvonly"===e||d||l?setTimeout(o,0):"webcam"===v?c(u):getScreenConstraints(v,function(e,n){return e?t(e):void c(n,u)},h),this.on("_dispose",function(){a&&(a.pause(),a.src="",a.load()),s&&(s.pause(),s.src="",s.load()),S.removeAllListeners(),void 0!==window.cancelChooseDesktopMedia&&window.cancelChooseDesktopMedia(h)})}function createEnableDescriptor(e){var n="get"+e+"Tracks";return{enumerable:!0,get:function(){if(this.peerConnection){var e=this.peerConnection.getLocalStreams();if(e.length){for(var t,i=0;t=e[i];i++)for(var r,o=t[n](),c=0;r=o[c];c++)if(!r.enabled)return!1;return!0}}},set:function(e){function t(n){n.enabled=e}this.peerConnection.getLocalStreams().forEach(function(e){e[n]().forEach(t)})}}}function WebRtcPeerRecvonly(e,n){return this instanceof WebRtcPeerRecvonly?void WebRtcPeerRecvonly.super_.call(this,"recvonly",e,n):new WebRtcPeerRecvonly(e,n)}function WebRtcPeerSendonly(e,n){return this instanceof WebRtcPeerSendonly?void WebRtcPeerSendonly.super_.call(this,"sendonly",e,n):new WebRtcPeerSendonly(e,n)}function WebRtcPeerSendrecv(e,n){return this instanceof WebRtcPeerSendrecv?void WebRtcPeerSendrecv.super_.call(this,"sendrecv",e,n):new WebRtcPeerSendrecv(e,n)}var freeice=require("freeice"),inherits=require("inherits"),UAParser=require("ua-parser-js"),uuid=require("uuid"),EventEmitter=require("events").EventEmitter,recursive=require("merge").recursive.bind(void 0,!0);try{!function(){throw new Error("Cannot find module 'kurento-browser-extensions' from '/var/lib/jenkins/workspace/kurento-js-build-project/lib'")}()}catch(error){"undefined"==typeof getScreenConstraints&&(console.warn("screen sharing is not available"),getScreenConstraints=function(e,n){n(new Error("This library is not enabled for screen sharing"))})}var MEDIA_CONSTRAINTS={audio:!0,video:{mandatory:{maxWidth:640,maxFrameRate:15,minFrameRate:15}}},ua=window&&window.navigator?window.navigator.userAgent:"",parser=new UAParser(ua);inherits(WebRtcPeer,EventEmitter),Object.defineProperties(WebRtcPeer.prototype,{enabled:{enumerable:!0,get:function(){return this.audioEnabled&&this.videoEnabled},set:function(e){this.audioEnabled=this.videoEnabled=e}},audioEnabled:createEnableDescriptor("Audio"),videoEnabled:createEnableDescriptor("Video")}),WebRtcPeer.prototype.getLocalStream=function(e){return this.peerConnection?this.peerConnection.getLocalStreams()[e||0]:void 0},WebRtcPeer.prototype.getRemoteStream=function(e){return this.peerConnection?this.peerConnection.getRemoteStreams()[e||0]:void 0},WebRtcPeer.prototype.dispose=function(){console.log("Disposing WebRtcPeer");var e=this.peerConnection;if(e){if("closed"===e.signalingState)return;e.getLocalStreams().forEach(streamStop),e.close()}this.emit("_dispose")},inherits(WebRtcPeerRecvonly,WebRtcPeer),inherits(WebRtcPeerSendonly,WebRtcPeer),inherits(WebRtcPeerSendrecv,WebRtcPeer),exports.bufferizeCandidates=bufferizeCandidates,exports.WebRtcPeerRecvonly=WebRtcPeerRecvonly,exports.WebRtcPeerSendonly=WebRtcPeerSendonly,exports.WebRtcPeerSendrecv=WebRtcPeerSendrecv;
Example #9
0
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.kurentoUtils=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
 * (C) Copyright 2014 Kurento (http://kurento.org/)
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 */

var freeice = require('freeice');

/**
 * @description Default handler for error callbacks. The error messaged passed
 *              as argument is showed in a console, a div layer which should be
 *              previously created.
 *
 * @function defaultOnerror
 *
 * @param error -
 *            {String} Error message
 */
function defaultOnerror(error) {
	if (error)
		console.error(error);
}

function noop() {
};

/**
 * @classdesc Wrapper object of an RTCPeerConnection. This object is aimed to
 *            simplify the development of WebRTC-based applications.
 *
 * @constructor module:kurentoUtils.WebRtcPeer
 *
 * @param mode -
 *            {String} Mode in which the PeerConnection will be configured.
 *            Valid values are: 'recv', 'send', and 'sendRecv'
 * @param localVideo -
 *            {Object} Video tag for the local stream
 * @param remoteVideo -
 *            {Object} Video tag for the remote stream
 * @param onsdpoffer -
 *            {Function} Callback executed when a SDP offer has been generated
 * @param onerror -
 *            {Function} Callback executed when an error happens generating an
 *            SDP offer
 * @param videoStream -
 *            {Object} MediaStream to be used as primary source (typically video
 *            and audio, or only video if combined with audioStream) for
 *            localVideo and to be added as stream to the RTCPeerConnection
 * @param audioStream -
 *            {Object} MediaStream to be used as second source (typically for
 *            audio) for localVideo and to be added as stream to the
 *            RTCPeerConnection
 */
function WebRtcPeer(mode, localVideo, remoteVideo, onsdpoffer, onerror,
		videoStream, audioStream) {

	Object.defineProperty(this, 'pc', {
		writable : true
	});

	this.localVideo = localVideo;
	this.remoteVideo = remoteVideo;
	this.onerror = onerror || defaultOnerror;
	this.stream = videoStream;
	this.audioStream = audioStream;
	this.mode = mode;
	this.onsdpoffer = onsdpoffer || noop;
}

/**
 * @description This method creates the RTCPeerConnection object taking into
 *              account the properties received in the constructor. It starts
 *              the SDP negotiation process: generates the SDP offer and invokes
 *              the onsdpoffer callback. This callback is expected to send the
 *              SDP offer, in order to obtain an SDP answer from another peer.
 *
 * @function module:kurentoUtils.WebRtcPeer.prototype.start
 *
 */
WebRtcPeer.prototype.start = function(server, options) {

	var self = this;

	server  = server  || this.server;
	options = options || this.seoptionsrver;

	if (!this.pc) {
		this.pc = new RTCPeerConnection(server, options);
	}

	var pc = this.pc;

	if (this.stream && this.localVideo) {
		this.localVideo.src = URL.createObjectURL(this.stream);
		this.localVideo.muted = true;
	}

	if (this.stream) {
		pc.addStream(this.stream);
	}

	if (this.audioStream) {
		pc.addStream(this.audioStream);
	}

	this.constraints = {
		mandatory : {
			OfferToReceiveAudio : (this.remoteVideo !== undefined),
			OfferToReceiveVideo : (this.remoteVideo !== undefined)
		}
	};

	pc.createOffer(function(offer) {
		console.log('Created SDP offer');
		pc.setLocalDescription(offer, function() {
			console.log('Local description set');
		}, self.onerror);

	}, this.onerror, this.constraints);

	var ended = false;
	pc.onicecandidate = function(e) {
		// candidate exists in e.candidate
		if (e.candidate) {
			ended = false;
			return;
		}

		if (ended) {
			return;
		}

		var offerSdp = pc.localDescription.sdp;
		console.log('ICE negotiation completed');

		self.onsdpoffer(offerSdp, self);
		// self.emit('sdpoffer', offerSdp);

		ended = true;
	};
}

/**
 * @description This method frees the resources used by WebRtcPeer.
 *
 * @function module:kurentoUtils.WebRtcPeer.prototype.dispose
 */
WebRtcPeer.prototype.dispose = function() {
	console.log('Disposing WebRtcPeer');

	// FIXME This is not yet implemented in firefox
	// if (this.stream) this.pc.removeStream(this.stream);

	// For old browsers, PeerConnection.close() is NOT idempotent and raise
	// error. We check its signaling state and don't close it if it's already
	// closed
	if (this.pc && this.pc.signalingState != 'closed')
		this.pc.close();

	if (this.localVideo)
		this.localVideo.src = '';
	if (this.remoteVideo)
		this.remoteVideo.src = '';

	if (this.stream) {
		this.stream.getAudioTracks().forEach(function(track) {
			track.stop && track.stop()
		})
		this.stream.getVideoTracks().forEach(function(track) {
			track.stop && track.stop()
		})
	}
};

/**
 * @description Default user media constraints considered when invoking the
 *              getUserMedia function. These values are: maxWidth=640,
 *              maxFrameRate=15, minFrameRate=15.
 *
 * @alias module:kurentoUtils.WebRtcPeer.prototype.userMediaConstraints
 */
WebRtcPeer.prototype.userMediaConstraints = {
	audio : true,
	video : {
		mandatory : {
			maxWidth : 640,
			maxFrameRate : 15,
			minFrameRate : 15
		}
	}
};

/**
 * @description Callback function invoked when and SDP answer is received.
 *              Developers are expected to invoke this function in order to
 *              complete the SDP negotiation.
 *
 * @function module:kurentoUtils.WebRtcPeer.prototype.processSdpAnswer
 *
 * @param sdpAnswer -
 *            Description of sdpAnswer
 * @param successCallback -
 *            Called when the remoteDescription and the remoteVideo.src have
 *            been set successfully.
 */
WebRtcPeer.prototype.processSdpAnswer = function(sdpAnswer, successCallback) {
	var answer = new RTCSessionDescription({
		type : 'answer',
		sdp : sdpAnswer,
	});

	console.log('SDP answer received, setting remote description');
	var self = this;
	self.pc.setRemoteDescription(answer, function() {
		if (self.remoteVideo) {
			var stream = self.pc.getRemoteStreams()[0];
			self.remoteVideo.src = URL.createObjectURL(stream);
		}
		if (successCallback) {
			successCallback();
		}
	}, this.onerror);
}

/**
 * @description Default ICE server (stun:stun.l.google.com:19302).
 *
 * @alias module:kurentoUtils.WebRtcPeer.prototype.server
 */
WebRtcPeer.prototype.server = {
	iceServers : freeice()
};

/**
 * @description Default options (DtlsSrtpKeyAgreement=true) for
 *              RTCPeerConnection.
 *
 * @alias module:kurentoUtils.WebRtcPeer.prototype.options
 */
WebRtcPeer.prototype.options = {
	optional : [ {
		DtlsSrtpKeyAgreement : true
	} ]
};

/**
 * @description This method creates the WebRtcPeer object and obtain userMedia
 *              if needed.
 *
 * @function module:kurentoUtils.WebRtcPeer.start
 *
 * @param mode -
 *            {String} Mode in which the PeerConnection will be configured.
 *            Valid values are: 'recv', 'send', and 'sendRecv'
 * @param localVideo -
 *            {Object} Video tag for the local stream
 * @param remoteVideo -
 *            {Object} Video tag for the remote stream
 * @param onSdp -
 *            {Function} Callback executed when a SDP offer has been generated
 * @param onerror -
 *            {Function} Callback executed when an error happens generating an
 *            SDP offer
 * @param mediaConstraints -
 *            {Object[]} Constraints used to create RTCPeerConnection
 * @param videoStream -
 *            {Object} MediaStream to be used as primary source (typically video
 *            and audio, or only video if combined with audioStream) for
 *            localVideo and to be added as stream to the RTCPeerConnection
 * @param videoStream -
 *            {Object} MediaStream to be used as primary source (typically video
 *            and audio, or only video if combined with audioStream) for
 *            localVideo and to be added as stream to the RTCPeerConnection
 * @param audioStream -
 *            {Object} MediaStream to be used as second source (typically for
 *            audio) for localVideo and to be added as stream to the
 *            RTCPeerConnection
 *
 * @return {module:kurentoUtils.WebRtcPeer}
 */
WebRtcPeer.start = function(mode, localVideo, remoteVideo, onSdp, onerror,
		mediaConstraints, videoStream, audioStream, server, options) {
	var wp = new WebRtcPeer(mode, localVideo, remoteVideo, onSdp, onerror,
			videoStream, audioStream);

	if (wp.mode !== 'recv' && !wp.stream) {
		var constraints = mediaConstraints ? mediaConstraints
				: wp.userMediaConstraints;

		getUserMedia(constraints, function(userStream) {
			wp.stream = userStream;
			wp.start(server, options);
		}, wp.onerror);
	} else {
		wp.start(server, options);
	}

	return wp;
};

/**
 * @description This methods creates a WebRtcPeer to receive video.
 *
 * @function module:kurentoUtils.WebRtcPeer.startRecvOnly
 *
 * @param remoteVideo -
 *            {Object} Video tag for the remote stream
 * @param onSdp -
 *            {Function} Callback executed when a SDP offer has been generated
 * @param onerror -
 *            {Function} Callback executed when an error happens generating an
 *            SDP offer
 * @param mediaConstraints -
 *            {Object[]} Constraints used to create RTCPeerConnection
 *
 * @return {module:kurentoUtils.WebRtcPeer}
 */
WebRtcPeer.startRecvOnly = function(remoteVideo, onSdp, onError,
		mediaConstraints, server, options) {
	return WebRtcPeer.start('recv', null, remoteVideo, onSdp, onError,
			mediaConstraints, server, options);
};

/**
 * @description This methods creates a WebRtcPeer to send video.
 *
 * @function module:kurentoUtils.WebRtcPeer.startSendOnly
 *
 * @param localVideo -
 *            {Object} Video tag for the local stream
 * @param onSdp -
 *            {Function} Callback executed when a SDP offer has been generated
 * @param onerror -
 *            {Function} Callback executed when an error happens generating an
 *            SDP offer
 * @param mediaConstraints -
 *            {Object[]} Constraints used to create RTCPeerConnection
 *
 * @return {module:kurentoUtils.WebRtcPeer}
 */
WebRtcPeer.startSendOnly = function(localVideo, onSdp, onError,
		mediaConstraints, server, options) {
	return WebRtcPeer.start('send', localVideo, null, onSdp, onError,
			mediaConstraints, server, options);
};

/**
 * @description This methods creates a WebRtcPeer to send and receive video.
 *
 * @function module:kurentoUtils.WebRtcPeer.startSendRecv
 *
 * @param localVideo -
 *            {Object} Video tag for the local stream
 * @param remoteVideo -
 *            {Object} Video tag for the remote stream
 * @param onSdp -
 *            {Function} Callback executed when a SDP offer has been generated
 * @param onerror -
 *            {Function} Callback executed when an error happens generating an
 *            SDP offer
 * @param mediaConstraints -
 *            {Object[]} Constraints used to create RTCPeerConnection
 *
 * @return {module:kurentoUtils.WebRtcPeer}
 */
WebRtcPeer.startSendRecv = function(localVideo, remoteVideo, onSdp, onError,
		mediaConstraints, server, options) {
	return WebRtcPeer.start('sendRecv', localVideo, remoteVideo, onSdp,
			onError, mediaConstraints, server, options);
};

module.exports = WebRtcPeer;

},{"freeice":3}],2:[function(require,module,exports){
Example #10
0
var quickconnect = require('rtc-quickconnect');
var buffered = require('rtc-bufferedchannel');
var freeice = require('freeice');
var utils = require('./Utils.js');

/* Configuration */
BEMTV_ROOM_DISCOVER_URL = "http://server.bem.tv/room"
BEMTV_SERVER = "http://server.bem.tv:8080"
ICE_SERVERS = freeice();
DESIRE_TIMEOUT = 0.7; // in seconds
REQ_TIMEOUT = 3; // in seconds
MAX_CACHE_SIZE = 10;


/* Header protocol messages */
CHUNK_REQ = "req"
CHUNK_DESIRE = "des"
CHUNK_DESACK = "desack"
CHUNK_OFFER = "offer"

/* Peer States */
PEER_IDLE = 0
PEER_WAITING = 1
PEER_UPLOADING = 2
PEER_DOWNLOADING = 3
PEER_DESIRING = 4

var BemTV = function() {
  this._init();
}
function noop(e){e&&console.error(e)}function trackStop(e){e.stop&&e.stop()}function streamStop(e){e.getTracks().forEach(trackStop)}function bufferizeCandidates(e,n){var t=[];return e.addEventListener("signalingstatechange",function(){if("stable"===this.signalingState)for(;t.length;){var e=t.shift();this.addIceCandidate(e.candidate,e.callback,e.callback)}}),function(i,r){switch(r=r||n,e.signalingState){case"closed":r(new Error("PeerConnection object is closed"));break;case"stable":if(e.remoteDescription){e.addIceCandidate(i,r,r);break}default:t.push({candidate:i,callback:r})}}}function removeFIDFromOffer(e){var n=e.indexOf("a=ssrc-group:FID");return n>0?e.slice(0,n):e}function getSimulcastInfo(e){var n=e.getVideoTracks();if(!n.length)return console.warn("No video tracks available in the video stream"),"";var t=["a=x-google-flag:conference","a=ssrc-group:SIM 1 2 3","a=ssrc:1 cname:localVideo","a=ssrc:1 msid:"+e.id+" "+n[0].id,"a=ssrc:1 mslabel:"+e.id,"a=ssrc:1 label:"+n[0].id,"a=ssrc:2 cname:localVideo","a=ssrc:2 msid:"+e.id+" "+n[0].id,"a=ssrc:2 mslabel:"+e.id,"a=ssrc:2 label:"+n[0].id,"a=ssrc:3 cname:localVideo","a=ssrc:3 msid:"+e.id+" "+n[0].id,"a=ssrc:3 mslabel:"+e.id,"a=ssrc:3 label:"+n[0].id];return t.push(""),t.join("\n")}function WebRtcPeer(e,n,t){function i(){if(d){var e=h.getRemoteStreams()[0],n=e?URL.createObjectURL(e):"";d.pause(),d.src=n,d.load(),console.log("Remote URL:",n)}}function r(e){return C&&("Chrome"===w.name||"Chromium"===w.name?(console.log("Adding multicast info"),e=new RTCSessionDescription({type:e.type,sdp:removeFIDFromOffer(e.sdp)+getSimulcastInfo(l)})):console.warn("Simulcast is only available in Chrome browser.")),e}function o(){P.emit("streamended",this)}function a(){"closed"===h.signalingState&&t('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'),l&&c&&P.showLocalVideo(),l&&(l.addEventListener("ended",o),h.addStream(l)),u&&(u.addEventListener("ended",o),h.addStream(u));var n=parser.getBrowser();"sendonly"!==e||"Chrome"!==n.name&&"Chromium"!==n.name||39!==n.major||(e="sendrecv"),t()}function s(e){void 0===e&&(e=MEDIA_CONSTRAINTS),getUserMedia(e,function(e){l=e,a()},t)}if(!(this instanceof WebRtcPeer))return new WebRtcPeer(e,n,t);WebRtcPeer.super_.call(this),n instanceof Function&&(t=n,n=void 0),n=n||{},t=(t||noop).bind(this);var c=n.localVideo,d=n.remoteVideo,l=n.videoStream,u=n.audioStream,f=n.mediaConstraints,v=n.connectionConstraints,h=n.peerConnection,p=n.sendSource||"webcam",g=uuid.v4(),b=recursive({iceServers:freeice()},n.configuration),m=n.onstreamended;m&&this.on("streamended",m);var R=n.onicecandidate;R&&this.on("icecandidate",R);var S=n.oncandidategatheringdone;S&&this.on("candidategatheringdone",S);var w=parser.getBrowser(),C=n.simulcast;h||(h=new RTCPeerConnection(b)),Object.defineProperties(this,{peerConnection:{get:function(){return h}},id:{value:n.id||g,writable:!1},remoteVideo:{get:function(){return d}},localVideo:{get:function(){return c}},currentFrame:{get:function(){if(d){if(d.readyState<d.HAVE_CURRENT_DATA)throw new Error("No video stream data available");var e=document.createElement("canvas");return e.width=d.videoWidth,e.height=d.videoHeight,e.getContext("2d").drawImage(d,0,0),e}}}});var P=this,y=[],E=!1;h.addEventListener("icecandidate",function(e){var n=e.candidate;EventEmitter.listenerCount(P,"icecandidate")||EventEmitter.listenerCount(P,"candidategatheringdone")?n?(P.emit("icecandidate",n),E=!1):E||(P.emit("candidategatheringdone"),E=!0):E||(y.push(n),n||(E=!0))}),h.onaddstream=n.onaddstream,h.onnegotiationneeded=n.onnegotiationneeded,this.on("newListener",function(e,n){if("icecandidate"===e||"candidategatheringdone"===e)for(;y.length;){var t=y.shift();!t==("candidategatheringdone"===e)&&n(t)}});var W=bufferizeCandidates(h);this.addIceCandidate=function(e,n){var t=new RTCIceCandidate(e);console.log("ICE candidate received"),n=(n||noop).bind(this),W(t,n)},this.generateOffer=function(n){n=n.bind(this);var t=!0,i=!0;f&&(t="boolean"==typeof f.audio?f.audio:!0,i="boolean"==typeof f.video?f.video:!0);var o="Firefox"===w.name&&w.version>34?{offerToReceiveAudio:"sendonly"!==e&&t,offerToReceiveVideo:"sendonly"!==e&&i}:{mandatory:{OfferToReceiveAudio:"sendonly"!==e&&t,OfferToReceiveVideo:"sendonly"!==e&&i},optional:[{DtlsSrtpKeyAgreement:!0}]},a=recursive(o,v);console.log("constraints: "+JSON.stringify(a)),h.createOffer(function(e){console.log("Created SDP offer"),e=r(e),h.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp,P.processAnswer.bind(P))},n)},n,a)},this.getLocalSessionDescriptor=function(){return h.localDescription},this.getRemoteSessionDescriptor=function(){return h.remoteDescription},this.showLocalVideo=function(){c.src=URL.createObjectURL(l),c.muted=!0},this.processAnswer=function(e,n){n=(n||noop).bind(this);var t=new RTCSessionDescription({type:"answer",sdp:e});return console.log("SDP answer received, setting remote description"),"closed"===h.signalingState?n("PeerConnection is closed"):void h.setRemoteDescription(t,function(){i(),n()},n)},this.processOffer=function(e,n){n=n.bind(this);var t=new RTCSessionDescription({type:"offer",sdp:e});return console.log("SDP offer received, setting remote description"),"closed"===h.signalingState?n("PeerConnection is closed"):void h.setRemoteDescription(t,function(){i(),h.createAnswer(function(e){e=r(e),console.log("Created SDP answer"),h.setLocalDescription(e,function(){console.log("Local description set",e.sdp),n(null,e.sdp)},n)},n)},n)},"recvonly"===e||l||u?setTimeout(a,0):"webcam"===p?s(f):getScreenConstraints(p,function(e,n){return e?t(e):(constraints=[f],constraints.unshift(n),void s(recursive.apply(void 0,constraints)))},g),this.on("_dispose",function(){c&&(c.pause(),c.src="",c.load()),d&&(d.pause(),d.src="",d.load()),P.removeAllListeners(),void 0!==window.cancelChooseDesktopMedia&&window.cancelChooseDesktopMedia(g)})}function createEnableDescriptor(e){var n="get"+e+"Tracks";return{enumerable:!0,get:function(){if(this.peerConnection){var e=this.peerConnection.getLocalStreams();if(e.length){for(var t,i=0;t=e[i];i++)for(var r,o=t[n](),a=0;r=o[a];a++)if(!r.enabled)return!1;return!0}}},set:function(e){function t(n){n.enabled=e}this.peerConnection.getLocalStreams().forEach(function(e){e[n]().forEach(t)})}}}function WebRtcPeerRecvonly(e,n){return this instanceof WebRtcPeerRecvonly?void WebRtcPeerRecvonly.super_.call(this,"recvonly",e,n):new WebRtcPeerRecvonly(e,n)}function WebRtcPeerSendonly(e,n){return this instanceof WebRtcPeerSendonly?void WebRtcPeerSendonly.super_.call(this,"sendonly",e,n):new WebRtcPeerSendonly(e,n)}function WebRtcPeerSendrecv(e,n){return this instanceof WebRtcPeerSendrecv?void WebRtcPeerSendrecv.super_.call(this,"sendrecv",e,n):new WebRtcPeerSendrecv(e,n)}function harkUtils(e,n){return hark(e,n)}var freeice=require("freeice"),inherits=require("inherits"),UAParser=require("ua-parser-js"),uuid=require("uuid"),hark=require("hark"),EventEmitter=require("events").EventEmitter,recursive=require("merge").recursive.bind(void 0,!0);try{require("kurento-browser-extensions")}catch(error){"undefined"==typeof getScreenConstraints&&(console.warn("screen sharing is not available"),getScreenConstraints=function(e,n){n(new Error("This library is not enabled for screen sharing"))})}var MEDIA_CONSTRAINTS={audio:!0,video:{width:640,framerate:15}},ua=window&&window.navigator?window.navigator.userAgent:"",parser=new UAParser(ua);inherits(WebRtcPeer,EventEmitter),Object.defineProperties(WebRtcPeer.prototype,{enabled:{enumerable:!0,get:function(){return this.audioEnabled&&this.videoEnabled},set:function(e){this.audioEnabled=this.videoEnabled=e}},audioEnabled:createEnableDescriptor("Audio"),videoEnabled:createEnableDescriptor("Video")}),WebRtcPeer.prototype.getLocalStream=function(e){return this.peerConnection?this.peerConnection.getLocalStreams()[e||0]:void 0},WebRtcPeer.prototype.getRemoteStream=function(e){return this.peerConnection?this.peerConnection.getRemoteStreams()[e||0]:void 0},WebRtcPeer.prototype.dispose=function(){console.log("Disposing WebRtcPeer");var e=this.peerConnection;try{if(e){if("closed"===e.signalingState)return;e.getLocalStreams().forEach(streamStop),e.close()}}catch(n){console.warn("Exception disposing webrtc peer "+n)}this.emit("_dispose")},inherits(WebRtcPeerRecvonly,WebRtcPeer),inherits(WebRtcPeerSendonly,WebRtcPeer),inherits(WebRtcPeerSendrecv,WebRtcPeer),exports.bufferizeCandidates=bufferizeCandidates,exports.WebRtcPeerRecvonly=WebRtcPeerRecvonly,exports.WebRtcPeerSendonly=WebRtcPeerSendonly,exports.WebRtcPeerSendrecv=WebRtcPeerSendrecv,exports.hark=harkUtils;
Example #12
0
var _getDefaultTransports = function () {
  var transports = []
  // udp transport
  if (UdpTransport.isCompatibleWithRuntime()) {
    _log.debug('using UDP transport')
    transports.push(new UdpTransport())
  }
  // tcp transport
  if (TcpTransport.isCompatibleWithRuntime()) {
    _log.debug('using TCP transport')
    transports.push(new TcpTransport())
  }
  // turn transport
  if (config.turnAddr !== undefined &&
    config.turnPort !== undefined &&
    config.turnUser !== undefined &&
    config.turnPass !== undefined &&
    config.onetpRegistrar !== undefined
  ) {
    // turn+tcp
    var turnConfig = {
      turnServer: config.turnAddr,
      turnPort: parseInt(config.turnPort),
      turnUsername: config.turnUser,
      turnPassword: config.turnPass,
      turnProtocol: new TurnProtocols.TCP(),
      signaling: new WebSocketSignaling({
        url: config.onetpRegistrar
      })
    }
    if (TurnTransport.isCompatibleWithRuntime(turnConfig)) {
      _log.debug('using TURN+TCP transport')
      transports.push(new TurnTransport(turnConfig))
    } else {
      // turn+udp
      turnConfig.turnProtocol = new TurnProtocols.UDP()
      if (TurnTransport.isCompatibleWithRuntime(turnConfig)) {
        _log.debug('using TURN+UDP transport')
        transports.push(new TurnTransport(turnConfig))
      }
    }
  }
  // webrtc transport
  if (WebRtcTransport.isCompatibleWithRuntime() &&
    config.onetpRegistrar !== undefined
  ) {
    // add stun servers
    var iceServers = freeice()
    // add turn servers
    if (config.turnAddr !== undefined &&
      config.turnPort !== undefined &&
      config.turnUser !== undefined &&
      config.turnPass !== undefined
    ) {
      var turnUrl = {
        url: 'turn:' + config.turnAddr + ':' + config.turnPort,
        username: config.turnUser,
        credential: config.turnPass
      }
      iceServers.push(turnUrl)
    }
    var webrtcConfig = {
      config: { iceServers: iceServers },
      signaling: new WebSocketSignaling({
        url: config.onetpRegistrar
      })
    }
    _log.debug('using WebRtc transport')
    transports.push(new WebRtcTransport(webrtcConfig))
  }
  return transports
}
Example #13
0
/**
 * Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
 * development of WebRTC-based applications.
 *
 * @constructor module:kurentoUtils.WebRtcPeer
 *
 * @param {String} mode Mode in which the PeerConnection will be configured.
 *  Valid values are: 'recv', 'send', and 'sendRecv'
 * @param localVideo Video tag for the local stream
 * @param remoteVideo Video tag for the remote stream
 * @param {MediaStream} videoStream Stream to be used as primary source
 *  (typically video and audio, or only video if combined with audioStream) for
 *  localVideo and to be added as stream to the RTCPeerConnection
 * @param {MediaStream} audioStream Stream to be used as second source
 *  (typically for audio) for localVideo and to be added as stream to the
 *  RTCPeerConnection
 */
function WebRtcPeer(mode, options, callback) {
  if (!(this instanceof WebRtcPeer))
    return new WebRtcPeer(mode, options, callback)

  WebRtcPeer.super_.call(this)

  if (options instanceof Function) {
    callback = options
    options = undefined
  }

  options = options || {}
  callback = (callback || noop).bind(this)

  var localVideo = options.localVideo;
  var remoteVideo = options.remoteVideo;
  var videoStream = options.videoStream;
  var audioStream = options.audioStream;
  var mediaConstraints = options.mediaConstraints;

  var connectionConstraints = options.connectionConstraints;
  var pc = options.peerConnection
  var sendSource = options.sendSource || 'webcam'

  var configuration = recursive({
      iceServers: freeice()
    },
    options.configuration);

  var onicecandidate = options.onicecandidate;
  if (onicecandidate) this.on('icecandidate', onicecandidate);

  var oncandidategatheringdone = options.oncandidategatheringdone;
  if (oncandidategatheringdone) this.on('candidategatheringdone',
    oncandidategatheringdone);

  // Init PeerConnection

  if (!pc) pc = new RTCPeerConnection(configuration);

  Object.defineProperty(this, 'peerConnection', {
    get: function () {
      return pc;
    }
  });

  /**
   * @member {(external:ImageData|undefined)} currentFrame
   */
  Object.defineProperty(this, 'currentFrame', {
    get: function () {
      // [ToDo] Find solution when we have a remote stream but we didn't set
      // a remoteVideo tag
      if (!remoteVideo) return;

      if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
        throw new Error('No video stream data available')

      var canvas = document.createElement('canvas')
      canvas.width = remoteVideo.videoWidth
      canvas.height = remoteVideo.videoHeight

      canvas.getContext('2d').drawImage(remoteVideo, 0, 0)

      return canvas
    }
  });

  var self = this;

  var candidategatheringdone = false
  pc.addEventListener('icecandidate', function (event) {
    var candidate = event.candidate
    if (candidate) {
      self.emit('icecandidate', candidate);
      candidategatheringdone = false
    } else if (!candidategatheringdone) {
      self.emit('candidategatheringdone');
      candidategatheringdone = true
    }
  });

  var candidatesQueue = []

  /**
   * Callback function invoked when an ICE candidate is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
   *
   * @param iceCandidate - Literal object with the ICE candidate description
   * @param callback - Called when the ICE candidate has been added.
   */
  this.addIceCandidate = function (iceCandidate, callback) {
    var candidate = new RTCIceCandidate(iceCandidate);

    console.log('ICE candidate received');

    callback = (callback || noop).bind(this)

    switch (pc.signalingState) {
    case 'closed':
      Callback(new Error('PeerConnection object is closed'))
      break

    case 'stable':
      if (pc.remoteDescription) {
        pc.addIceCandidate(candidate, callback, callback);
        break;
      }

    default:
      candidatesQueue.push({
        candidate: candidate,
        callback: callback
      })
    }
  }

  pc.addEventListener('signalingstatechange', function () {
    if (this.signalingState == 'stable')
      while (candidatesQueue.length) {
        var entry = candidatesQueue.shift()

        this.addIceCandidate(entry.candidate, entry.callback, entry.callback);
      }
  })

  this.generateOffer = function (callback) {
    callback = callback.bind(this)

    var constraints = recursive({
      offerToReceiveAudio: (mode !== 'sendonly'),
      offerToReceiveVideo: (mode !== 'sendonly')
    }, connectionConstraints);

    console.log('constraints: ' + JSON.stringify(constraints));

    pc.createOffer(function (offer) {
        console.log('Created SDP offer');

        pc.setLocalDescription(offer, function () {
            console.log('Local description set', offer.sdp);

            callback(null, offer.sdp, self.processAnswer.bind(self));
          },
          callback);
      },
      callback, constraints);
  }

  this.getLocalSessionDescriptor = function () {
    return pc.localDescription
  }

  this.getRemoteSessionDescriptor = function () {
    return pc.remoteDescription
  }

  function setRemoteVideo() {
    if (remoteVideo) {
      var stream = pc.getRemoteStreams()[0]
      var url = stream ? URL.createObjectURL(stream) : "";

      remoteVideo.src = url;

      console.log('Remote URL:', url)
    }
  }

  /**
   * Callback function invoked when a SDP answer is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
   *
   * @param sdpAnswer - Description of sdpAnswer
   * @param callback - Called when the remote description has been set
   *  successfully.
   */
  this.processAnswer = function (sdpAnswer, callback) {
    callback = (callback || noop).bind(this)

    var answer = new RTCSessionDescription({
      type: 'answer',
      sdp: sdpAnswer,
    });

    console.log('SDP answer received, setting remote description');

    if (pc.signalingState == 'closed')
      return callback('PeerConnection is closed')

    pc.setRemoteDescription(answer, function () {
        setRemoteVideo()

        callback();
      },
      callback);
  }

  /**
   * Callback function invoked when a SDP offer is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
   *
   * @param sdpOffer - Description of sdpOffer
   * @param callback - Called when the remote description has been set
   *  successfully.
   */
  this.processOffer = function (sdpOffer, callback) {
    callback = callback.bind(this)

    var offer = new RTCSessionDescription({
      type: 'offer',
      sdp: sdpOffer,
    });

    console.log('SDP offer received, setting remote description');

    if (pc.signalingState == 'closed')
      return callback('PeerConnection is closed')

    pc.setRemoteDescription(offer, function () {
        setRemoteVideo()

        // Generate answer

        var constraints = recursive({
          //        offerToReceiveAudio: (mode !== 'sendonly'),
          //        offerToReceiveVideo: (mode !== 'sendonly')
        }, connectionConstraints);

        console.log('constraints: ' + JSON.stringify(constraints));

        pc.createAnswer(function (answer) {
            console.log('Created SDP answer');

            pc.setLocalDescription(answer, function () {
                console.log('Local description set', answer.sdp);

                callback(null, answer.sdp);
              },
              callback);
          },
          callback, constraints);
      },
      callback);
  }

  /**
   * This function creates the RTCPeerConnection object taking into account the
   * properties received in the constructor. It starts the SDP negotiation
   * process: generates the SDP offer and invokes the onsdpoffer callback. This
   * callback is expected to send the SDP offer, in order to obtain an SDP
   * answer from another peer.
   */
  function start() {
    if (videoStream && localVideo) {
      localVideo.src = URL.createObjectURL(videoStream);
      localVideo.muted = true;
    }

    if (videoStream) pc.addStream(videoStream);
    if (audioStream) pc.addStream(audioStream);

    // [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
    if (mode == 'sendonly') mode = 'sendrecv';

    //    // Create the offer with the required constraints
    //        self.generateOffer(callback)
    callback()
  }

  if (mode !== 'recvonly' && !videoStream && !audioStream) {
    function getMedia(constraints) {
      getUserMedia(recursive(MEDIA_CONSTRAINTS, constraints), function (
          stream) {
          videoStream = stream;

          start()
        },
        callback);
    }

    if (sendSource != 'webcam' && !mediaConstraints)
      getScreenConstraints(sendMode, function (error, constraints) {
        if (error) return callback(error)

        getMedia(constraints)
      })
    else
      getMedia(mediaConstraints)
  } else {
    setTimeout(start, 0)
  }

  this.on('_dispose', function () {
    if (localVideo) localVideo.src = '';
    if (remoteVideo) remoteVideo.src = '';
  })
}
/**
 * Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
 * development of WebRTC-based applications.
 *
 * @constructor module:kurentoUtils.WebRtcPeer
 *
 * @param {String} mode Mode in which the PeerConnection will be configured.
 *  Valid values are: 'recv', 'send', and 'sendRecv'
 * @param localVideo Video tag for the local stream
 * @param remoteVideo Video tag for the remote stream
 * @param {MediaStream} videoStream Stream to be used as primary source
 *  (typically video and audio, or only video if combined with audioStream) for
 *  localVideo and to be added as stream to the RTCPeerConnection
 * @param {MediaStream} audioStream Stream to be used as second source
 *  (typically for audio) for localVideo and to be added as stream to the
 *  RTCPeerConnection
 */
function WebRtcPeer(mode, options, callback) {
  if (!(this instanceof WebRtcPeer)) {
    return new WebRtcPeer(mode, options, callback)
  }

  WebRtcPeer.super_.call(this)

  if (options instanceof Function) {
    callback = options
    options = undefined
  }

  options = options || {}
  callback = (callback || noop).bind(this)

  var localVideo = options.localVideo
  var remoteVideo = options.remoteVideo
  var videoStream = options.videoStream
  var audioStream = options.audioStream
  var mediaConstraints = options.mediaConstraints

  var connectionConstraints = options.connectionConstraints
  var pc = options.peerConnection
  var sendSource = options.sendSource || 'webcam'

  var guid = uuid.v4()
  var configuration = recursive({
      iceServers: freeice()
    },
    options.configuration)

  var onstreamended = options.onstreamended
  if (onstreamended) this.on('streamended', onstreamended)

  var onicecandidate = options.onicecandidate
  if (onicecandidate) this.on('icecandidate', onicecandidate)

  var oncandidategatheringdone = options.oncandidategatheringdone
  if (oncandidategatheringdone) {
    this.on('candidategatheringdone', oncandidategatheringdone)
  }

  var simulcast = options.simulcast

  // Init PeerConnection

  if (!pc) pc = new RTCPeerConnection(configuration)

  Object.defineProperties(this, {
    'peerConnection': {
      get: function () {
        return pc
      }
    },

    'remoteVideo': {
      get: function () {
        return remoteVideo
      }
    },

    'localVideo': {
      get: function () {
        return localVideo
      }
    },

    /**
     * @member {(external:ImageData|undefined)} currentFrame
     */
    'currentFrame': {
      get: function () {
        // [ToDo] Find solution when we have a remote stream but we didn't set
        // a remoteVideo tag
        if (!remoteVideo) return;

        if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
          throw new Error('No video stream data available')

        var canvas = document.createElement('canvas')
        canvas.width = remoteVideo.videoWidth
        canvas.height = remoteVideo.videoHeight

        canvas.getContext('2d').drawImage(remoteVideo, 0, 0)

        return canvas
      }
    }
  })

  var self = this

  var candidatesQueueOut = []

  var candidategatheringdone = false
  pc.onicecandidate = function(event){
    var candidate = event.candidate

    if (EventEmitter.listenerCount(self, 'icecandidate') ||
        EventEmitter.listenerCount(
            self, 'candidategatheringdone')) {
      if (candidate) {
        self.emit('icecandidate', candidate)
        candidategatheringdone = false
      } else if (!candidategatheringdone) {
        self.emit('candidategatheringdone')
        candidategatheringdone = true
      }
    } else if (!candidategatheringdone) {
      // Not listening to 'icecandidate' or 'candidategatheringdone' events, queue
      // the candidate until one of them is listened
      candidatesQueueOut.push(candidate)

      if (!candidate) candidategatheringdone = true
    }
  };

  this.on('newListener', function (event, listener) {
    if (event === 'icecandidate' || event === 'candidategatheringdone') {
      while (candidatesQueueOut.length) {
        var candidate = candidatesQueueOut.shift()

        if (!candidate === (event === 'candidategatheringdone')) {
          listener(candidate)
        }
      }
    }
  })

  var addIceCandidate = bufferizeCandidates(pc)

  /**
   * Callback function invoked when an ICE candidate is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
   *
   * @param iceCandidate - Literal object with the ICE candidate description
   * @param callback - Called when the ICE candidate has been added.
   */
  this.addIceCandidate = function (iceCandidate, callback) {
    var candidate = new RTCIceCandidate(iceCandidate)

    console.log('ICE candidate received')

    callback = (callback || noop).bind(this)

    addIceCandidate(candidate, callback)
  }

  this.generateOffer = function (callback) {
    callback = callback.bind(this)

    var browser = parser.getBrowser()

    var offerAudio = true
    var offerVideo = true
      // Constraints must have both blocks
    if (mediaConstraints) {
      offerAudio = (typeof mediaConstraints.audio === 'boolean') ?
        mediaConstraints.audio : true
      offerVideo = (typeof mediaConstraints.video === 'boolean') ?
        mediaConstraints.video : true
    }

    var browserDependantConstraints = (browser.name === 'Firefox' &&
      browser.version > 34) ? {
      offerToReceiveAudio: (mode !== 'sendonly' && offerAudio),
      offerToReceiveVideo: (mode !== 'sendonly' && offerVideo)
    } : {
      mandatory: {
        OfferToReceiveAudio: (mode !== 'sendonly' && offerAudio),
        OfferToReceiveVideo: (mode !== 'sendonly' && offerVideo)
      },
      optional: [{
        DtlsSrtpKeyAgreement: true
      }]
    }
    var constraints = recursive(browserDependantConstraints,
      connectionConstraints)

    console.log('constraints: ' + JSON.stringify(constraints))

    pc.createOffer(function (offer) {
        console.log('Created SDP offer')

        if (simulcast) {
          if ((browser.name === 'Chrome' || browser.name === 'Chromium')) {
            console.log('Adding multicast info')

            offer = new RTCSessionDescription({
              'type': offer.type,
              'sdp': removeFIDFromOffer(offer.sdp) + getSimulcastInfo(
                videoStream)
            });
          } else {
            console.warn('Simulcast is only available in Chrome browser.');
          }
        }

        pc.setLocalDescription(offer, function () {
            console.log('Local description set', offer.sdp)

            callback(null, offer.sdp, self.processAnswer.bind(self))
          },
          callback)
      },
      callback, constraints)
  }

  this.getLocalSessionDescriptor = function () {
    return pc.localDescription
  }

  this.getRemoteSessionDescriptor = function () {
    return pc.remoteDescription
  }

  function setRemoteVideo() {
    if (remoteVideo) {
      var stream = pc.getRemoteStreams()[0];
      var url = stream;
      attachMediaStream(remoteVideo, stream);
      console.log('Remote URL:', url);
    }
  }

  this.showLocalVideo = function () {
    attachMediaStream(localVideo, videoStream);
  }

  /**
   * Callback function invoked when a SDP answer is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
   *
   * @param sdpAnswer - Description of sdpAnswer
   * @param callback - Called when the remote description has been set
   *  successfully.
   */
  this.processAnswer = function (sdpAnswer, callback) {
    callback = (callback || noop).bind(this)

    var answer = new RTCSessionDescription({
      type: 'answer',
      sdp: sdpAnswer
    })

    console.log('SDP answer received, setting remote description')

    if (pc.signalingState === 'closed') {
      return callback('PeerConnection is closed')
    }

    pc.setRemoteDescription(answer, function () {
        setRemoteVideo()

        callback()
      },
      callback)
  }

  /**
   * Callback function invoked when a SDP offer is received. Developers are
   * expected to invoke this function in order to complete the SDP negotiation.
   *
   * @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
   *
   * @param sdpOffer - Description of sdpOffer
   * @param callback - Called when the remote description has been set
   *  successfully.
   */
  this.processOffer = function (sdpOffer, callback) {
    callback = callback.bind(this)

    var offer = new RTCSessionDescription({
      type: 'offer',
      sdp: sdpOffer
    })

    console.log('SDP offer received, setting remote description')

    if (pc.signalingState === 'closed') {
      return callback('PeerConnection is closed')
    }

    pc.setRemoteDescription(offer, function () {
        setRemoteVideo()

        // Generate answer

        pc.createAnswer(function (answer) {
            console.log('Created SDP answer')

            pc.setLocalDescription(answer, function () {
                console.log('Local description set', answer.sdp)

                callback(null, answer.sdp)
              },
              callback)
          },
          callback)
      },
      callback)
  }

  function streamEndedListener() {
    self.emit('streamended', this)
  }

  /**
   * This function creates the RTCPeerConnection object taking into account the
   * properties received in the constructor. It starts the SDP negotiation
   * process: generates the SDP offer and invokes the onsdpoffer callback. This
   * callback is expected to send the SDP offer, in order to obtain an SDP
   * answer from another peer.
   */
  function start() {
    if (pc.signalingState === 'closed') {
      callback(
        'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'
      )
    }

    if (videoStream && localVideo) {
      self.showLocalVideo()
    }

    if (videoStream) {
      videoStream.onended = function(event){
        streamEndedListener();
      };
      pc.addStream(videoStream)
    }

    if (audioStream) {
      audioStream.onended = function(event){
        streamEndedListener();
      };
      pc.addStream(audioStream)
    }

    // [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
    var browser = parser.getBrowser()
    if (mode === 'sendonly' &&
      (browser.name === 'Chrome' || browser.name === 'Chromium') &&
      browser.major === 39) {
      mode = 'sendrecv'
    }

    callback()
  }

  if (mode !== 'recvonly' && !videoStream && !audioStream) {
    function getMedia(constraints) {
      if (constraints === undefined) {
        constraints = MEDIA_CONSTRAINTS
      }
      getUserMedia(constraints, function (stream) {
        videoStream = stream
        start()
      }, callback)
    }
    if (sendSource === 'webcam') {
      getMedia(mediaConstraints)
    } else {
      getScreenConstraints(sendSource, function (error, constraints_) {
        if (error)
          return callback(error)

        constraints = [mediaConstraints]
        constraints.unshift(constraints_)
        getMedia(recursive.apply(undefined, constraints))
      }, guid)
    }
  } else {
    setTimeout(start, 0)
  }

  this.on('_dispose', function () {
    if (localVideo) {
      localVideo.pause()
      localVideo.src = ''
      localVideo.load()
    }
    if (remoteVideo) {
      remoteVideo.pause()
      remoteVideo.src = ''
      remoteVideo.load()
    }
    self.removeAllListeners()

    if (window.cancelChooseDesktopMedia !== undefined) {
      window.cancelChooseDesktopMedia(guid)
    }
  })
}