Interoperability

Since sharing CU-RTC-Web<http://blogs.msdn.com/b/interoperability/archive/2012/08/06/customizable-ubiquitous-real-time-communication-over-the-web-cu-rtc-web.aspx> - our proposed design for the Web-RTC API - we have been experimenting with the API and its capabilities.  Today, we'd like to share a simple example application, to demonstrate the API capabilities.
We are still committed to the W3C process.  Our API proposal provides concrete recommendations for many of the open issues in the working group, even if it does still forgo the use of SDP.
Calling Gateways
Calling a legacy device using Web-RTC is a use case that we anticipate will be important many users.  Dialing in to an existing call center through a gateway is likely to be a common scenario as companies Web-RTC enable their sites.
[cid:image003.png@01CDB077.1DB10670]
In this simple configuration, an existing gateway services calls originating from the PSTN.  In order to interoperate with Web-RTC clients, this gateway needs to support ICE<http://tools.ietf.org/html/rfc5245> lite and G.711.

*         ICE or ICE lite is a critical part of Web-RTC security solution, even if this scenario does not require the NAT traversal capabilities it provides.  Full ICE is rarely implemented by gateway devices. This scenario assumes the gateway only supports ICE lite.  ICE lite was specifically designed for scenarios just like this.

*         Web-RTC implementations are required to support the G.711 audio codec.  It is expected that many legacy systems will not support the other audio codec that Web-RTC implementations require: Opus.
In this example deployment, the web server provides the following configuration to clients:

*         Transport details: IP and port

*         ICE details: username fragment and password

*         Secure RTP keys for both inbound and outbound media
The client then takes that information and establishes a bi-directional, secured RTP session with the gateway. The gateway forwards media between the call center and web client.
JavaScript for this simple application is shown below.
API Refinements
We've also made some improvements to our API proposal.  As we continue to gain experience with its use, we discover small errors and omissions.  An updated proposal is attached to this email.
We've also listened to the feedback we've received on our initial proposal.  The most common concern voiced was that asking application developers to implement ICE, or something like it, was too difficult.
In response to concerns over ease of use we've added the RealtimeTransportBuilder interface. Our original view was that third party libraries would be created to support this capability.  This is still possible with the API provided - our prototype of RealtimeTransportBuilder is implemented using nothing more than the RealtimePort and the RealtimeTransport APIs.
RealtimeTransportBuilder is designed to make it as easy as possible to construct a peer-to-peer transport. Application developers who use this interface will benefit from a browser-based implementation of NAT traversal. The application needs to provide a channel for exchanging port (or candidate) information between peers.  RealtimeTransportBuilder will do all of the hard work, producing a RealtimeTransport.
The following example code demonstrates how easy it is to use a RealtimeTransportBuilder:

var options = { transport: transportOptions, stun: stunServer };

var builder = new RealtimeTransportBuilder(options);

builder.onport = function(e) {

    signaling.send('port', e.port);

};

signaling.onport = function(port) {

    builder.addRemote(port);

};

builder.onconnect = function(e) {

    gotTransport(e.transport);  // at which point streams can be added, etc....

};

builder.start();

ICE Lite Gateway Client Code
The source code for our client is included below:

(function() {

    'use strict'; /*jshint browser:true*/

    /*global MediaStream,RealtimePort,RealtimeTransport,LocalRealtimeMediaStream,RemoteRealtimeMediaStream,RealtimeMediaDescription,getUserMedia,URL,b64*/

    var gatewayConfig;

    var localPorts;

    var localPort;

    var transport;

    var localMedia;



    function buildDescription(ssrc) {

        var g711Codec = {

            type: 'audio/PCMU',

            clockRate: 8000,

            packetType: 0

        };

        var stream = {

            ssrc: ssrc

        };

        return new RealtimeMediaDescription({

            streams: [stream],

            codecs: [g711Codec]

        });

    }



    function startOutgoingStream() {

        if(transport && localMedia) {

            var localDescription = buildDescription();

            var outgoingStream =

                    new LocalRealtimeMediaStream(localMedia.audioTracks.item(0),

                                                 localDescription, transport);

            outgoingStream.play();

        }

    }



    function discoveredSsrc(e) {

        var remoteDescription = buildDescription(e.ssrc);

        var rtStream = new RemoteRealtimeMediaStream(remoteDescription, transport);

        var incomingStream = new MediaStream();

        incomingStream.audioTracks.add(rtStream.track);

        document.getElementById('output').src = URL.createObjectURL(incomingStream);

    }



    function gotTransport(err, t) {

        transport = t;

        transport.addEventListener('unknownssrc', discoveredSsrc);

        startOutgoingStream();

    }



    function gotAudio(stream) {

        localMedia = stream;

        startOutgoingStream();

    }



    function portChecked(e) {

        var transportOptions;

        if(!localPort) {

            localPort = e.target;

            transportOptions = {

                mode: "srtp",

                outboundSdes: gatewayConfig.local.sdes,

                inboundSdes: gatewayConfig.remote.sdes

            };

            RealtimeTransport.createTransport(localPort, gatewayConfig.port,

                                              transportOptions, gotTransport);

        }

    }



    function checkPorts() {

        if(localPorts && gatewayConfig) {

            localPorts.forEach(function(port) {

                port.addEventListener('checksuccess', portChecked);

                port.check(gatewayConfig.port);

            });

        }

    }



    function gotPorts(err, ports) {

        localPorts = ports;

        checkPorts();

    }



    function gotGatewayConfig(e) {

        gatewayConfig = JSON.parse(e.target.responseText);

        gatewayConfig.local.sdes.key = b64.Decode(gatewayConfig.local.sdes.key);

        gatewayConfig.local.sdes.salt = b64.Decode(gatewayConfig.local.sdes.salt);

        gatewayConfig.remote.sdes.key = b64.Decode(gatewayConfig.remote.sdes.key);

        gatewayConfig.remote.sdes.salt = b64.Decode(gatewayConfig.remote.sdes.salt);

        checkPorts();

    }



    function go() {

        var xhr = new XMLHttpRequest();

        xhr.open('GET', '/config', true);

        xhr.addEventListener('load', gotGatewayConfig);

        xhr.send();



        RealtimePort.openLocalPorts(gotPorts);



        navigator.getUserMedia({

            audio: true

        }, gotAudio);

    }

    window.go = go;

}());
The sample code is embedded in an HTML page with an <audio id="output"> tag, and a button that invokes the go() function.
The sample relies on the web server providing a resource at "/config" that provides a JSON-formatted representation that contains details of the gateway configuration in the following form:

{

    "port": {

        "ip": "192.0.2.75",               // IP of gateway

        "port": 12345,                    // port

        "ufrag": "TaKOfCOILiyV1KGk",      // username fragment

        "pwd": "G/jcLmuXxHniW4vk"         // password

    },

    "local": {                            // parameters for outbound media

        "sdes": {                         // SRTP description

            "key": "vc6kySgR4ggsitwQiIAQkQ==",     // 16 byte, base64 encoded key

            "salt": "nPVZ4sBH2h84EEVXuWg="         // 14 byte, base64 encoded salt

        }

    },

    "remote": {                           // parameters for inbound media, as above

        "sdes": {

            "key": "n6BKAnhQ/cQI7uOtPebKMw==",

            "salt": "JgHUxBt7ZgSgyrrNT54="

        }

    }

}
This sample code relies on a b64 module that converts from the base64 encoded values above to the ArrayBuffer instances required by the CU-RTC-Web API..
Using RealtimeTransportBuilder
An astute observer will notice that the example above is not robust in the presence of packet loss.  It is also not immediately obvious that it is necessary to attempt a connectivity check using all local ports.  These are the sorts of errors that are easy to miss.
Using the RealtimeTransportBuilder simplifies the code and removes these issues. The updated sample has no need for the gotPorts(), checkPorts() and portChecked() functions.  The call to RealtimePort.openLocalPorts() is removed and a RealtimeTransportBuilder is constructed using data from the gateway configuration once that is retrieved.
Here is the modified JavaScript:

(function() {

    'use strict'; /*jshint browser:true*/

    /*global MediaStream,RealtimeTransportBuilder,LocalRealtimeMediaStream,RemoteRealtimeMediaStream,RealtimeMediaDescription,getUserMedia,URL,b64*/

    var gatewayConfig;

   var localPorts;

    var localPort;

    var transport;

    var localMedia;



    function buildDescription(ssrc) {

        var g711Codec = {

            type: 'audio/PCMU',

            clockRate: 8000,

            packetType: 0

        };

        var stream = {

            ssrc: ssrc

        };

        return new RealtimeMediaDescription({

            streams: [stream],

            codecs: [g711Codec]

        });

    }



    function startOutgoingStream() {

        if(transport && localMedia) {

            var localDescription = buildDescription();

            var outgoingStream =

                    new LocalRealtimeMediaStream(localMedia.audioTracks.item(0),

                                                 localDescription, transport);

            outgoingStream.play();

        }

    }



    function discoveredSsrc(e) {

        var remoteDescription = buildDescription(e.ssrc);

        var rtStream = new RemoteRealtimeMediaStream(remoteDescription, transport);

        var incomingStream = new MediaStream();

        incomingStream.audioTracks.add(rtStream.track);

        document.getElementById('output').src = URL.createObjectURL(incomingStream);

    }



    function gotTransport(err, t) {

        transport = t;

        transport.addEventListener('unknownssrc', discoveredSsrc);

        startOutgoingStream();

    }



    function gotAudio(stream) {

        localMedia = stream;

        startOutgoingStream();

    }



    function buildTransport(e) {

        var options = {

            transport: {

                mode: 'srtp',

                outboundSdes: gatewayConfig.local.sdes,

                inboundSdes: gatewayConfig.remote.sdes

            }

        };

        var transportBuilder = new RealtimeTransportBuilder(options);

        transportBuilder.addEventListener('connect', gotTransport);

        transportBuilder.start();

        transportBuilder.addRemote(gatewayConfig.port);

    }



    function gotGatewayConfig(e) {

        gatewayConfig = JSON.parse(e.target.responseText);

        gatewayConfig.local.sdes.key = b64.Decode(gatewayConfig.local.sdes.key);

        gatewayConfig.local.sdes.salt = b64.Decode(gatewayConfig.local.sdes.salt);

        gatewayConfig.remote.sdes.key = b64.Decode(gatewayConfig.remote.sdes.key);

        gatewayConfig.remote.sdes.salt = b64.Decode(gatewayConfig.remote.sdes.salt);

        buildTransport();

    }



    function go() {

        var xhr = new XMLHttpRequest();

        xhr.open('GET', '/config', true);

        xhr.addEventListener('load', gotGatewayConfig);

        xhr.send();



        navigator.getUserMedia({

            audio: true

        }, gotAudio);

    }

    window.go = go;

}());
This example shows how this API can be used to create a simple client that talks to a VoIP gateway. This enables some very important use cases for sites deploying WebRTC.  We encourage people to replicate this example with their own implementations.

Received on Tuesday, 23 October 2012 18:11:03 UTC