In this tutorial, we are going to focus on the transferring arbitrary data using WebRTC Data Channel Protocol.

The WebRTC introduces the SCTP (Stream Control Transmission Protocol) as a way of sending data through the peer connection. SCTP is built on top of the DTLS (Datagram Transport Layer Security), which is sitting on top of the UDP stack.

We can establish the RTCDataChannel API this way:


var peerConnection = new RTCPeerConnection();

//establish peer connection using signalling here

var dataChannelOptions = {
    reliable: false,
    maxTransmitTime: 3000
};

var dataChannel = peerConnection.createDataChannel("myLable", dataChannelOptions);

And that’s it! The RTCDataChannel is established. This will happen once signaling has been performed and the connection has been successfully created.

The data channel can be in the following states: 1. connecting A default state, data channel waits for a connection. 2. open The connection is established. 3. closing The channel is being destroyed. 4. closed The channel is closed and communication is not possible.

When the other peer creates a channel, the ondatachannel event of the RTCPeerConnection object is fired. The RTCDataChannel object also provides a few straightforward events:


dataChannel.onerror = function(error){
    console.log("Data channel error:", error);
};

dataChannel.onmessage = function(event){
    console.log("Data channel message:", event.data);
};

dataChannel.onopen = function(){
    console.log("Data channel opened, ready to send messages!");
    dataChannel.send("Hello world!");
};

dataChannel.onclose = function(){
    console.log("Data channel has been closed");
};

You can configure your data channel options using the second parameter in the constructor. They are: 1. reliable Guarantee or not message delivery. (TCP or UDP) 2. ordered Whether messages should be sent and received the right order. 3. maxRetransmitTime Max time to resend a failed message. 4. maxRetransmits Max number of times to resend a failed message. 5. protocol Will force a different subprotocol. Will show an error if it is not supported. 6. negotiated Whether the developer is responsible for creating data channels on both peers or the browser should perform this step automatically. 7. id Channel ID

The send method of the data channel allows to send different javascript types over the transport layer: 1. String A simpe javascript string 2. Blob A file-like format of raw data 3. ArrayBuffer A typed-array 4. ArrayBufferView A view frame of the ArrayBuffer

You can identify the type this way:


dataChannel.onmessage = function(event){
    console.log("Data channel message:", event.data);

    var data = event.data;

    if(data instanceof Blob){
        //handle blob
    } else if (data instanceof ArrayBuffer){
        //handle ArrayBuffer
    } else if (data instanceof  ArrayBufferView){
        //handle ArrayBufferView
    } else {
        //handle string
    }

};

So let’s add a text chat in our application. It will look something like this:

chat

Run our signaling server, which we created in part 3. Add the following index.html file:

Login As

Login

Call
Hang Up

Send

Add the following client.js file:


//our user
var name;
//user connected to us
var connectedUser;

//setup a connection to websocket server
var connection = new WebSocket('ws://localhost:9090');

connection.onopen = function(){
    console.log("Connected");
};

//handling messages we got from the server
connection.onmessage = function(message){

    console.log("Got message", message.data);

    var data = JSON.parse(message.data);

    switch(data.type){
        //check for login success
        case "login":
            onLogin(data.success);
            break;
        //when a user wants to connect to us
        case "offer":
            onOffer(data.offer, data.name);
            break;
        //when we send offer to a user and he send back an answer to us
        case "answer":
            onAnswer(data.answer);
            break;
        //when remote user sends us an ice candidate
        case "candidate":
            onCandidate(data.candidate);
            break;
        //when remote user leaves us
        case "leave":
            onLeave();
            break;
        default:
            break;
    }

};

connection.onerror = function(err){
    console.log("Got error", err);
};

//alias for sending messages in json format
function send(message){
    if(connectedUser){
        message.name = connectedUser;
    }
    connection.send(JSON.stringify(message));
}

//Login implementation
var loginPage = document.querySelector('#login-page'),
    usernameInput = document.querySelector('#username'),
    loginButton = document.querySelector('#login'),
    callPage = document.querySelector('#call-page'),
    theirUsernameInput = document.querySelector('#their-username'),
    callButton = document.querySelector('#call'),
    hangUpButton = document.querySelector('#hang-up');

//initially hide call page in order to show login page first
callPage.style.display = 'none';

//Login when the user clicks the button "login"
loginButton.addEventListener('click', function(event){
    name = usernameInput.value;
    if(name.length > 0){
        send({
            type: "login",
            name:name
        });
    }
});

function onLogin(success){
    if(success === false){
        alert("Login unsuccessful, please try a different name.");
    } else {
        //if login was successful
        loginPage.style.display = "none";
        callPage.style.display = "block";
        //set up requirements for maling a webrtc connection
        startConnection();
    }
}

//Starting a peer connection
var yourVideo = document.querySelector('#yours'),
    theirVideo = document.querySelector('#theirs'),
    yourConnection, stream;

function startConnection(){

    //if the user's device supports webrtc
    if(hasUserMedia()){
        navigator.getUserMedia({video: true, audio: true}, function(myStream){
            stream = myStream;
            //add local stream in the top right corner
            yourVideo.src = window.URL.createObjectURL(stream);

            if(hasRTCPeerConnection()){
                setupPeerConnection(stream);
            } else {
                alert("Sorry, your browser does not support WebRTC");
            }

        }, function(error){
            console.log(error);
        })
    } else {
        alert("Sorry, your browser does not support WebRTC");
    }

}

function setupPeerConnection(stream){
    var configuration = {
        "iceServers":[{"url":"stun:stun.1.google.com:19302"}]
    };
    yourConnection  = new RTCPeerConnection(configuration, {
        optional: [{
            RtpDataChannels: true
        }]
    });
    openDataChannel();

    //Setup stream listening
    yourConnection.addStream(stream);
    yourConnection.onaddstream = function(e){
        theirVideo.src = window.URL.createObjectURL(e.stream);
    };

    //setup ice handling
    //sending ice candidate to another user
    yourConnection.onicecandidate = function(event){
        console.log("onicecandidate");
        if(event.candidate){
            send({
                type: "candidate",
                candidate: event.candidate
            });
        }
    };

}

//check if the user's device supports webrtc
function hasUserMedia(){
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
        || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    return !!navigator.getUserMedia;
}

//check if the user's device supports rtcpeerconnection object
function hasRTCPeerConnection(){
    window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection
        || window.mozRTCPeerConnection;
    window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription
        || window.mozRTCSessionDescription;
    window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate
        || window.mozRTCIceCandidate;
    return !!window.RTCPeerConnection;
}

//Initiating a call to the other user
callButton.addEventListener("click", function(){
    var theirUsername = theirUsernameInput.value;
    if(theirUsername.length > 0){
        startPeerConnection(theirUsername);
    }
});

//sending offer and starting ice candidates trading
//remember this process is asynchronous
function startPeerConnection(user){

    connectedUser = user;

    //begin the offer
    yourConnection.createOffer(function(offer){
        send({
            type: "offer",
            offer: offer
        });
        yourConnection.setLocalDescription(offer);
    }), function(error){
        alert("An error has occured.");
    };

}

//when we receive an offer from another user
function onOffer(offer, name){
    connectedUser = name;
    yourConnection.setRemoteDescription(new RTCSessionDescription(offer));

    yourConnection.createAnswer(function(answer){
        yourConnection.setLocalDescription(answer);
        send({
            type: "answer",
            answer: answer
        });
    }, function(error){
        alert("An error has occured");
    });

}

//when we got an answer to our offer
function onAnswer(answer){
    yourConnection.setRemoteDescription(new RTCSessionDescription(answer));
}

//when we got ice candidate from a user we save it
function onCandidate(candidate){
    console.log("oncandidate");
    yourConnection.addIceCandidate(new RTCIceCandidate(candidate));
}

//Hanging up a call
//send the other user a leave message and destroy a connection
hangUpButton.addEventListener("click", function(){
    send({
        type: "leave"
    });
    onLeave();
});

function onLeave(){
    connectedUser = null;
    theirVideo.src = null;
    //close rtcpeerconnection and stop transmitting our stream to the other user
    yourConnection.close();
    yourConnection.onicecandidate = null;
    yourConnection.onaddstream = null;
    //setup rtcpeerconnection again to accept new calls
    setupPeerConnection(stream);
}

//Implementing text chat
function openDataChannel(){
    var dataChannelOptions = {
        reliable:true
    };
    dataChannel = yourConnection.createDataChannel("myLabel", dataChannelOptions);

    dataChannel.onerror = function(error){
        console.log("Data Channel Error:", error);
    };

    dataChannel.onmessage = function(event){
        console.log("Got Data Channel Message:", event.data);

        received.innerHTML += "recv: " + event.data + "
";
        received.scrollTop = received.scrollHeight;
    };

    dataChannel.onopen = function(){
        dataChannel.send(fo + "has connected");
    };

    dataChannel.onclose = function(){
        console.log("The Data Channel is closed");
    };

}

//when sending a text message
sendButton.addEventListener("click", function(event){
    console.log("send mes!");
    var val = messageInput.value;
    received.innerHTML += "send: " + val + "
";
    received.scrollTop = received.scrollHeight;
    dataChannel.send(val);
});

Then login with 2 users. Call one to another and try to send messages. This example works for Chome only.

That’s all. Hope this series was helpful to you.