|
|
|
@ -56,6 +56,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -56,6 +56,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
InternalPC.prototype.addStream = function() { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
InternalPC.prototype.removeStream = function() { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
InternalPC.prototype.negotiationNeeded = function() { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -87,13 +90,11 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -87,13 +90,11 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
|
|
|
|
|
this.e = $({}); |
|
|
|
|
|
|
|
|
|
this.currentcall = null; |
|
|
|
|
this.currentconference = null; |
|
|
|
|
this.conference = new PeerConference(this); |
|
|
|
|
this.currentroom = null; |
|
|
|
|
this.msgQueue = []; |
|
|
|
|
|
|
|
|
|
this.started = false; |
|
|
|
|
this.initiator = null; |
|
|
|
|
this.msgQueues = {}; |
|
|
|
|
this.usermediaReady = false; |
|
|
|
|
this.pendingMediaCalls = []; |
|
|
|
|
|
|
|
|
|
this.usermedia = null; |
|
|
|
|
this.audioMute = false; |
|
|
|
@ -160,22 +161,39 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -160,22 +161,39 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.receivedRoom = function(event, room) { |
|
|
|
|
this.currentroom = room; |
|
|
|
|
if (this.isConferenceRoom()) { |
|
|
|
|
if (!this.usermedia) { |
|
|
|
|
this.doUserMediaWithInternalCall(); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (this.currentcall && this.currentcall.isinternal) { |
|
|
|
|
this.stop(); |
|
|
|
|
} |
|
|
|
|
// Switching from a conference room closes all current connections.
|
|
|
|
|
_.defer(_.bind(function() { |
|
|
|
|
this.doHangup(); |
|
|
|
|
}, this)); |
|
|
|
|
} |
|
|
|
|
console.log("Joined room", room, this.api.id); |
|
|
|
|
this.currentroom = room; |
|
|
|
|
_.defer(_.bind(function() { |
|
|
|
|
this.maybeStartLocalVideo(); |
|
|
|
|
}, this), 100); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.isConferenceRoom = function() { |
|
|
|
|
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) { |
|
|
|
|
|
|
|
|
|
//console.log(">>>>>>>>>>>>", type, from, data, to, to2);
|
|
|
|
@ -191,227 +209,210 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -191,227 +209,210 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
WebRTC.prototype.callForEachCall = function(fn) { |
|
|
|
|
var calls = this.conference.getCalls(); |
|
|
|
|
if (!calls.length) { |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
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; |
|
|
|
|
if (this.currentcall) { |
|
|
|
|
fn(this.currentcall, count); |
|
|
|
|
count++; |
|
|
|
|
if (this.currentconference) { |
|
|
|
|
_.each(this.currentconference.calls, function(v, count) { |
|
|
|
|
fn(v); |
|
|
|
|
count++; |
|
|
|
|
}); |
|
|
|
|
WebRTC.prototype.pushFrontMessage = function(id, message) { |
|
|
|
|
this._getMessageQueue(id, true).unshift(message); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.popFrontMessage = function(id) { |
|
|
|
|
var queue = this._getMessageQueue(id); |
|
|
|
|
if (!queue) { |
|
|
|
|
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") { |
|
|
|
|
console.log('PeerConnection has not been created yet!'); |
|
|
|
|
WebRTC.prototype._processAnswer = function(to, data, type, to2, from) { |
|
|
|
|
var call = this.conference.getCall(from); |
|
|
|
|
if (!call) { |
|
|
|
|
console.warn("Received Answer from unknown id -> ignore", from); |
|
|
|
|
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) { |
|
|
|
|
case "Offer": |
|
|
|
|
console.log("Offer process."); |
|
|
|
|
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]); |
|
|
|
|
} |
|
|
|
|
this._processOffer(to, data, type, to2, from); |
|
|
|
|
break; |
|
|
|
|
case "Candidate": |
|
|
|
|
targetcall = this.findTargetCall(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);
|
|
|
|
|
this._processCandidate(to, data, type, to2, from); |
|
|
|
|
break; |
|
|
|
|
case "Answer": |
|
|
|
|
targetcall = this.findTargetCall(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); |
|
|
|
|
}); |
|
|
|
|
this._processAnswer(to, data, type, to2, from); |
|
|
|
|
break; |
|
|
|
|
case "Bye": |
|
|
|
|
targetcall = this.findTargetCall(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]); |
|
|
|
|
this._processBye(to, data, type, to2, from); |
|
|
|
|
break; |
|
|
|
|
case "Conference": |
|
|
|
|
if ((!this.currentcall || data.indexOf(this.currentcall.id) === -1) && !this.isConferenceRoom()) { |
|
|
|
|
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]); |
|
|
|
|
} |
|
|
|
|
this._processConference(to, data, type, to2, from); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
console.log("Unhandled message type", type, data); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.testMediaAccess = function(cb) { |
|
|
|
@ -419,50 +420,46 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -419,50 +420,46 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
var success = function(stream) { |
|
|
|
|
console.info("testMediaAccess success"); |
|
|
|
|
cb(true); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
var failed = function() { |
|
|
|
|
console.info("testMediaAccess failed"); |
|
|
|
|
cb(false); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
UserMedia.testGetUserMedia(success, failed); |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.createCall = function(id, from, to) { |
|
|
|
|
|
|
|
|
|
var currentcall = new PeerCall(this, id, from, to); |
|
|
|
|
currentcall.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) { |
|
|
|
|
var call = new PeerCall(this, id, from, to); |
|
|
|
|
call.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) { |
|
|
|
|
this.onConnectionStateChange(iceConnectionState, currentcall); |
|
|
|
|
}, this)); |
|
|
|
|
currentcall.e.on("remoteStreamAdded", _.bind(function(event, stream, currentcall) { |
|
|
|
|
call.e.on("remoteStreamAdded", _.bind(function(event, stream, currentcall) { |
|
|
|
|
this.onRemoteStreamAdded(stream, currentcall); |
|
|
|
|
}, this)); |
|
|
|
|
currentcall.e.on("remoteStreamRemoved", _.bind(function(event, stream, currentcall) { |
|
|
|
|
call.e.on("remoteStreamRemoved", _.bind(function(event, stream, currentcall) { |
|
|
|
|
this.onRemoteStreamRemoved(stream, currentcall); |
|
|
|
|
}, this)); |
|
|
|
|
currentcall.e.on("error", _.bind(function(event, id, message) { |
|
|
|
|
if (!id) { |
|
|
|
|
id = "failed_peerconnection"; |
|
|
|
|
call.e.on("error", _.bind(function(event, error_id, message) { |
|
|
|
|
if (!error_id) { |
|
|
|
|
error_id = "failed_peerconnection"; |
|
|
|
|
} |
|
|
|
|
this.e.triggerHandler("error", [message, id]); |
|
|
|
|
_.defer(_.bind(this.doHangup, this), "error", currentcall.id); // Hangup on error is good yes??
|
|
|
|
|
this.e.triggerHandler("error", [message, error_id]); |
|
|
|
|
_.defer(_.bind(this.doHangup, this), "error", id); // Hangup on error is good yes??
|
|
|
|
|
}, this)); |
|
|
|
|
|
|
|
|
|
return currentcall; |
|
|
|
|
|
|
|
|
|
call.e.on("closed", _.bind(function() { |
|
|
|
|
this.conference.removeCall(id); |
|
|
|
|
}, this)); |
|
|
|
|
return call; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.doUserMediaWithInternalCall = function() { |
|
|
|
|
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(call) { |
|
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
var usermedia = new UserMedia({ |
|
|
|
@ -472,8 +469,12 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -472,8 +469,12 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
}); |
|
|
|
|
usermedia.e.on("mediasuccess mediaerror", _.bind(function(event, um) { |
|
|
|
|
this.e.triggerHandler("usermedia", [um]); |
|
|
|
|
this.usermediaReady = true; |
|
|
|
|
// Start always, no matter what.
|
|
|
|
|
this.maybeStart(um); |
|
|
|
|
while (this.pendingMediaCalls.length > 0) { |
|
|
|
|
var c = this.pendingMediaCalls.shift(); |
|
|
|
|
this.maybeStart(um, c); |
|
|
|
|
} |
|
|
|
|
}, this)); |
|
|
|
|
usermedia.e.on("mediachanged", _.bind(function(event, um) { |
|
|
|
|
// Propagate media change events.
|
|
|
|
@ -482,7 +483,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -482,7 +483,9 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
usermedia.e.on("stopped", _.bind(function(event, um) { |
|
|
|
|
if (um === this.usermedia) { |
|
|
|
|
this.e.triggerHandler("usermedia", [null]); |
|
|
|
|
this.usermediaReady = false; |
|
|
|
|
this.usermedia = null; |
|
|
|
|
this.maybeStartLocalVideo(); |
|
|
|
|
} |
|
|
|
|
}, this)); |
|
|
|
|
this.e.one("stop", function() { |
|
|
|
@ -490,55 +493,99 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -490,55 +493,99 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
}); |
|
|
|
|
this.usermedia = usermedia; |
|
|
|
|
this.e.triggerHandler("usermedia", [usermedia]); |
|
|
|
|
this.pendingMediaCalls.push(call); |
|
|
|
|
|
|
|
|
|
return usermedia.doGetUserMedia(currentcall); |
|
|
|
|
return usermedia.doGetUserMedia(call); |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.doCall = function(id) { |
|
|
|
|
|
|
|
|
|
if (this.currentcall) { |
|
|
|
|
// Conference mode.
|
|
|
|
|
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]); |
|
|
|
|
WebRTC.prototype._doCallUserMedia = function(call) { |
|
|
|
|
if (this.usermedia) { |
|
|
|
|
if (!this.usermediaReady) { |
|
|
|
|
this.pendingMediaCalls.push(call); |
|
|
|
|
} else { |
|
|
|
|
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); |
|
|
|
|
return this.doHangup(); |
|
|
|
|
this.maybeStart(this.usermedia, call); |
|
|
|
|
} |
|
|
|
|
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.
|
|
|
|
|
var currentcall = this.currentcall; |
|
|
|
|
if (!currentcall) { |
|
|
|
|
console.warn("Trying to accept without a call.", currentcall); |
|
|
|
|
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.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; |
|
|
|
|
} |
|
|
|
|
var ok = this.doUserMedia(currentcall); |
|
|
|
|
if (ok) { |
|
|
|
|
this.e.triggerHandler("waitforusermedia", [currentcall]); |
|
|
|
|
} else { |
|
|
|
|
this.e.triggerHandler("error", ["Failed to access camera/microphone.", "failed_getusermedia"]); |
|
|
|
|
return this.doHangup(); |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
@ -670,99 +717,94 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
@@ -670,99 +717,94 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
|
|
|
|
|
|
|
|
|
|
WebRTC.prototype.stop = function() { |
|
|
|
|
|
|
|
|
|
if (this.currentconference) { |
|
|
|
|
this.currentconference.close(); |
|
|
|
|
this.currentconference = null; |
|
|
|
|
} |
|
|
|
|
if (this.currentcall) { |
|
|
|
|
this.currentcall.close(); |
|
|
|
|
this.currentcall = null; |
|
|
|
|
} |
|
|
|
|
this.conference.close(); |
|
|
|
|
this.e.triggerHandler("peerconference", [null]); |
|
|
|
|
this.e.triggerHandler("peercall", [null]); |
|
|
|
|
this.e.triggerHandler("stop"); |
|
|
|
|
this.msgQueue.length = 0; |
|
|
|
|
this.initiator = null; |
|
|
|
|
this.started = false; |
|
|
|
|
this.msgQueues = {}; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
if (id) { |
|
|
|
|
var currentcall = this.findTargetCall(id); |
|
|
|
|
if (!currentcall) { |
|
|
|
|
console.warn("Tried to hangup unknown call.", reason, id); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (currentcall !== this.currentcall) { |
|
|
|
|
currentcall.close(); |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
var call = this.conference.removeCall(id); |
|
|
|
|
if (!call) { |
|
|
|
|
console.warn("Tried to hangup unknown call.", reason, id); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
call.close(); |
|
|
|
|
if (reason !== "receivedbye") { |
|
|
|
|
this.api.sendBye(id, reason); |
|
|
|
|
} |
|
|
|
|
if (this.currentcall) { |
|
|
|
|
id = this.currentcall.id; |
|
|
|
|
var calls = this.conference.getCalls(); |
|
|
|
|
if (!calls.length) { |
|
|
|
|
// Last peer disconnected, perform cleanup.
|
|
|
|
|
this.e.triggerHandler("peercall", [null]); |
|
|
|
|
_.defer(_.bind(function() { |
|
|
|
|
this.e.triggerHandler("done", [reason]); |
|
|
|
|
}, this)); |
|
|
|
|
this.stop(); |
|
|
|
|
} else if (calls.length === 1) { |
|
|
|
|
this.e.triggerHandler("peerconference", [null]); |
|
|
|
|
this.e.triggerHandler("peercall", [calls[0]]); |
|
|
|
|
} |
|
|
|
|
this.stop(); |
|
|
|
|
if (id) { |
|
|
|
|
if (reason !== "receivedbye") { |
|
|
|
|
this.api.sendBye(id, reason); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.maybeStart = function(usermedia) { |
|
|
|
|
|
|
|
|
|
//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)); |
|
|
|
|
WebRTC.prototype.maybeStart = function(usermedia, call, autocall) { |
|
|
|
|
|
|
|
|
|
//console.log("maybeStart", call);
|
|
|
|
|
if (call.peerconnection) { |
|
|
|
|
console.log("Already started", call); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
WebRTC.prototype.calleeStart = function() { |
|
|
|
|
|
|
|
|
|
var args; |
|
|
|
|
while (this.msgQueue.length > 0) { |
|
|
|
|
args = this.msgQueue.shift(); |
|
|
|
|
this.processReceivedMessage.apply(this, args); |
|
|
|
|
if (!autocall) { |
|
|
|
|
if (!call.isinternal) { |
|
|
|
|
this.e.triggerHandler("connecting", [call]); |
|
|
|
|
} else if (!this.conference.hasCalls()) { |
|
|
|
|
// Signal UI that media access has been granted.
|
|
|
|
|
this.e.triggerHandler("done"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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) { |
|
|
|
|