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');