Browse Source

Major refactoring of call / conference handling.

Removed difference between single peer-to-peer calls and conferences
with multiple peers. There is only a single code path now that creates
calls and stores them in a conference (which holds all active calls).
With this also fixed some timing issues that could cause conference
peers to not send or receive media streams.

Should help with some of the issues reported in #276.
pull/293/head
Joachim Bauch 9 years ago
parent
commit
d4f936d57b
  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. 622
      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);
}; };
PeerConference.prototype.checkEmpty = function() { // Creates a new unique random id to be used as conference id.
if (!_.isEmpty(this.calls) || (this.currentcall && this.currentcall.id)) { PeerConference.prototype._createConferenceId = function() {
return false; return this.webrtc.api.id + "_" + (++conferences) + "_" + Math.round(Math.random() * 1e16);
}
console.log("Conference is now empty -> cleaning up.");
this.e.triggerHandler("finished");
return true;
}; };
PeerConference.prototype.createCall = function(id, from, to) { PeerConference.prototype.getOrCreateId = function() {
if (!this.id) {
var currentcall = new PeerCall(this.webrtc, id, from, to); this.id = this._createConferenceId();
currentcall.e.on("closed", _.bind(function() { console.log("Created new conference id", this.id);
delete this.calls[id];
if (this.callsIn.hasOwnProperty(id)) {
delete this.callsIn[id];
} }
console.log("Cleaned up conference call", id); return this.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) { PeerConference.prototype.hasCalls = function() {
return this.callsCount > 0;
if ((this.currentcall && id === this.currentcall.id) || this.calls.hasOwnProperty(id)) { };
// 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); // Return number of currently active and pending calls.
call.setInitiate(true); PeerConference.prototype.getCallsCount = function() {
call.e.on("sessiondescription", _.bind(function(event, sessionDescription) { return this.callsCount;
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);
}; };

622
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) {
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); 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;
if (this.currentcall) {
do {
if (this.initiator && this.currentcall.to === id) {
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;
}; };
WebRTC.prototype.callForEachCall = function(fn) { WebRTC.prototype.callForEachCall = function(fn) {
var calls = this.conference.getCalls();
var count = 0; if (!calls.length) {
if (this.currentcall) { return 0;
fn(this.currentcall, count);
count++;
if (this.currentconference) {
_.each(this.currentconference.calls, function(v, count) {
fn(v);
count++;
});
} }
_.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 count; return queue;
};
WebRTC.prototype.pushBackMessage = function(id, message) {
this._getMessageQueue(id, true).push(message);
}; };
WebRTC.prototype.processReceivedMessage = function(to, data, type, to2, from) { WebRTC.prototype.pushFrontMessage = function(id, message) {
this._getMessageQueue(id, true).unshift(message);
};
if (!this.started && type !== "Conference") { WebRTC.prototype.popFrontMessage = function(id) {
console.log('PeerConnection has not been created yet!'); var queue = this._getMessageQueue(id);
return; if (!queue) {
return null;
} }
var message = queue.shift();
if (!queue.length) {
delete this.msgQueues[id];
}
return message;
};
var targetcall; WebRTC.prototype._processOffer = function(to, data, type, to2, from) {
switch (type) {
case "Offer":
console.log("Offer process."); console.log("Offer process.");
targetcall = this.findTargetCall(from); var call = this.conference.getCall(from);
if (targetcall) { if (call) {
if (!this.settings.renegotiation && targetcall.peerconnection && targetcall.peerconnection.hasRemoteDescription()) { // Remote peer is trying to renegotiate media.
if (!this.settings.renegotiation && call.peerconnection && call.peerconnection.hasRemoteDescription()) {
// Call replace support without renegotiation. // Call replace support without renegotiation.
this.doHangup("unsupported", from); this.doHangup("unsupported", from);
console.error("Processing new offers is not implemented without renegotiation."); console.error("Processing new offers is not implemented without renegotiation.");
return; return;
} }
// Hey we know this call.
targetcall.setRemoteDescription(new window.RTCSessionDescription(data), _.bind(function(sessionDescription, currentcall) { call.setRemoteDescription(new window.RTCSessionDescription(data), _.bind(function(sessionDescription, currentcall) {
if (currentcall === this.currentcall) { this.e.triggerHandler("peercall", [currentcall]);
// Main call.
this.e.triggerHandler("peercall", [this.currentcall]);
}
currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) { currentcall.createAnswer(_.bind(function(sessionDescription, currentcall) {
console.log("Sending answer", sessionDescription, currentcall.id); console.log("Sending answer", sessionDescription, currentcall.id);
this.api.sendAnswer(currentcall.id, sessionDescription); this.api.sendAnswer(currentcall.id, sessionDescription);
}, this)); }, this));
}, this)); }, this));
} else { return;
// No target call. Check conference auto answer support. }
if (this.currentconference && this.currentconference.id === data._conference) {
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); console.log("Received conference Offer -> auto.", from, data._conference);
// Clean own internal data before feeding into browser. // Clean own internal data before feeding into browser.
delete data._conference; delete data._conference;
this.currentconference.autoAnswer(from, new window.RTCSessionDescription(data)); autoaccept = true;
break; } else if (this.conference.hasCalls()) {
} // TODO(fancycode): support joining callers to currently active conference.
// Cannot do anything with this offer, reply with busy. console.warn("Received Offer while already in a call -> busy.", from);
console.log("Received Offer from unknown id -> busy.", from);
this.api.sendBye(from, "busy"); this.api.sendBye(from, "busy");
this.e.triggerHandler("busy", [from, to2, to]); this.e.triggerHandler("busy", [from, to2, to]);
return;
} }
break;
case "Candidate": call = this.createCall(from, this.api.id, from);
targetcall = this.findTargetCall(from); if (!this.conference.addIncoming(from, call)) {
if (!targetcall) { 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;
}
// Delegate next steps to UI.
this.e.triggerHandler("offer", [from, to2, to]);
};
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); console.warn("Received Candidate for unknown id -> ignore.", from);
return; return;
} }
var candidate = new window.RTCIceCandidate({ var candidate = new window.RTCIceCandidate({
sdpMLineIndex: data.sdpMLineIndex, sdpMLineIndex: data.sdpMLineIndex,
sdpMid: data.sdpMid, sdpMid: data.sdpMid,
candidate: data.candidate candidate: data.candidate
}); });
targetcall.addIceCandidate(candidate); call.addIceCandidate(candidate);
//console.log("Got candidate", data.sdpMid, data.sdpMLineIndex, data.candidate); //console.log("Got candidate", data.sdpMid, data.sdpMLineIndex, data.candidate);
break; };
case "Answer":
targetcall = this.findTargetCall(from); WebRTC.prototype._processAnswer = function(to, data, type, to2, from) {
if (!targetcall) { var call = this.conference.getCall(from);
if (!call) {
console.warn("Received Answer from unknown id -> ignore", from); console.warn("Received Answer from unknown id -> ignore", from);
return; return;
} }
console.log("Answer process."); console.log("Answer process.");
this.conference.setCallActive(call.id);
// TODO(longsleep): In case of negotiation this could switch offer and answer // 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. // and result in a offer sdp sent as answer data. We need to handle this.
targetcall.setRemoteDescription(new window.RTCSessionDescription(data), function() { call.setRemoteDescription(new window.RTCSessionDescription(data), function() {
// Received remote description as answer. // Received remote description as answer.
console.log("Received answer after we sent offer", data); console.log("Received answer after we sent offer", data);
}); });
break; };
case "Bye":
targetcall = this.findTargetCall(from); WebRTC.prototype._processBye = function(to, data, type, to2, from) {
if (!targetcall) {
console.warn("Received Bye from unknown id -> ignore.", from);
return;
}
console.log("Bye process."); console.log("Bye process.");
if (this.currentconference) { this.doHangup("receivedbye", from);
// Hand over current call to next conference call. // Delegate bye to UI.
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]); this.e.triggerHandler("bye", [data.Reason, from, to, to2]);
break; };
case "Conference":
if ((!this.currentcall || data.indexOf(this.currentcall.id) === -1) && !this.isConferenceRoom()) { 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); console.warn("Received Conference for unknown call -> ignore.", to, data);
return; return;
} else { } else if (ids.length == 1) {
var currentconference = this.currentconference; // Peer-to-peer call will be upgraded to conference.
if (!currentconference) { if (data.indexOf(ids[0]) === -1) {
currentconference = this.currentconference = new PeerConference(this, this.currentcall, to); console.warn("Received Conference for unknown call -> ignore.", to, data);
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; 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;
} }
currentconference.applyUpdate(data);
this.e.triggerHandler("peerconference", [currentconference]); 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) {
case "Offer":
this._processOffer(to, data, type, to2, from);
break;
case "Candidate":
this._processCandidate(to, data, type, to2, from);
break;
case "Answer":
this._processAnswer(to, data, type, to2, from);
break;
case "Bye":
this._processBye(to, data, type, to2, from);
break;
case "Conference":
this._processConference(to, data, type, to2, from);
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 { } else {
var currentcall = this.currentcall = this.createCall(id, null, id); this.maybeStart(this.usermedia, call);
this.e.triggerHandler("peercall", [currentcall]); }
var ok = this.doUserMedia(currentcall); return true;
}
var ok = this.doUserMedia(call);
if (ok) { if (ok) {
this.e.triggerHandler("waitforusermedia", [currentcall]); this.e.triggerHandler("waitforusermedia", [call]);
} else { return true;
}
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]);
return this.doHangup(); if (call.id) {
this.doHangup("usermedia", call.id);
}
return false;
};
WebRTC.prototype._doAutoStartCall = function(call) {
if (!this.usermedia) {
return false;
} }
this.initiator = true;
if (!this.usermediaReady) {
// getUserMedia is still pending, defer starting of call.
this.pendingMediaCalls.push(call);
} else {
this.maybeStart(this.usermedia, call, true);
} }
return true;
}; };
WebRTC.prototype.doAccept = function() { 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;
}
this.e.triggerHandler("peercall", [call]);
if (!autocall) {
this.e.triggerHandler("connecting", [call]);
}
if ((autocall && count > 0) || this.isConferenceRoom()) {
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;
}
}
//NOTE(longsleep): currentcall was created as early as possible to be able to process incoming candidates. if (!this._doCallUserMedia(call)) {
var currentcall = this.currentcall;
if (!currentcall) {
console.warn("Trying to accept without a call.", currentcall);
return; return;
} }
var ok = this.doUserMedia(currentcall); };
if (ok) {
this.e.triggerHandler("waitforusermedia", [currentcall]); WebRTC.prototype.doAccept = function(call, autoanswer) {
} else { if (typeof call === "string") {
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); var id = call;
return this.doHangup(); 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; return false;
} }
if (currentcall !== this.currentcall) { call.close();
currentcall.close();
if (reason !== "receivedbye") { if (reason !== "receivedbye") {
this.api.sendBye(id, reason); this.api.sendBye(id, reason);
} }
_.defer(_.bind(function() { var calls = this.conference.getCalls();
if (this.currentcall && currentcall) { if (!calls.length) {
this.e.triggerHandler("statechange", ["connected", this.currentcall]); // Last peer disconnected, perform cleanup.
} else { this.e.triggerHandler("peercall", [null]);
this.e.triggerHandler("done", [reason]);
}
}, this));
return;
}
}
if (this.currentcall) {
id = this.currentcall.id;
_.defer(_.bind(function() { _.defer(_.bind(function() {
this.e.triggerHandler("done", [reason]); this.e.triggerHandler("done", [reason]);
}, this)); }, this));
}
this.stop(); this.stop();
if (id) { } else if (calls.length === 1) {
if (reason !== "receivedbye") { this.e.triggerHandler("peerconference", [null]);
this.api.sendBye(id, reason); this.e.triggerHandler("peercall", [calls[0]]);
}
} }
return true;
} }
WebRTC.prototype.maybeStart = function(usermedia) { WebRTC.prototype.maybeStart = function(usermedia, call, autocall) {
//console.log("maybeStart", this.started); //console.log("maybeStart", call);
if (!this.started) { if (call.peerconnection) {
console.log("Already started", call);
return;
}
var currentcall = this.currentcall; if (!autocall) {
currentcall.setInitiate(this.initiator); if (!call.isinternal) {
this.e.triggerHandler("connecting", [currentcall]); this.e.triggerHandler("connecting", [call]);
console.log('Creating PeerConnection.', currentcall); } else if (!this.conference.hasCalls()) {
currentcall.createPeerConnection(_.bind(function(peerconnection) { // Signal UI that media access has been granted.
this.e.triggerHandler("done");
}
}
console.log('Creating PeerConnection.', call);
call.createPeerConnection(_.bind(function(peerconnection) {
// Success call. // Success call.
usermedia.addToPeerConnection(peerconnection); usermedia.addToPeerConnection(peerconnection);
this.started = true; if (!call.initiate) {
if (!this.initiator) { this.processPendingMessages(call.id);
this.calleeStart();
} }
currentcall.e.on("negotiationNeeded", _.bind(function(event, currentcall) { call.e.on("negotiationNeeded", _.bind(function(event, call) {
this.sendOfferWhenNegotiationNeeded(currentcall); this.sendOfferWhenNegotiationNeeded(call);
}, this)); }, this));
}, this), _.bind(function() { }, this), _.bind(function() {
// Error call. // Error call.
this.e.triggerHandler("error", ["Failed to create peer connection. See log for details."]); this.e.triggerHandler("error", ["Failed to create peer connection. See log for details."]);
this.doHangup(); if (call.id) {
}, this)); this.doHangup("failed", call.id);
} }
}, this));
}; };
WebRTC.prototype.calleeStart = function() { WebRTC.prototype.processPendingMessages = function(id) {
do {
var args; var message = this.popFrontMessage(id);
while (this.msgQueue.length > 0) { if (!message) {
args = this.msgQueue.shift(); break;
this.processReceivedMessage.apply(this, args);
} }
this.processReceivedMessage.apply(this, message);
} while (true);
}; };
WebRTC.prototype.sendOfferWhenNegotiationNeeded = function(currentcall, to) { WebRTC.prototype.sendOfferWhenNegotiationNeeded = function(currentcall, to) {

Loading…
Cancel
Save