- From: Adam Bergkvist <adam.bergkvist@ericsson.com>
- Date: Thu, 26 Jan 2012 13:41:52 +0100
- To: "public-webrtc@w3.org" <public-webrtc@w3.org>
Hi
I've done some JS coding to compare the API changes introduces by JSEP with the
existing API (with e.g. ROAP under the hood. Basically all the work is done in
the configurePeer() method. There are two versions of that method in the code
below; on for the old API and then a version with JSEP. (The version with
the old API runs in the Ericsson prototype if the first argument getUserMedia
is changed to a string.)
The basic flow of the application is:
1. configurePeer() is called either with or without an initial offer; if there's
an initial offer we're being called, otherwise we're initiating a call.
2. configurePeer() creates a PeerConnection object and sets up the necessary
event listeners.
3. The signaling channel is re-configured (from accepting a call) to handle
incoming signaling messages.
4. If we're the caller the initiate a new call, otherwise we handle an incoming
call.
<script>
// shared code
var pc;
var addButton;
var selfView;
var remoteView;
var signalingChannel;
window.onload = function () {
document.getElementById("call_button").onclick = initiateCall;
addButton = document.getElementById("add_button");
selfView = document.getElementById("self_view");
remoteView = document.getElementById("remote_view");
signalingChannel = new SignalingChannel();
// setup message handler to handle an incoming call
signalingChannel.onmessage = function (evt) {
configurePeer(evt.data);
};
};
function initiateCall() {
configurePeer();
}
// old API (with ROAP)
function configurePeer(initialOffer) {
navigator.webkitGetUserMedia({"audio": true, "video": true}, function (localStream) {
// create a new PeerConnection object and direct all generated signaling messages
// to the other peer via the signaling channel
pc = new webkitPeerConnection("", signalingChannel.send);
// reset message handler to feed incoming signaling messages to PeerConnection
signalingChannel.onmessage = function (evt) {
pc.processSignalingMessage(evt.data);
};
// once remote stream arrives, show it in the remote view video element
pc.onaddstream = function (evt) {
remoteView.src = webkitURL.createObjectURL(evt.stream);
};
// once the PeerConnection is open, prepare for adding a new stream to call
pc.onopen = function () {
addButton.onclick = function () {
navigator.webkitGetUserMedia({"audio": true, "video": true}, function (localStream) {
// show the new stream in the self-view
selfView.src = webkitURL.createObjectURL(localStream);
// add the new local stream to be sent
pc.addStream(localStream);
});
};
};
// if we have a initial offer (we're being called), process it
if (initialOffer)
pc.processSignalingMessage(initialOffer);
// show local stream as self-view
selfView.src = webkitURL.createObjectURL(localStream);
// add local stream to be sent
pc.addStream(localStream);
});
}
// JSEP
function configurePeer(initialOffer) {
navigator.webkitGetUserMedia({"audio": true, "video": true}, function (localStream) {
var updateOffer;
var hasOutstandingOffer = false;
var hasPendingStreamsToOffer = false;
var calleeIceStarted = false;
// create a new PeerConnection object and direct all generated ice candidate messages
// to the other peer via the signaling channel
pc = new webkitPeerConnection("", function (candidate) {
signalingChannel.send(JSON.stringify({ "type": "candidate", "candidate": candidate }));
});
function createAndSendUpdateOffer() {
// create a new updated sdp offer and send it to the other peer via the signaling channel
// (we can't call setLocalDescription() rigth away so we store the sdp offer in local variable)
updateOffer = pc.createOffer();
signalingChannel.send(JSON.stringify({ "type": "offer", "sdp": updateOffer }));
// mark that we have an outstanding offer
hasOutstandingOffer = true;
}
// reset message handler to handle incoming signaling messages
signalingChannel.onmessage = function (evt) {
var msg = JSON.parse(evt.data);
if (msg.type == "candidate") {
// feed any incoming candidates to our PeerConnection
pc.processIceMessage(msg.candidate);
// time for callee to start ice processing
if (initialOffer && !calleeIceStarted) {
pc.connect();
calleeIceStarted = true;
}
} else if (msg.type == "offer") {
// feed the sdp offer into PeerConnection
pc.setRemoteDescription(PeerConnection.SDP_OFFER, msg.sdp);
// create an sdp answer based on the offer and set it as our local description
var answer = pc.createAnswer(msg.sdp);
pc.setLocalDescription(PeerConnection.SDP_ANSWER, answer);
// send the answer via the signaling channel
signalingChannel.send(JSON.stringify({ "type": "answer", "sdp": answer }));
} else if (msg.type == "answer") {
// if the answer corresponds to an updated offer (i.e. not the initial offer),
// we need to process both the updated offer as well as the received answer
if (!msg.initialAnswer)
pc.setLocalDescription(PeerConnection.SDP_OFFER, updateOffer);
pc.setRemoteDescription(PeerConnection.SDP_ANSWER, msg.sdp);
// mark that we have received an answer and no longer has an outstanding offer
hasOutstandingOffer = false;
// offer any pending streams added while we had an outstanding offer
if (hasPendingStreamsToOffer) {
createAndSendUpdateOffer();
hasPendingStreamsToOffer = false;
}
}
};
// once remote stream arrives, show it in the remote view video element
pc.onaddstream = function (evt) {
remoteView.src = webkitURL.createObjectURL(evt.stream);
};
// once the PeerConnection is open, prepare for adding a new stream to call
pc.onopen = function () {
addButton.onclick = function () {
navigator.webkitGetUserMedia({"audio": true, "video": true}, function (localStream) {
// show the new stream in the self-view
selfView.src = webkitURL.createObjectURL(localStream);
// add the new local stream to be sent
pc.addStream(localStream);
// check if we have an outstanding offer (i.e. we can't send a new one until
// we have received an answer)
if (hasOutstandingOffer) {
hasPendingStreamsToOffer = true;
return;
}
createAndSendUpdateOffer();
});
};
};
// show local stream as self-view
selfView.src = webkitURL.createObjectURL(localStream);
// if we have an initial offer, we're being called; otherwise we're initiating a call
if (initialOffer) {
// parse the incoming offer
var offer = JSON.parse(initialOffer);
// feed the sdp offer into PeerConnection
pc.setRemoteDescription(PeerConnection.SDP_OFFER, offer.sdp);
// add the reply stream to be sent
pc.addStream(localStream);
// create an sdp answer based on the offer and set it as our local description
var answer = pc.createAnswer(offer.sdp);
pc.setLocalDescription(PeerConnection.SDP_ANSWER, answer);
// send the answer via the signaling channel (mark it as an anser to the initial offer)
signalingChannel.send(JSON.stringify({ "type": "answer", "initialAnswer": true, "sdp": answer }));
} else { // we're initiating a call
// add the stream to be sent
pc.addStream(localStream);
// create the initial sdp offer and set it as our local description
var offer = pc.createOffer();
pc.setLocalDescription(PeerConnection.SDP_OFFER, offer);
// send the offer to the other peer via the signaling channel
signalingChannel.send(JSON.stringify({ "type": "offer", "sdp": offer }));
// start gathering candidates
pc.connect();
}
});
}
</script>
Some comments:
The API changes that JSEP introduces makes it quite a bit more complicated to work
with compared to the existing API (with e.g. ROAP under the hood). For example:
- More code for a simple example (e.g. to be presented as an example in the spec).
- JSEP has five ways to feed signaling information to PeerConnection compared to
one in the ROAP case. You really need to know what you're getting on the signaling
channel.
- Special cases such as glare and multiple offers have to be handled in JavaScript.
A simple example with the existing API (and ROAP) is quite powerful sice special
cases are handled under the hood.
It's also a question if PeerConnection.createOffer() and PeerConnection.createAnswer()
actually can have a return value? If the browser needs to reach down into the platform
to gather information these methods may need to be async and use a callback.
----
BR
Adam
Received on Thursday, 26 January 2012 12:42:18 UTC