Re: Code examples: Existing API and JSEP

On Thu, Jan 26, 2012 at 7:41 AM, Adam Bergkvist <adam.bergkvist@ericsson.com
> wrote:

>
> 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.
>

Right, this is what we acknowledged as a tradeoff of JSEP; by pushing more
control into JS, more code is needed. But as you can see, the amount of
code is not that large, and this could easily be placed into a JS library.

>
> 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.
>

Good point. I'd be interested in knowing whether anyone would need this.

>
>

Received on Friday, 27 January 2012 04:25:49 UTC