How to use RTCDataChannel?
HOME © Muaz Khan . @WebRTCWeb . Github . Latest issues . What's New?
This tutorial is out-dated (written in 2013). Please check this tutorial instead: https://codelabs.developers.google.com/codelabs/webrtc-web/#0
This document guides you through:
- How to setup SCTP data connection (for chrome)?
- How to setup SCTP data connection (for firefox)?
- How to setup RTP data connection (for older chrome releases)?
A few terminologies:
- There is always an "offerer".
- There is always an "answerer".
- "Offerer" creates offer; it must be shared with "answerer".
- "answerer" sets his remote session-descriptions based on "offer-sdp" provided by offerer.
- "answerer" creates answer; it must be shared with "Offerer".
- "Offerer" sets his remote session-descriptions based on "answerer-sdp" provided by answerer.
SCTP data-connection and Chrome!
Setting global variables used by both offerer and answerer.
Global variables:
var iceServers = { iceServers: [{ url: 'stun:stun.l.google.com:19302' }] }; var offererDataChannel, answererDataChannel;
Creating two objects; one for offerer; and other for answerer.
Object for offerer:
var Offerer = { createOffer: function () { var peer = new webkitRTCPeerConnection(iceServers); offererDataChannel = peer.createDataChannel('channel', {}); setChannelEvents(offererDataChannel); peer.onicecandidate = function (event) { if (event.candidate) Send_to_Other_Peer(event.candidate); }; peer.createOffer(function (sdp) { peer.setLocalDescription(sdp); Send_to_Other_Peer(sdp); }); this.peer = peer; return this; }, setRemoteDescription: function (sdp) { this.peer.setRemoteDescription(new RTCSessionDescription(sdp)); }, addIceCandidate: function (candidate) { this.peer.addIceCandidate(new RTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); } };
Object for answerer:
var Answerer = { createAnswer: function (offerSDP) { var peer = new RTCPeerConnection(iceServers); peer.ondatachannel = function (event) { answererDataChannel = event.channel; setChannelEvents(answererDataChannel); }; peer.onicecandidate = function (event) { if (event.candidate) Send_to_Other_Peer(event.candidate); }; peer.setRemoteDescription(new RTCSessionDescription(offerSDP)); peer.createAnswer(function (sdp) { peer.setLocalDescription(sdp); Send_to_Other_Peer(sdp); }); this.peer = peer; return this; }, addIceCandidate: function (candidate) { this.peer.addIceCandidate(new RTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); } };
setChannelEvents function:
function setChannelEvents(channel) { channel.onmessage = function (event) { var data = JSON.parse(event.data); console.log(data); }; channel.onopen = function () { channel.push = channel.send; channel.send = function (data) { channel.push(JSON.stringify(data)); }; }; channel.onerror = function (e) { console.error('channel.onerror', JSON.stringify(e, null, '\t')); }; channel.onclose = function (e) { console.warn('channel.onclose', JSON.stringify(e, null, '\t')); }; }
Now, you've two objects; one for offerer; and last one for answerer. You can execute/call them according to the situation of the user.
If a user is offerer:
var offerer = Offerer.createOffer();
If a user is answerer:
var answerer = Answerer.createAnswer(offerSDP);
A simple example:
var websocket = new WebSocket('ws://localhost:8888/'); websocket.onmessage = function (e) { e = JSON.parse(e.data); // Don't get self sent messages if (e.senderid == userid) return; var data = e.data; // if other user created offer; and sent you offer-sdp if (data.offerSDP) { window.answerer = Answerer.createAnswer(data.offerSDP); } // if other user created answer; and sent you answer-sdp if (data.answerSDP) { window.offerer.setRemoteDescription(data.answerSDP); } // if other user sent you ice candidates if (data.ice) { // it will be fired both for offerer and answerer (window.answerer || window.offerer).addIceCandidate(data.ice); } }; var userid = Math.random() * 1000; websocket.push = websocket.send; websocket.send = function (data) { // wait/loop until socket connection gets open if (websocket.readState != 1) { // websocket connection is not opened yet. return setTimeout(function () { websocket.send(data); }, 500); } // data is stringified because websocket protocol accepts only string data var json_stringified_data = JSON.stringify({ senderid: userid, data: data }); websocket.push(json_stringified_data); }
SCTP data-connection and Firefox!
Setting global variables used by both offerer and answerer.
Global variables:
var iceServers = { iceServers: [{ url: 'stun:23.21.150.121' }] }; var offerAnswerConstraints = { optional: [], mandatory: { OfferToReceiveAudio: true, OfferToReceiveVideo: true } }; var offererDataChannel, answererDataChannel;
Creating two objects; one for offerer; and other for answerer.
Object for offerer:
var Offerer = { createOffer: function () { var peer = new mozRTCPeerConnection(iceServers); peer.onicecandidate = function (event) { if (event.candidate) Send_to_Other_Peer(event.candidate); }; peer.ondatachannel = function (event) { offererDataChannel = event.channel; setChannelEvents(offererDataChannel); }; peer.onconnection = function () { websocket.send({askToCreateDataChannel:true}); }; navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peer.addStream(stream); offererDataChannel = peer.createDataChannel('channel', {}); setChannelEvents(offererDataChannel); // create offer after attaching fake media stream peer.createOffer(function (sdp) { peer.setLocalDescription(sdp); Send_to_Other_Peer(sdp); }); }, function(error) {} ); this.peer = peer; return this; }, setRemoteDescription: function (sdp) { this.peer.setRemoteDescription(new mozRTCSessionDescription(sdp)); }, addIceCandidate: function (candidate) { this.peer.addIceCandidate(new mozRTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); } };
Object for answerer:
var Answerer = { createAnswer: function (offerSDP) { var peer = new mozRTCPeerConnection(iceServers); peer.ondatachannel = function (event) { answererDataChannel = event.channel; setChannelEvents(answererDataChannel); }; peer.onicecandidate = function (event) { if (event.candidate) Send_to_Other_Peer(event.candidate); }; navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peer.addStream(stream); // create answer after attaching fake media stream peer.setRemoteDescription(new mozRTCSessionDescription(offerSDP)); peer.createAnswer(function (sdp) { peer.setLocalDescription(sdp); Send_to_Other_Peer(sdp); }); }, function(error) {}); this.peer = peer; return this; }, addIceCandidate: function (candidate) { this.peer.addIceCandidate(new mozRTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); } };
setChannelEvents function:
function setChannelEvents(channel) { channel.onmessage = function (event) { var data = JSON.parse(event.data); console.log(data); }; channel.onopen = function () { channel.push = channel.send; channel.send = function (data) { channel.push(JSON.stringify(data)); }; }; channel.onerror = function (e) { console.error('channel.onerror', JSON.stringify(e, null, '\t')); }; channel.onclose = function (e) { console.warn('channel.onclose', JSON.stringify(e, null, '\t')); }; }
Now, you've two objects; one for offerer; and last one for answerer. You can execute/call them according to the situation of the user.
If a user is offerer:
var offerer = Offerer.createOffer();
If a user is answerer:
var answerer = Answerer.createAnswer(offerSDP);
A simple example:
var websocket = new WebSocket('ws://localhost:8888/'); websocket.onmessage = function (e) { e = JSON.parse(e.data); // Don't get self sent messages if (e.senderid == userid) return; var data = e.data; // if other user created offer; and sent you offer-sdp if (data.offerSDP) { window.answerer = Answerer.createAnswer(data.offerSDP); } // if other user created answer; and sent you answer-sdp if (data.answerSDP) { window.offerer.setRemoteDescription(data.answerSDP); } // if other user sent you ice candidates if (data.ice) { // it will be fired both for offerer and answerer (window.answerer || window.offerer).addIceCandidate(data.ice); } // we need to create data channel for both ends! (on Firefox only) if(data.askToCreateDataChannel) { answererDataChannel = peer.createDataChannel('channel', {}); setChannelEvents(answererDataChannel); } }; var userid = Math.random() * 1000; websocket.push = websocket.send; websocket.send = function (data) { // wait/loop until socket connection gets open if (websocket.readState != 1) { // websocket connection is not opened yet. return setTimeout(function () { websocket.send(data); }, 500); } // data is stringified because websocket protocol accepts only string data var json_stringified_data = JSON.stringify({ senderid: userid, data: data }); websocket.push(json_stringified_data); }
RTP data-connection and Chrome!
var iceServers = { iceServers: [{ url: 'stun:stun.l.google.com:19302' }] }; var optionalRtpDataChannels = { optional: [{ RtpDataChannels: true }] }; var offerer = new webkitRTCPeerConnection(iceServers, optionalRtpDataChannels), answerer, answererDataChannel; var offererDataChannel = offerer.createDataChannel('RTCDataChannel', { reliable: false }); setChannelEvents(offererDataChannel, 'offerer'); offerer.onicecandidate = function (event) { if (!event || !event.candidate) return; answerer && answerer.addIceCandidate(event.candidate); }; var mediaConstraints = { optional: [], mandatory: { OfferToReceiveAudio: false, // Hmm!! OfferToReceiveVideo: false // Hmm!! } }; offerer.createOffer(function (sessionDescription) { offerer.setLocalDescription(sessionDescription); createAnswer(sessionDescription); }, null, mediaConstraints); function createAnswer(offerSDP) { answerer = new webkitRTCPeerConnection(iceServers, optionalRtpDataChannels); answererDataChannel = answerer.createDataChannel('RTCDataChannel', { reliable: false }); setChannelEvents(answererDataChannel, 'answerer'); answerer.onicecandidate = function (event) { if (!event || !event.candidate) return; offerer && offerer.addIceCandidate(event.candidate); }; answerer.setRemoteDescription(offerSDP); answerer.createAnswer(function (sessionDescription) { answerer.setLocalDescription(sessionDescription); offerer.setRemoteDescription(sessionDescription); }, null, mediaConstraints); } function setChannelEvents(channel, channelNameForConsoleOutput) { channel.onmessage = function (event) { console.debug(channelNameForConsoleOutput, 'received a message:', event.data); }; channel.onopen = function () { channel.send('first text message over RTP data ports'); }; channel.onclose = function (e) { console.error(e); }; channel.onerror = function (e) { console.error(e); }; }
After executing above code in the console; try to send messages over RTP data ports like this:
offererDataChannel.send('message from offerer'); answererDataChannel.send('message from answerer');