Browse Source

Merge pull request #293 from fancycode/conference_refactoring

Major refactoring of call / conference handling.
pull/295/head
Joachim Bauch 9 years ago committed by GitHub
parent
commit
c6354b8a14
  1. 10
      static/js/controllers/statusmessagecontroller.js
  2. 30
      static/js/controllers/uicontroller.js
  3. 7
      static/js/directives/buddylist.js
  4. 33
      static/js/mediastream/peercall.js
  5. 250
      static/js/mediastream/peerconference.js
  6. 6
      static/js/mediastream/peerconnection.js
  7. 712
      static/js/mediastream/webrtc.js

10
static/js/controllers/statusmessagecontroller.js

@ -25,8 +25,8 @@ define([], function() {
// StatusmessageController // StatusmessageController
return ["$scope", "mediaStream", function($scope, mediaStream) { return ["$scope", "mediaStream", function($scope, mediaStream) {
$scope.doHangup = function() { $scope.doHangup = function(reason, id) {
mediaStream.webrtc.doHangup(); mediaStream.webrtc.doHangup(reason, id);
} }
$scope.doAbort = function() { $scope.doAbort = function() {
mediaStream.webrtc.doHangup("abort", $scope.dialing); mediaStream.webrtc.doHangup("abort", $scope.dialing);
@ -35,10 +35,10 @@ define([], function() {
mediaStream.connector.reconnect(); mediaStream.connector.reconnect();
} }
$scope.doAccept = function() { $scope.doAccept = function() {
mediaStream.webrtc.doAccept(); mediaStream.webrtc.doAccept($scope.incoming);
} }
$scope.doReject = function() { $scope.doReject = function(id) {
mediaStream.webrtc.doHangup('reject'); mediaStream.webrtc.doHangup('reject', id, $scope.incoming);
} }
}]; }];

30
static/js/controllers/uicontroller.js

@ -206,10 +206,11 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
$scope.updatePeerFromConference = function() { $scope.updatePeerFromConference = function() {
if (!$scope.conferenceObject) { if (!$scope.conferenceObject) {
$scope.conferencePeers.length = 0;
return; return;
} }
var peerIds = $scope.conferenceObject.peerIds(); var peerIds = $scope.conferenceObject.getCallIds();
if ($scope.peer && peerIds.indexOf($scope.peer) === -1) { if ($scope.peer && peerIds.indexOf($scope.peer) === -1) {
$scope.peer = null; $scope.peer = null;
} }
@ -229,7 +230,7 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
return; return;
} }
if ($scope.conference) { if ($scope.conference || $scope.isConferenceRoom()) {
$scope.setStatus("conference"); $scope.setStatus("conference");
} else { } else {
$scope.setStatus("connected"); $scope.setStatus("connected");
@ -473,7 +474,7 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
$timeout.cancel(pickupTimeout); $timeout.cancel(pickupTimeout);
pickupTimeout = null; pickupTimeout = null;
// Kill ringer. // Kill ringer.
if (peercall && peercall.from === null) { if (peercall && peercall.isOutgoing()) {
dialerEnabled = true; dialerEnabled = true;
} else { } else {
dialerEnabled = false; dialerEnabled = false;
@ -485,7 +486,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
} }
// Apply peer call to scope. // Apply peer call to scope.
safeApply($scope, function(scope) { safeApply($scope, function(scope) {
// NOTE: the internal call will have a "id" of "null".
scope.peer = peercall ? peercall.id : null; scope.peer = peercall ? peercall.id : null;
scope.setConnectedStatus(); scope.setConnectedStatus();
}); });
@ -497,20 +497,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
scope.conferenceObject = peerconference ? peerconference : null; scope.conferenceObject = peerconference ? peerconference : null;
scope.updatePeerFromConference(); scope.updatePeerFromConference();
scope.setConnectedStatus(); scope.setConnectedStatus();
if (!peerconference) {
scope.peer = null;
if (scope.usermedia) {
$timeout(function() {
scope.usermedia = null;
mediaStream.webrtc.stop();
if (mediaStream.webrtc.isConferenceRoom()) {
mediaStream.webrtc.doUserMediaWithInternalCall();
}
$scope.layout.buddylist = true;
$scope.layout.buddylistAutoHide = false;
}, 0);
}
}
}); });
}); });
@ -520,7 +506,7 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
}); });
if ($scope.updateAutoAccept(null, from)) { if ($scope.updateAutoAccept(null, from)) {
// Auto accept support. // Auto accept support.
mediaStream.webrtc.doAccept(); mediaStream.webrtc.doAccept(from);
return; return;
} }
// Start to ring. // Start to ring.
@ -533,7 +519,7 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
// Start accept timeout. // Start accept timeout.
pickupTimeout = $timeout(function() { pickupTimeout = $timeout(function() {
console.log("Pickup timeout reached."); console.log("Pickup timeout reached.");
mediaStream.webrtc.doHangup("pickuptimeout"); mediaStream.webrtc.doHangup("pickuptimeout", from);
$scope.$emit("notification", "incomingpickuptimeout", { $scope.$emit("notification", "incomingpickuptimeout", {
reason: 'pickuptimeout', reason: 'pickuptimeout',
from: from from: from
@ -631,10 +617,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
mediaStream.webrtc.e.on("waitforusermedia connecting", function(event, currentcall) { mediaStream.webrtc.e.on("waitforusermedia connecting", function(event, currentcall) {
var t = event.type; var t = event.type;
if (currentcall && currentcall.isinternal && t === "connecting") {
// Don't show "Calling Someone" for the internal call.
return;
}
safeApply($scope, function(scope) { safeApply($scope, function(scope) {
scope.dialing = currentcall ? currentcall.id : null; scope.dialing = currentcall ? currentcall.id : null;
scope.setStatus(t); scope.setStatus(t);

7
static/js/directives/buddylist.js

@ -56,6 +56,13 @@ define(['underscore', 'text!partials/buddylist.html'], function(_, template) {
$scope.$apply(updateBuddyListVisibility); $scope.$apply(updateBuddyListVisibility);
}); });
$scope.$watch("peer", function() {
if ($scope.peer) {
// Also reset the buddylist if the peer is cleared after the "done" event.
updateBuddyListVisibility();
}
});
$scope.$on("room.joined", function(ev) { $scope.$on("room.joined", function(ev) {
inRoom = true; inRoom = true;
updateBuddyListVisibility(); updateBuddyListVisibility();

33
static/js/mediastream/peercall.js

@ -38,6 +38,7 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
this.offerOptions = $.extend(true, {}, this.webrtc.settings.offerOptions); this.offerOptions = $.extend(true, {}, this.webrtc.settings.offerOptions);
this.peerconnection = null; this.peerconnection = null;
this.pendingCandidates = [];
this.datachannels = {}; this.datachannels = {};
this.streams = {}; this.streams = {};
@ -47,6 +48,10 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
}; };
PeerCall.prototype.isOutgoing = function() {
return !!this.from;
};
PeerCall.prototype.setInitiate = function(initiate) { PeerCall.prototype.setInitiate = function(initiate) {
this.initiate = !! initiate; this.initiate = !! initiate;
//console.log("Set initiate", this.initiate, this); //console.log("Set initiate", this.initiate, this);
@ -74,6 +79,10 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
// TODO(longsleep): Check if this can happen? // TODO(longsleep): Check if this can happen?
error_cb(peerconnection); error_cb(peerconnection);
} }
while (this.pendingCandidates.length > 0) {
var candidate = this.pendingCandidates.shift();
this.addIceCandidate(candidate);
}
return peerconnection; return peerconnection;
}; };
@ -96,6 +105,12 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
PeerCall.prototype.onCreateAnswerOffer = function(cb, sessionDescription) { PeerCall.prototype.onCreateAnswerOffer = function(cb, sessionDescription) {
if (sessionDescription.type === "answer") {
// We processed the incoming Offer by creating an answer, so it's safe
// to create own Offers to perform renegotiation.
this.peerconnection.setReadyForRenegotiation(true);
}
this.setLocalSdp(sessionDescription); this.setLocalSdp(sessionDescription);
// Convert to object to allow custom property injection. // Convert to object to allow custom property injection.
@ -130,6 +145,10 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
console.error("Failed to create answer/offer", event); console.error("Failed to create answer/offer", event);
// Even though the Offer/Answer could not be created, we now allow
// to create own Offers to perform renegotiation again.
this.peerconnection.setReadyForRenegotiation(true);
}; };
PeerCall.prototype.setRemoteDescription = function(sessionDescription, cb) { PeerCall.prototype.setRemoteDescription = function(sessionDescription, cb) {
@ -142,6 +161,12 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
this.setRemoteSdp(sessionDescription); this.setRemoteSdp(sessionDescription);
if (sessionDescription.type === "offer") {
// Prevent creation of Offer messages to renegotiate streams while the
// remote Offer is being processed.
peerconnection.setReadyForRenegotiation(false);
}
peerconnection.setRemoteDescription(sessionDescription, _.bind(function() { peerconnection.setRemoteDescription(sessionDescription, _.bind(function() {
console.log("Set remote session description.", sessionDescription, this); console.log("Set remote session description.", sessionDescription, this);
if (cb) { if (cb) {
@ -255,6 +280,10 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
}; };
PeerCall.prototype.onNegotiationNeeded = function() { PeerCall.prototype.onNegotiationNeeded = function() {
if (!this.peerconnection.readyForRenegotiation) {
console.log("PeerConnection is not ready for renegotiation yet", this);
return;
}
if (!this.negotiationNeeded) { if (!this.negotiationNeeded) {
this.negotiationNeeded = true; this.negotiationNeeded = true;
@ -270,6 +299,10 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
// Avoid errors when still receiving candidates but closed. // Avoid errors when still receiving candidates but closed.
return; return;
} }
if (!this.peerconnection) {
this.pendingCandidates.push(candidate);
return;
}
this.peerconnection.addIceCandidate(candidate, function() { this.peerconnection.addIceCandidate(candidate, function() {
//console.log("Remote candidate added successfully.", candidate); //console.log("Remote candidate added successfully.", candidate);
}, function(error) { }, function(error) {

250
static/js/mediastream/peerconference.js

@ -22,180 +22,111 @@
"use strict"; "use strict";
define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall) { define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall) {
//NOTE(longsleep): This id should be changed to something undeterministic.
var conferences = 0; var conferences = 0;
var PeerConference = function(webrtc, currentcall, id) { var STATE_ACTIVE = "active";
var STATE_INCOMING = "incoming";
var STATE_OUTGOING = "outgoing";
var PeerConference = function(webrtc) {
this.webrtc = webrtc; this.webrtc = webrtc;
this.currentcall = currentcall;
this.calls = {}; this.calls = {};
this.callsIn = {}; this.callsCount = 0;
this.callStates = {};
this.connectedCalls = {};
this.conferenceMode = false;
this.e = $({}); this.e = $({});
this.id = null;
if (!id) { // Send conference updates to the other peers once we get a new connection.
this.id = webrtc.api.id + "_" + (++conferences); webrtc.e.on("statechange", _.bind(function(event, iceConnectionState, currentcall) {
} else { this.onConnectionStateChange(iceConnectionState, currentcall);
this.id = id;
}
if (!webrtc.usermedia) {
// Conference was started without getUM being called before. This
// happens for server-manager conference rooms. Create internal
// dummy call to trigger getUM, so actual conference calls can
// be established.
webrtc.doUserMediaWithInternalCall();
}
this.usermedia = webrtc.usermedia;
webrtc.e.on("usermedia", _.bind(function(event, um) {
console.log("Conference user media changed", um);
this.usermedia = um;
}, this)); }, this));
};
console.log("Created conference", this.id); // Creates a new unique random id to be used as conference id.
PeerConference.prototype._createConferenceId = function() {
return this.webrtc.api.id + "_" + (++conferences) + "_" + Math.round(Math.random() * 1e16);
}; };
PeerConference.prototype.checkEmpty = function() { PeerConference.prototype.getOrCreateId = function() {
if (!_.isEmpty(this.calls) || (this.currentcall && this.currentcall.id)) { if (!this.id) {
return false; this.id = this._createConferenceId();
console.log("Created new conference id", this.id);
} }
return this.id;
console.log("Conference is now empty -> cleaning up.");
this.e.triggerHandler("finished");
return true;
}; };
PeerConference.prototype.createCall = function(id, from, to) { PeerConference.prototype.hasCalls = function() {
return this.callsCount > 0;
var currentcall = new PeerCall(this.webrtc, id, from, to);
currentcall.e.on("closed", _.bind(function() {
delete this.calls[id];
if (this.callsIn.hasOwnProperty(id)) {
delete this.callsIn[id];
}
console.log("Cleaned up conference call", id);
this.checkEmpty();
}, this));
currentcall.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) {
this.onConnectionStateChange(iceConnectionState, currentcall);
}, this));
currentcall.e.on("remoteStreamAdded", _.bind(function(event, stream, currentcall) {
this.webrtc.onRemoteStreamAdded(stream, currentcall);
}, this));
currentcall.e.on("remoteStreamRemoved", _.bind(function(event, stream, currentcall) {
this.webrtc.onRemoteStreamRemoved(stream, currentcall);
}, this));
return currentcall;
}; };
PeerConference.prototype.doCall = function(id, autocall) { // Return number of currently active and pending calls.
PeerConference.prototype.getCallsCount = function() {
if ((this.currentcall && id === this.currentcall.id) || this.calls.hasOwnProperty(id)) { return this.callsCount;
// Ignore calls which we already have. };
//console.debug("Already got a call to this id (doCall)", id, this.calls, this.currentcall);
return;
}
var call = this.calls[id] = this.createCall(id, null, id);
call.setInitiate(true);
call.e.on("sessiondescription", _.bind(function(event, sessionDescription) {
console.log("Injected conference id into sessionDescription", this.id);
sessionDescription._conference = this.id;
}, this));
if (!autocall) { PeerConference.prototype._addCallWithState = function(id, call, state) {
this.webrtc.e.triggerHandler("connecting", [call]); if (this.calls.hasOwnProperty(id)) {
console.warn("Already has a call for", id);
return false;
} }
console.log("Creating PeerConnection", call); this.calls[id] = call;
call.createPeerConnection(_.bind(function(peerconnection) { this.callStates[id] = state;
// Success call. this.callsCount += 1;
if (this.usermedia) { return true;
this.usermedia.addToPeerConnection(peerconnection); };
}
call.e.on("negotiationNeeded", _.bind(function(event, extracall) {
this.webrtc.sendOfferWhenNegotiationNeeded(extracall);
}, this));
}, this), _.bind(function() {
// Error call.
console.error("Failed to create peer connection for conference call.");
}, this));
PeerConference.prototype.addIncoming = function(from, call) {
return this._addCallWithState(from, call, STATE_INCOMING);
}; };
PeerConference.prototype.callClosed = function(call) { PeerConference.prototype.addOutgoing = function(to, call) {
if (_.isEmpty(this.callsIn)) { return this._addCallWithState(to, call, STATE_OUTGOING);
// No more calls in the conference };
return null;
}
if (call !== this.currentcall) { PeerConference.prototype._setCallState = function(id, state) {
// An arbitrary call of the conference hung up. if (this.callStates.hasOwnProperty(id)) {
delete this.calls[call.id]; this.callStates[id] = state;
delete this.callsIn[call.id]; console.log("Call state changed", id, state);
console.log("Conference call closed", call);
} else {
// The "initiator" call of the conference hung up, promote another
// call to "initator" and return it.
var calls = _.keys(this.callsIn);
var id = calls[0];
this.currentcall = this.calls[id];
delete this.calls[id];
delete this.callsIn[id];
console.log("Handed over conference to", id, this.currentcall);
} }
return this.currentcall;
}; };
PeerConference.prototype.autoAnswer = function(from, rtcsdp) { PeerConference.prototype.setCallActive = function(id) {
this._setCallState(id, STATE_ACTIVE);
if ((this.currentcall && from === this.currentcall.id) || this.calls.hasOwnProperty(from)) { };
console.warn("Already got a call to this id (autoAnswer)", from, this.calls);
return;
}
var call = this.calls[from] = this.createCall(from, this.webrtc.api.id, from); PeerConference.prototype.getCall = function(id) {
console.log("Creating PeerConnection", call); return this.calls[id] || null;
call.createPeerConnection(_.bind(function(peerconnection) { };
// Success call.
call.setRemoteDescription(rtcsdp, _.bind(function() {
if (this.usermedia) {
this.usermedia.addToPeerConnection(peerconnection);
}
call.e.on("negotiationNeeded", _.bind(function(event, extracall) {
this.webrtc.sendOfferWhenNegotiationNeeded(extracall);
}, this));
call.createAnswer(_.bind(function(sessionDescription, extracall) {
console.log("Sending answer", sessionDescription, extracall.id);
this.webrtc.api.sendAnswer(extracall.id, sessionDescription);
}, this));
}, this));
}, this), _.bind(function() {
// Error call.
console.error("Failed to create peer connection for auto answer.");
}, this));
PeerConference.prototype.getCalls = function() {
return _.values(this.calls);
}; };
PeerConference.prototype.getCall = function(id) { PeerConference.prototype.getCallIds = function() {
return _.keys(this.calls);
};
var call = this.calls[id]; PeerConference.prototype.removeCall = function(id) {
if (!call) { if (!this.calls.hasOwnProperty(id)) {
call = null; return null;
} }
var call = this.calls[id];
delete this.calls[id];
delete this.callStates[id];
delete this.connectedCalls[id];
this.callsCount -= 1;
return call; return call;
}; };
PeerConference.prototype.close = function() { PeerConference.prototype.close = function() {
this.currentcall = null;
var api = this.webrtc.api; var api = this.webrtc.api;
_.each(this.calls, function(c) { _.each(this.calls, function(c) {
c.close(); c.close();
@ -205,6 +136,10 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall
} }
}); });
this.calls = {}; this.calls = {};
this.callStates = {};
this.connectedCalls = {};
this.callsCount = 0;
this.id = null;
}; };
@ -214,8 +149,8 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall
switch (iceConnectionState) { switch (iceConnectionState) {
case "completed": case "completed":
case "connected": case "connected":
if (!this.callsIn.hasOwnProperty(currentcall.id)) { if (!this.connectedCalls.hasOwnProperty(currentcall.id)) {
this.callsIn[currentcall.id] = true; this.connectedCalls[currentcall.id] = true;
this.pushUpdate(); this.pushUpdate();
} }
break; break;
@ -223,7 +158,6 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall
console.warn("Conference peer connection state failed", currentcall); console.warn("Conference peer connection state failed", currentcall);
break; break;
} }
this.webrtc.onConnectionStateChange(iceConnectionState, currentcall);
}; };
@ -233,44 +167,16 @@ define(['jquery', 'underscore', 'mediastream/peercall'], function($, _, PeerCall
return; return;
} }
var calls = _.keys(this.callsIn); var ids = _.keys(this.connectedCalls);
if (calls) { if (ids.length > 1) {
if (this.currentcall) { ids.push(this.webrtc.api.id);
calls.push(this.currentcall.id); console.log("Calls in conference:", ids);
calls.push(this.webrtc.api.id); this.webrtc.api.sendConference(this.getOrCreateId(), ids);
}
} }
console.log("Calls in conference: ", calls);
this.webrtc.api.sendConference(this.id, calls);
};
PeerConference.prototype.applyUpdate = function(ids) {
console.log("Applying conference update", this.id, ids);
var myid = this.webrtc.api.id;
_.each(ids, _.bind(function(id) {
var res = myid < id ? -1 : myid > id ? 1 : 0;
console.log("Considering conference peers to call", res, id);
if (res === -1) {
this.doCall(id, true);
}
}, this));
}; };
PeerConference.prototype.peerIds = function() { PeerConference.prototype.peerIds = function() {
return this.getCallIds();
var result = _.keys(this.calls);
// "peerIds" returns the session ids of all participants in the
// conference, so we need to add the id of the peer the user called
// manually before migrating to a conference (but only if it has an id,
// i.e. is not an internal call object).
if (this.currentcall && this.currentcall.id && result.indexOf(this.currentcall.id) === -1) {
result.push(this.currentcall.id);
}
return result;
}; };
return PeerConference; return PeerConference;

6
static/js/mediastream/peerconnection.js

@ -33,6 +33,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
this.pc = null; this.pc = null;
this.datachannel = null; this.datachannel = null;
this.datachannelReady = false; this.datachannelReady = false;
this.readyForRenegotiation = true;
if (currentcall) { if (currentcall) {
this.createPeerConnection(currentcall); this.createPeerConnection(currentcall);
@ -40,6 +41,10 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
}; };
PeerConnection.prototype.setReadyForRenegotiation = function(ready) {
this.readyForRenegotiation = !!ready;
};
PeerConnection.prototype.createPeerConnection = function(currentcall) { PeerConnection.prototype.createPeerConnection = function(currentcall) {
// XXX(longsleep): This function is a mess. // XXX(longsleep): This function is a mess.
@ -318,7 +323,6 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
}; };
PeerConnection.prototype.createAnswer = function() { PeerConnection.prototype.createAnswer = function() {
return this.pc.createAnswer.apply(this.pc, arguments); return this.pc.createAnswer.apply(this.pc, arguments);
}; };

712
static/js/mediastream/webrtc.js

@ -56,6 +56,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
InternalPC.prototype.addStream = function() { InternalPC.prototype.addStream = function() {
}; };
InternalPC.prototype.removeStream = function() {
};
InternalPC.prototype.negotiationNeeded = function() { InternalPC.prototype.negotiationNeeded = function() {
}; };
@ -87,13 +90,11 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
this.e = $({}); this.e = $({});
this.currentcall = null; this.conference = new PeerConference(this);
this.currentconference = null;
this.currentroom = null; this.currentroom = null;
this.msgQueue = []; this.msgQueues = {};
this.usermediaReady = false;
this.started = false; this.pendingMediaCalls = [];
this.initiator = null;
this.usermedia = null; this.usermedia = null;
this.audioMute = false; this.audioMute = false;
@ -160,22 +161,39 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
}; };
WebRTC.prototype.receivedRoom = function(event, room) { WebRTC.prototype.receivedRoom = function(event, room) {
this.currentroom = room;
if (this.isConferenceRoom()) { if (this.isConferenceRoom()) {
if (!this.usermedia) { // Switching from a conference room closes all current connections.
this.doUserMediaWithInternalCall(); _.defer(_.bind(function() {
} this.doHangup();
} else { }, this));
if (this.currentcall && this.currentcall.isinternal) {
this.stop();
}
} }
console.log("Joined room", room, this.api.id);
this.currentroom = room;
_.defer(_.bind(function() {
this.maybeStartLocalVideo();
}, this), 100);
}; };
WebRTC.prototype.isConferenceRoom = function() { WebRTC.prototype.isConferenceRoom = function() {
return this.currentroom && this.currentroom.Type === roomTypeConference; return this.currentroom && this.currentroom.Type === roomTypeConference;
}; };
WebRTC.prototype.maybeStartLocalVideo = function() {
if (!this.isConferenceRoom()) {
return;
}
console.log("Start local video");
var call = new InternalCall(this);
this._doCallUserMedia(call);
};
WebRTC.prototype.stopLocalVideo = function() {
if (this.usermedia) {
this.usermedia.stop();
}
};
WebRTC.prototype.processReceived = function(event, to, data, type, to2, from) { WebRTC.prototype.processReceived = function(event, to, data, type, to2, from) {
//console.log(">>>>>>>>>>>>", type, from, data, to, to2); //console.log(">>>>>>>>>>>>", type, from, data, to, to2);
@ -191,227 +209,210 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
return; return;
} }
if (!this.initiator && !this.started) { this.processReceivedMessage(to, data, type, to2, from);
switch (type) {
case "Offer":
if (this.currentcall && !this.currentcall.isinternal) {
console.warn("Received Offer while not started and with current call -> busy.", from);
this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]);
return;
}
this.msgQueue.unshift([to, data, type, to2, from]);
if (this.currentcall && this.currentcall.isinternal) {
// Internal getUM is currently in progress, defer
// evaluation of "Offer" until that is completed.
return;
}
// Create call.
this.currentcall = this.createCall(from, from, from);
// Delegate next steps to UI.
this.e.triggerHandler("offer", [from, to2, to]);
break;
case "Bye":
if (!this.currentcall) {
console.warn("Received Bye while without currentcall -> ignore.", from);
return;
}
if (this.currentcall.from !== from) {
console.warn("Received Bye from another id -> ignore.", from);
return;
}
console.log("Bye process (started false)");
this.doHangup("receivedbye", from);
// Delegate bye to UI.
this.e.triggerHandler("bye", [data.Reason, from, to, to2]);
break;
case "Conference":
// No existing call yet, only supported for server-managed
// conference.
if (!this.isConferenceRoom()) {
console.warn("Received Conference outside call for invalid room type.");
return;
}
this.processReceivedMessage(to, data, type, to2, from);
break;
default:
this.msgQueue.push([to, data, type, to2, from]);
break;
}
} else {
this.processReceivedMessage(to, data, type, to2, from);
}
}; };
WebRTC.prototype.findTargetCall = function(id) { WebRTC.prototype.findTargetCall = function(id) {
return this.conference.getCall(id);
};
var targetcall = null; WebRTC.prototype.callForEachCall = function(fn) {
if (this.currentcall) { var calls = this.conference.getCalls();
do { if (!calls.length) {
if (this.initiator && this.currentcall.to === id) { return 0;
targetcall = this.currentcall;
break;
}
if (!this.initiator && this.currentcall.from === id) {
targetcall = this.currentcall;
break;
}
if (this.currentcall.id === id) {
targetcall = this.currentcall;
break;
}
if (this.currentconference) {
targetcall = this.currentconference.getCall(id)
}
} while (false);
} }
return targetcall; _.map(calls, fn);
return calls.length;
};
WebRTC.prototype._getMessageQueue = function(id, create) {
var queue = this.msgQueues[id] || null;
if (queue === null && create) {
queue = this.msgQueues[id] = [];
}
return queue;
}; };
WebRTC.prototype.callForEachCall = function(fn) { WebRTC.prototype.pushBackMessage = function(id, message) {
this._getMessageQueue(id, true).push(message);
};
var count = 0; WebRTC.prototype.pushFrontMessage = function(id, message) {
if (this.currentcall) { this._getMessageQueue(id, true).unshift(message);
fn(this.currentcall, count); };
count++;
if (this.currentconference) { WebRTC.prototype.popFrontMessage = function(id) {
_.each(this.currentconference.calls, function(v, count) { var queue = this._getMessageQueue(id);
fn(v); if (!queue) {
count++; return null;
}); }
var message = queue.shift();
if (!queue.length) {
delete this.msgQueues[id];
}
return message;
};
WebRTC.prototype._processOffer = function(to, data, type, to2, from) {
console.log("Offer process.");
var call = this.conference.getCall(from);
if (call) {
// Remote peer is trying to renegotiate media.
if (!this.settings.renegotiation && call.peerconnection && call.peerconnection.hasRemoteDescription()) {
// Call replace support without renegotiation.
this.doHangup("unsupported", from);
console.error("Processing new offers is not implemented without renegotiation.");
return;
}
call.setRemoteDescription(new window.RTCSessionDescription(data), _.bind(function(sessionDescription, currentcall) {
this.e.triggerHandler("peercall", [currentcall]);
currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending answer", sessionDescription, currentcall.id);
this.api.sendAnswer(currentcall.id, sessionDescription);
}, this));
}, this));
return;
}
var autoaccept = false;
if (data._conference) {
if (this.conference.id !== data._conference) {
console.warn("Received Offer for unknown conference -> busy.", from);
this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]);
return;
}
console.log("Received conference Offer -> auto.", from, data._conference);
// Clean own internal data before feeding into browser.
delete data._conference;
autoaccept = true;
} else if (this.conference.hasCalls()) {
// TODO(fancycode): support joining callers to currently active conference.
console.warn("Received Offer while already in a call -> busy.", from);
this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]);
return;
}
call = this.createCall(from, this.api.id, from);
if (!this.conference.addIncoming(from, call)) {
console.warn("Already got a call, not processing Offer", from, autoaccept);
return;
}
this.pushFrontMessage(from, [to, data, type, to2, from]);
if (autoaccept) {
if (!this.doAccept(call, true)) {
this.popFrontMessage(from);
} }
return;
} }
return count;
// Delegate next steps to UI.
this.e.triggerHandler("offer", [from, to2, to]);
}; };
WebRTC.prototype.processReceivedMessage = function(to, data, type, to2, from) { WebRTC.prototype._processCandidate = function(to, data, type, to2, from) {
var call = this.conference.getCall(from);
if (!call) {
console.warn("Received Candidate for unknown id -> ignore.", from);
return;
}
var candidate = new window.RTCIceCandidate({
sdpMLineIndex: data.sdpMLineIndex,
sdpMid: data.sdpMid,
candidate: data.candidate
});
call.addIceCandidate(candidate);
//console.log("Got candidate", data.sdpMid, data.sdpMLineIndex, data.candidate);
};
if (!this.started && type !== "Conference") { WebRTC.prototype._processAnswer = function(to, data, type, to2, from) {
console.log('PeerConnection has not been created yet!'); var call = this.conference.getCall(from);
if (!call) {
console.warn("Received Answer from unknown id -> ignore", from);
return; return;
} }
var targetcall; console.log("Answer process.");
this.conference.setCallActive(call.id);
// TODO(longsleep): In case of negotiation this could switch offer and answer
// and result in a offer sdp sent as answer data. We need to handle this.
call.setRemoteDescription(new window.RTCSessionDescription(data), function() {
// Received remote description as answer.
console.log("Received answer after we sent offer", data);
});
};
WebRTC.prototype._processBye = function(to, data, type, to2, from) {
console.log("Bye process.");
this.doHangup("receivedbye", from);
// Delegate bye to UI.
this.e.triggerHandler("bye", [data.Reason, from, to, to2]);
};
WebRTC.prototype._processConference = function(to, data, type, to2, from) {
var ids = this.conference.getCallIds();
if (!ids.length && !this.isConferenceRoom()) {
console.warn("Received Conference for unknown call -> ignore.", to, data);
return;
} else if (ids.length == 1) {
// Peer-to-peer call will be upgraded to conference.
if (data.indexOf(ids[0]) === -1) {
console.warn("Received Conference for unknown call -> ignore.", to, data);
return;
}
this.conference.id = to;
} else if (this.conference.id && this.conference.id !== to) {
console.warn("Received Conference for wrong id -> ignore.", to, this.conference);
return;
}
if (!this.conference.id) {
if (!this.isConferenceRoom()) {
console.warn("Received initial Conference for non-conference room -> ignore.", to, this.conference);
return;
}
this.conference.id = to;
console.log("Setting received conference id", to);
}
console.log("Applying conference update", data);
var myid = this.api.id;
_.each(data, _.bind(function(id) {
var res = myid < id ? -1 : myid > id ? 1 : 0;
console.log("Considering conference peers to call", res, id);
if (res === -1) {
this.doCall(id, true);
}
}, this));
this.e.triggerHandler("peerconference", [this.conference]);
};
WebRTC.prototype.processReceivedMessage = function(to, data, type, to2, from) {
switch (type) { switch (type) {
case "Offer": case "Offer":
console.log("Offer process."); this._processOffer(to, data, type, to2, from);
targetcall = this.findTargetCall(from);
if (targetcall) {
if (!this.settings.renegotiation && targetcall.peerconnection && targetcall.peerconnection.hasRemoteDescription()) {
// Call replace support without renegotiation.
this.doHangup("unsupported", from);
console.error("Processing new offers is not implemented without renegotiation.");
return;
}
// Hey we know this call.
targetcall.setRemoteDescription(new window.RTCSessionDescription(data), _.bind(function(sessionDescription, currentcall) {
if (currentcall === this.currentcall) {
// Main call.
this.e.triggerHandler("peercall", [this.currentcall]);
}
currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending answer", sessionDescription, currentcall.id);
this.api.sendAnswer(currentcall.id, sessionDescription);
}, this));
}, this));
} else {
// No target call. Check conference auto answer support.
if (this.currentconference && this.currentconference.id === data._conference) {
console.log("Received conference Offer -> auto.", from, data._conference);
// Clean own internal data before feeding into browser.
delete data._conference;
this.currentconference.autoAnswer(from, new window.RTCSessionDescription(data));
break;
}
// Cannot do anything with this offer, reply with busy.
console.log("Received Offer from unknown id -> busy.", from);
this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]);
}
break; break;
case "Candidate": case "Candidate":
targetcall = this.findTargetCall(from); this._processCandidate(to, data, type, to2, from);
if (!targetcall) {
console.warn("Received Candidate for unknown id -> ignore.", from);
return;
}
var candidate = new window.RTCIceCandidate({
sdpMLineIndex: data.sdpMLineIndex,
sdpMid: data.sdpMid,
candidate: data.candidate
});
targetcall.addIceCandidate(candidate);
//console.log("Got candidate", data.sdpMid, data.sdpMLineIndex, data.candidate);
break; break;
case "Answer": case "Answer":
targetcall = this.findTargetCall(from); this._processAnswer(to, data, type, to2, from);
if (!targetcall) {
console.warn("Received Answer from unknown id -> ignore", from);
return;
}
console.log("Answer process.");
// TODO(longsleep): In case of negotiation this could switch offer and answer
// and result in a offer sdp sent as answer data. We need to handle this.
targetcall.setRemoteDescription(new window.RTCSessionDescription(data), function() {
// Received remote description as answer.
console.log("Received answer after we sent offer", data);
});
break; break;
case "Bye": case "Bye":
targetcall = this.findTargetCall(from); this._processBye(to, data, type, to2, from);
if (!targetcall) {
console.warn("Received Bye from unknown id -> ignore.", from);
return;
}
console.log("Bye process.");
if (this.currentconference) {
// Hand over current call to next conference call.
var newcurrentcall = this.currentconference.callClosed(targetcall);
targetcall.close()
if (newcurrentcall && newcurrentcall != this.currentcall) {
this.currentcall = newcurrentcall;
this.e.triggerHandler("peercall", [newcurrentcall]);
} else if (!newcurrentcall) {
this.doHangup("receivedbye", targetcall.id);
}
if (this.currentconference && !this.currentconference.checkEmpty()) {
this.e.triggerHandler("peerconference", [this.currentconference]);
}
} else {
this.doHangup("receivedbye", targetcall.id);
}
this.e.triggerHandler("bye", [data.Reason, from, to, to2]);
break; break;
case "Conference": case "Conference":
if ((!this.currentcall || data.indexOf(this.currentcall.id) === -1) && !this.isConferenceRoom()) { this._processConference(to, data, type, to2, from);
console.warn("Received Conference for unknown call -> ignore.", to, data);
return;
} else {
var currentconference = this.currentconference;
if (!currentconference) {
currentconference = this.currentconference = new PeerConference(this, this.currentcall, to);
currentconference.e.one("finished", _.bind(function() {
this.currentconference = null;
this.e.triggerHandler("peerconference", [null]);
}, this));
} else {
if (currentconference.id !== to) {
console.warn("Received Conference for wrong id -> ignore.", to, currentconference);
return;
}
}
currentconference.applyUpdate(data);
this.e.triggerHandler("peerconference", [currentconference]);
}
break; break;
default: default:
console.log("Unhandled message type", type, data); console.log("Unhandled message type", type, data);
break;
} }
}; };
WebRTC.prototype.testMediaAccess = function(cb) { WebRTC.prototype.testMediaAccess = function(cb) {
@ -419,50 +420,46 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
var success = function(stream) { var success = function(stream) {
console.info("testMediaAccess success"); console.info("testMediaAccess success");
cb(true); cb(true);
} };
var failed = function() { var failed = function() {
console.info("testMediaAccess failed"); console.info("testMediaAccess failed");
cb(false); cb(false);
} };
UserMedia.testGetUserMedia(success, failed); UserMedia.testGetUserMedia(success, failed);
}; };
WebRTC.prototype.createCall = function(id, from, to) { WebRTC.prototype.createCall = function(id, from, to) {
var call = new PeerCall(this, id, from, to);
var currentcall = new PeerCall(this, id, from, to); call.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) {
currentcall.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) {
this.onConnectionStateChange(iceConnectionState, currentcall); this.onConnectionStateChange(iceConnectionState, currentcall);
}, this)); }, this));
currentcall.e.on("remoteStreamAdded", _.bind(function(event, stream, currentcall) { call.e.on("remoteStreamAdded", _.bind(function(event, stream, currentcall) {
this.onRemoteStreamAdded(stream, currentcall); this.onRemoteStreamAdded(stream, currentcall);
}, this)); }, this));
currentcall.e.on("remoteStreamRemoved", _.bind(function(event, stream, currentcall) { call.e.on("remoteStreamRemoved", _.bind(function(event, stream, currentcall) {
this.onRemoteStreamRemoved(stream, currentcall); this.onRemoteStreamRemoved(stream, currentcall);
}, this)); }, this));
currentcall.e.on("error", _.bind(function(event, id, message) { call.e.on("error", _.bind(function(event, error_id, message) {
if (!id) { if (!error_id) {
id = "failed_peerconnection"; error_id = "failed_peerconnection";
} }
this.e.triggerHandler("error", [message, id]); this.e.triggerHandler("error", [message, error_id]);
_.defer(_.bind(this.doHangup, this), "error", currentcall.id); // Hangup on error is good yes?? _.defer(_.bind(this.doHangup, this), "error", id); // Hangup on error is good yes??
}, this)); }, this));
call.e.on("closed", _.bind(function() {
return currentcall; this.conference.removeCall(id);
}, this));
return call;
}; };
WebRTC.prototype.doUserMediaWithInternalCall = function() { WebRTC.prototype.doUserMedia = function(call) {
if (this.currentcall && !this.currentcall.isinternal) {
console.warn("Already have a current call, not doing internal getUM", this.currentcall);
return;
}
var currentcall = this.currentcall = new InternalCall(this);
this.e.triggerHandler("peercall", [currentcall]);
this.doUserMedia(currentcall);
};
WebRTC.prototype.doUserMedia = function(currentcall) { if (this.usermedia) {
// We should not create a new UserMedia object while the current one
// is still being used.
console.error("UserMedia already created, check caller");
}
// Create default media (audio/video). // Create default media (audio/video).
var usermedia = new UserMedia({ var usermedia = new UserMedia({
@ -472,8 +469,12 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
}); });
usermedia.e.on("mediasuccess mediaerror", _.bind(function(event, um) { usermedia.e.on("mediasuccess mediaerror", _.bind(function(event, um) {
this.e.triggerHandler("usermedia", [um]); this.e.triggerHandler("usermedia", [um]);
this.usermediaReady = true;
// Start always, no matter what. // Start always, no matter what.
this.maybeStart(um); while (this.pendingMediaCalls.length > 0) {
var c = this.pendingMediaCalls.shift();
this.maybeStart(um, c);
}
}, this)); }, this));
usermedia.e.on("mediachanged", _.bind(function(event, um) { usermedia.e.on("mediachanged", _.bind(function(event, um) {
// Propagate media change events. // Propagate media change events.
@ -482,7 +483,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
usermedia.e.on("stopped", _.bind(function(event, um) { usermedia.e.on("stopped", _.bind(function(event, um) {
if (um === this.usermedia) { if (um === this.usermedia) {
this.e.triggerHandler("usermedia", [null]); this.e.triggerHandler("usermedia", [null]);
this.usermediaReady = false;
this.usermedia = null; this.usermedia = null;
this.maybeStartLocalVideo();
} }
}, this)); }, this));
this.e.one("stop", function() { this.e.one("stop", function() {
@ -490,55 +493,99 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
}); });
this.usermedia = usermedia; this.usermedia = usermedia;
this.e.triggerHandler("usermedia", [usermedia]); this.e.triggerHandler("usermedia", [usermedia]);
this.pendingMediaCalls.push(call);
return usermedia.doGetUserMedia(currentcall); return usermedia.doGetUserMedia(call);
}; };
WebRTC.prototype.doCall = function(id) { WebRTC.prototype._doCallUserMedia = function(call) {
if (this.usermedia) {
if (this.currentcall) { if (!this.usermediaReady) {
// Conference mode. this.pendingMediaCalls.push(call);
var currentconference = this.currentconference;
if (!currentconference) {
currentconference = this.currentconference = new PeerConference(this, this.currentcall);
currentconference.e.one("finished", _.bind(function() {
this.currentconference = null;
this.e.triggerHandler("peerconference", [null]);
}, this));
}
currentconference.doCall(id);
this.e.triggerHandler("peerconference", [currentconference]);
} else {
var currentcall = this.currentcall = this.createCall(id, null, id);
this.e.triggerHandler("peercall", [currentcall]);
var ok = this.doUserMedia(currentcall);
if (ok) {
this.e.triggerHandler("waitforusermedia", [currentcall]);
} else { } else {
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); this.maybeStart(this.usermedia, call);
return this.doHangup();
} }
this.initiator = true; return true;
}
var ok = this.doUserMedia(call);
if (ok) {
this.e.triggerHandler("waitforusermedia", [call]);
return true;
} }
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]);
if (call.id) {
this.doHangup("usermedia", call.id);
}
return false;
}; };
WebRTC.prototype.doAccept = function() { WebRTC.prototype._doAutoStartCall = function(call) {
if (!this.usermedia) {
return false;
}
//NOTE(longsleep): currentcall was created as early as possible to be able to process incoming candidates. if (!this.usermediaReady) {
var currentcall = this.currentcall; // getUserMedia is still pending, defer starting of call.
if (!currentcall) { this.pendingMediaCalls.push(call);
console.warn("Trying to accept without a call.", currentcall); } else {
this.maybeStart(this.usermedia, call, true);
}
return true;
};
WebRTC.prototype.doCall = function(id, autocall) {
var call = this.createCall(id, null, id);
call.setInitiate(true);
var count = this.conference.getCallsCount();
if (!this.conference.addOutgoing(id, call)) {
console.log("Already has a call with", id);
return; return;
} }
var ok = this.doUserMedia(currentcall); this.e.triggerHandler("peercall", [call]);
if (ok) { if (!autocall) {
this.e.triggerHandler("waitforusermedia", [currentcall]); this.e.triggerHandler("connecting", [call]);
} else { }
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); if ((autocall && count > 0) || this.isConferenceRoom()) {
return this.doHangup(); call.e.on("sessiondescription", _.bind(function(event, sessionDescription) {
var cid = this.conference.getOrCreateId();
console.log("Injected conference id into sessionDescription", cid);
sessionDescription._conference = cid;
}, this));
}
if (count > 0) {
if (count === 1) {
// Notify UI that it's a conference now.
this.e.triggerHandler("peerconference", [this.conference]);
}
if (this._doAutoStartCall(call)) {
return;
}
} }
if (!this._doCallUserMedia(call)) {
return;
}
};
WebRTC.prototype.doAccept = function(call, autoanswer) {
if (typeof call === "string") {
var id = call;
call = this.conference.getCall(id);
if (!call) {
console.warn("Trying to accept unknown call.", id);
return false;
}
}
this.conference.setCallActive(call.id);
if (autoanswer && this._doAutoStartCall(call)) {
return true;
}
return this._doCallUserMedia(call);
}; };
WebRTC.prototype.doXfer = function(id, token, options) { WebRTC.prototype.doXfer = function(id, token, options) {
@ -670,99 +717,94 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
WebRTC.prototype.stop = function() { WebRTC.prototype.stop = function() {
if (this.currentconference) { this.conference.close();
this.currentconference.close();
this.currentconference = null;
}
if (this.currentcall) {
this.currentcall.close();
this.currentcall = null;
}
this.e.triggerHandler("peerconference", [null]); this.e.triggerHandler("peerconference", [null]);
this.e.triggerHandler("peercall", [null]); this.e.triggerHandler("peercall", [null]);
this.e.triggerHandler("stop"); this.e.triggerHandler("stop");
this.msgQueue.length = 0; this.msgQueues = {};
this.initiator = null;
this.started = false;
} }
WebRTC.prototype.doHangup = function(reason, id) { WebRTC.prototype.doHangup = function(reason, id) {
if (!id) {
console.log("Closing all calls")
_.each(this.conference.getCallIds(), _.bind(function(callid) {
this.doHangup(reason, callid);
}, this));
this.stop();
return true;
}
console.log("Hanging up.", id); console.log("Hanging up.", id);
if (id) { var call = this.conference.removeCall(id);
var currentcall = this.findTargetCall(id); if (!call) {
if (!currentcall) { console.warn("Tried to hangup unknown call.", reason, id);
console.warn("Tried to hangup unknown call.", reason, id); return false;
return; }
} call.close();
if (currentcall !== this.currentcall) { if (reason !== "receivedbye") {
currentcall.close(); this.api.sendBye(id, reason);
if (reason !== "receivedbye") {
this.api.sendBye(id, reason);
}
_.defer(_.bind(function() {
if (this.currentcall && currentcall) {
this.e.triggerHandler("statechange", ["connected", this.currentcall]);
} else {
this.e.triggerHandler("done", [reason]);
}
}, this));
return;
}
} }
if (this.currentcall) { var calls = this.conference.getCalls();
id = this.currentcall.id; if (!calls.length) {
// Last peer disconnected, perform cleanup.
this.e.triggerHandler("peercall", [null]);
_.defer(_.bind(function() { _.defer(_.bind(function() {
this.e.triggerHandler("done", [reason]); this.e.triggerHandler("done", [reason]);
}, this)); }, this));
this.stop();
} else if (calls.length === 1) {
this.e.triggerHandler("peerconference", [null]);
this.e.triggerHandler("peercall", [calls[0]]);
} }
this.stop(); return true;
if (id) {
if (reason !== "receivedbye") {
this.api.sendBye(id, reason);
}
}
} }
WebRTC.prototype.maybeStart = function(usermedia) { WebRTC.prototype.maybeStart = function(usermedia, call, autocall) {
//console.log("maybeStart", this.started);
if (!this.started) {
var currentcall = this.currentcall;
currentcall.setInitiate(this.initiator);
this.e.triggerHandler("connecting", [currentcall]);
console.log('Creating PeerConnection.', currentcall);
currentcall.createPeerConnection(_.bind(function(peerconnection) {
// Success call.
usermedia.addToPeerConnection(peerconnection);
this.started = true;
if (!this.initiator) {
this.calleeStart();
}
currentcall.e.on("negotiationNeeded", _.bind(function(event, currentcall) {
this.sendOfferWhenNegotiationNeeded(currentcall);
}, this));
}, this), _.bind(function() {
// Error call.
this.e.triggerHandler("error", ["Failed to create peer connection. See log for details."]);
this.doHangup();
}, this));
//console.log("maybeStart", call);
if (call.peerconnection) {
console.log("Already started", call);
return;
} }
}; if (!autocall) {
if (!call.isinternal) {
WebRTC.prototype.calleeStart = function() { this.e.triggerHandler("connecting", [call]);
} else if (!this.conference.hasCalls()) {
var args; // Signal UI that media access has been granted.
while (this.msgQueue.length > 0) { this.e.triggerHandler("done");
args = this.msgQueue.shift(); }
this.processReceivedMessage.apply(this, args);
} }
console.log('Creating PeerConnection.', call);
call.createPeerConnection(_.bind(function(peerconnection) {
// Success call.
usermedia.addToPeerConnection(peerconnection);
if (!call.initiate) {
this.processPendingMessages(call.id);
}
call.e.on("negotiationNeeded", _.bind(function(event, call) {
this.sendOfferWhenNegotiationNeeded(call);
}, this));
}, this), _.bind(function() {
// Error call.
this.e.triggerHandler("error", ["Failed to create peer connection. See log for details."]);
if (call.id) {
this.doHangup("failed", call.id);
}
}, this));
};
WebRTC.prototype.processPendingMessages = function(id) {
do {
var message = this.popFrontMessage(id);
if (!message) {
break;
}
this.processReceivedMessage.apply(this, message);
} while (true);
}; };
WebRTC.prototype.sendOfferWhenNegotiationNeeded = function(currentcall, to) { WebRTC.prototype.sendOfferWhenNegotiationNeeded = function(currentcall, to) {

Loading…
Cancel
Save