Skip to content

Instantly share code, notes, and snippets.

@slaskis
Last active December 11, 2015 04:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slaskis/4547040 to your computer and use it in GitHub Desktop.
Save slaskis/4547040 to your computer and use it in GitHub Desktop.
Data Channel Examples
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 1</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 480px;
height: 640px;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
</style>
</head>
<body>
<div id="left">
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled="true"></textarea><br>
<button id="startButton" onclick="createConnection()">Start</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled="true">
</textarea><br>
</div>
<script>
var pc1, pc2, sendChannel, receiveChannel;
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function createConnection() {
var servers = null;
pc1 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc1');
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
sendChannel = pc1.createDataChannel("sendDataChannel",
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
pc1.onicecandidate = iceCallback1;
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
pc2 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created remote peer connection object pc2');
pc2.onicecandidate = iceCallback2;
pc2.ondatachannel = receiveChannelCallback;
pc1.createOffer(gotDescription1);
startButton.disabled = true;
closeButton.disabled = false;
}
function sendData() {
var data = document.getElementById("dataChannelSend").value;
sendChannel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
trace('Closing data Channels');
sendChannel.close();
trace('Closed data channel with label: ' + sendChannel.label);
receiveChannel.close();
trace('Closed data channel with label: ' + receiveChannel.label);
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
trace('Closed peer connections');
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
}
function gotDescription1(desc) {
pc1.setLocalDescription(desc);
trace('Offer from pc1 \n' + desc.sdp);
pc2.setRemoteDescription(desc);
pc2.createAnswer(gotDescription2);
}
function gotDescription2(desc) {
pc2.setLocalDescription(desc);
trace('Answer from pc2 \n' + desc.sdp);
pc1.setRemoteDescription(desc);
}
function iceCallback1(event) {
trace('local ice callback');
if (event.candidate) {
pc2.addIceCandidate(event.candidate);
trace('Local ICE candidate: \n' + event.candidate.candidate);
}
}
function iceCallback2(event) {
trace('remote ice callback');
if (event.candidate) {
pc1.addIceCandidate(event.candidate);
trace('Remote ICE candidate: \n ' + event.candidate.candidate);
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message');
document.getElementById("dataChannelReceive").value = event.data;
}
function onSendChannelStateChange() {
var readyState = sendChannel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
var readyState = receiveChannel.readyState;
trace('Receive channel state is: ' + readyState);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 2 (w. video)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 480px;
height: 640px;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
</style>
</head>
<body>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea><br>
<button id="startButton" onclick="createConnection()">Start</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled="true">
</textarea><br>
</div>
<script>
var pc1, pc2, sendChannel, receiveChannel;
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function getLocalVideo(pc){
navigator.webkitGetUserMedia(
{video: true},
function(stream){
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc.addStream(stream)
pc.createOffer(gotDescription1);
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function createConnection() {
var servers = null;
pc1 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc1');
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
sendChannel = pc1.createDataChannel("sendDataChannel",
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
pc1.onicecandidate = iceCallback1;
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
pc2 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created remote peer connection object pc2');
pc2.onicecandidate = iceCallback2;
pc2.ondatachannel = receiveChannelCallback;
pc2.onaddstream = attachVideo;
pc2.onremovestream = detachVideo;
pc2.onclose = detachVideo;
// NOTE: if an offer has already been created before
// the media stream is added it will not show up
// it probably needs to recreate the PC to
// re-negotiate
// but the datachannel will work!
// pc1.createOffer(gotDescription1);
startButton.disabled = true;
closeButton.disabled = false;
getLocalVideo(pc1)
}
function sendData() {
var data = document.getElementById("dataChannelSend").value;
sendChannel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
trace('Closing data Channels');
sendChannel.close();
trace('Closed data channel with label: ' + sendChannel.label);
receiveChannel.close();
trace('Closed data channel with label: ' + receiveChannel.label);
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
trace('Closed peer connections');
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
}
function gotDescription1(desc) {
pc1.setLocalDescription(desc);
trace('Offer from pc1 \n' + desc.sdp);
pc2.setRemoteDescription(desc);
pc2.createAnswer(gotDescription2);
}
function gotDescription2(desc) {
pc2.setLocalDescription(desc);
trace('Answer from pc2 \n' + desc.sdp);
pc1.setRemoteDescription(desc);
}
function iceCallback1(event) {
trace('local ice callback');
if (event.candidate) {
pc2.addIceCandidate(event.candidate);
trace('Local ICE candidate: \n' + event.candidate.candidate);
}
}
function iceCallback2(event) {
trace('remote ice callback');
if (event.candidate) {
pc1.addIceCandidate(event.candidate);
trace('Remote ICE candidate: \n ' + event.candidate.candidate);
}
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote video')
document.getElementById('remote').src = null;
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message');
document.getElementById("dataChannelReceive").value = event.data;
}
function onSendChannelStateChange() {
var readyState = sendChannel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
var readyState = receiveChannel.readyState;
trace('Receive channel state is: ' + readyState);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 3 (w. signals)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 480px;
height: 640px;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
</style>
</head>
<body>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea><br>
<button id="startButton" onclick="createConnection()">Start</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled="true">
</textarea><br>
</div>
<script>
var pc1, pc2, sendChannel, receiveChannel, ws;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function getLocalVideo(){
navigator.webkitGetUserMedia(
{video: true},
function(stream){
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc1.addStream(stream)
pc1.createOffer(gotDescription1);
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function createSignals(){
ws = new WebSocket('ws://echo.websocket.org/');
ws.onopen = function(){
trace('WS open')
ws.onmessage = function(m){
trace('WS message: '+m.data)
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
var desc = new RTCSessionDescription(json);
pc2.setRemoteDescription(desc);
pc2.createAnswer(gotDescription2);
} else if( json.type == 'answer' ){
var desc = new RTCSessionDescription(json);
pc1.setRemoteDescription(desc);
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
}
startButton.disabled = false;
}
}
function createConnection() {
var servers = null;
pc1 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc1');
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
sendChannel = pc1.createDataChannel("sendDataChannel",
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
pc1.onicecandidate = iceCallback1;
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
pc2 = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created remote peer connection object pc2');
pc2.onicecandidate = iceCallback2;
pc2.ondatachannel = receiveChannelCallback;
pc2.onaddstream = attachVideo;
pc2.onremovestream = detachVideo;
pc2.onclose = detachVideo;
// NOTE: if an offer has already been created before
// the media stream is added it will not show up
// it probably needs to recreate the PC to
// re-negotiate
// but the datachannel will work!
// pc1.createOffer(gotDescription1);
startButton.disabled = true;
closeButton.disabled = false;
getLocalVideo(pc1)
}
function sendData() {
var data = document.getElementById("dataChannelSend").value;
sendChannel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
trace('Closing data Channels');
sendChannel.close();
trace('Closed data channel with label: ' + sendChannel.label);
receiveChannel.close();
trace('Closed data channel with label: ' + receiveChannel.label);
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
trace('Closed peer connections');
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
}
function gotDescription1(desc) {
pc1.setLocalDescription(desc);
trace('Offer from pc1 \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function gotDescription2(desc) {
pc2.setLocalDescription(desc);
trace('Answer from pc2 \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function iceCallback1(event) {
trace('local ice callback');
if (!pc2.remoteDescription) {
trace('Local ICE candidate: too soon?')
return
}
if (event.candidate) {
pc2.addIceCandidate(event.candidate);
trace('Local ICE candidate: \n' + event.candidate.candidate);
} else {
trace('Local ICE candidate: end of candidates')
}
}
function iceCallback2(event) {
trace('remote ice callback');
if (!pc1.remoteDescription) {
trace('Remote ICE candidate: too soon?')
return
}
if (event.candidate) {
pc1.addIceCandidate(event.candidate);
trace('Remote ICE candidate: \n ' + event.candidate.candidate);
} else {
trace('Remote ICE candidate: end of candidates')
}
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote video')
document.getElementById('remote').src = null;
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message');
document.getElementById("dataChannelReceive").value = event.data;
}
function onSendChannelStateChange() {
var readyState = sendChannel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
var readyState = receiveChannel.readyState;
trace('Receive channel state is: ' + readyState);
}
createSignals()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 4 (w. real signals)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 480px;
height: 640px;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
</style>
</head>
<body>
<div id="left">
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea><br>
<button id="startButton" onclick="sendOffer()">Start</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled></textarea><br>
</div>
<script>
var ws, pc, channel;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function createSignals(){
ws = new WebSocket('ws://nj.publicclass.co:8081/dc4');
ws.onopen = function(){
clearTimeout(ws.timeout)
trace('WS open')
createConnection()
ws.onmessage = function(m){
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
pc.createAnswer(gotAnswerDescription);
} else if( json.type == 'answer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
if( channel.readyState != 'open' )
sendOffer()
} else if( json.type == 'close' ){
closeDataChannels()
} else if( json.a && json.b ){
startButton.disabled = false;
} else {
trace('WS message: '+m.data)
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
closeDataChannels()
startButton.disabled = true;
}
}
ws.timeout = setTimeout(function(e){
console.error('connection to relay server timed out')
console.log('start it with:')
console.log(' npm i ws')
console.log(' node relay.js')
},5000)
}
function sendOffer(){
pc.createOffer(gotOfferDescription);
}
function createConnection() {
var servers = null;
pc = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc');
createSendChannel()
pc.onicecandidate = iceCallback;
pc.ondatachannel = receiveChannelCallback;
pc.onopen = onConnectionStateChange;
pc.onclose = onConnectionStateChange;
}
function createSendChannel(){
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
channel = pc.createDataChannel('hello',
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function sendData() {
var data = document.getElementById("dataChannelSend").value;
channel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
if( !pc || pc.readyState != 'active' ) return;
trace('Closing data Channels');
channel.close();
trace('Closed data channel with label: ' + channel.label);
pc.close();
onConnectionStateChange()
pc = null;
trace('Closed peer connections');
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
// send a signal to close on the other end too
ws.send(JSON.stringify({type:'close'}))
// recreate the connection in case it wants to restart
createConnection()
}
function gotOfferDescription(desc) {
pc.setLocalDescription(desc);
trace('Offer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function gotAnswerDescription(desc) {
pc.setLocalDescription(desc);
trace('Answer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function iceCallback(event) {
trace('ice callback');
if (!pc.remoteDescription) {
trace('ICE candidate: too soon?')
return
}
if (event.candidate) {
pc.addIceCandidate(event.candidate);
trace('ICE candidate: \n' + event.candidate.candidate);
} else {
trace('ICE candidate: end of candidates')
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
channel = event.channel;
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message');
document.getElementById("dataChannelReceive").value = event.data;
}
function onChannelStateChange() {
var readyState = channel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onConnectionStateChange() {
var readyState = pc.readyState;
trace('Connection state is: ' + readyState);
if (readyState == "active") {
startButton.disabled = true;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
var readyState = channel.readyState;
trace('Receive channel state is: ' + readyState);
}
createSignals()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 5 (w. real signals & video)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 480px;
height: 640px;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
</style>
</head>
<body>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea><br>
<button id="startButton" onclick="getLocalVideo()">Add Video</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled></textarea><br>
</div>
<script>
var ws, pc, channel;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function createSignals(){
ws = new WebSocket('ws://nj.publicclass.co:8081/dc5');
ws.onopen = function(){
clearTimeout(ws.timeout)
trace('WS open')
createConnection()
ws.onmessage = function(m){
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
pc.createAnswer(gotAnswerDescription);
} else if( json.type == 'answer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
if( channel.readyState != 'open' )
sendOffer()
} else if( json.type == 'close' ){
closeDataChannels()
} else if( json.a && json.b ){
startButton.disabled = false;
} else {
trace('WS message: '+m.data)
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
closeDataChannels()
startButton.disabled = true;
}
}
ws.timeout = setTimeout(function(e){
console.error('connection to relay server timed out')
console.log('start it with:')
console.log(' npm i ws')
console.log(' node relay.js')
},5000)
}
function getLocalVideo(){
navigator.webkitGetUserMedia(
{video: true},
function(stream){
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc.addStream(stream)
sendOffer()
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function sendOffer(){
pc.createOffer(gotOfferDescription);
}
function createConnection() {
var servers = null;
pc = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc');
createSendChannel()
pc.onaddstream = attachVideo;
pc.onremovestream = detachVideo;
pc.onicecandidate = iceCallback;
pc.ondatachannel = receiveChannelCallback;
pc.onopen = onConnectionStateChange;
pc.onclose = onConnectionStateChange;
}
function createSendChannel(){
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
channel = pc.createDataChannel('hello',
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function sendData() {
var data = document.getElementById("dataChannelSend").value;
channel.send(data);
trace('Sent Data: ' + data);
}
function closeDataChannels() {
if( !pc || pc.readyState != 'active' ) return;
detachVideo()
trace('Closing data Channels');
channel.close();
trace('Closed data channel with label: ' + channel.label);
pc.close();
onConnectionStateChange()
pc = null;
trace('Closed peer connections');
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
// send a signal to close on the other end too
ws.send(JSON.stringify({type:'close'}))
// recreate the connection in case it wants to restart
createConnection()
}
function gotOfferDescription(desc) {
pc.setLocalDescription(desc);
trace('Offer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function gotAnswerDescription(desc) {
pc.setLocalDescription(desc);
trace('Answer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function iceCallback(event) {
trace('ice callback');
if (!pc.remoteDescription) {
trace('ICE candidate: too soon?')
return
}
if (event.candidate) {
pc.addIceCandidate(event.candidate);
trace('ICE candidate: \n' + event.candidate.candidate);
} else {
trace('ICE candidate: end of candidates')
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
channel = event.channel;
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message');
document.getElementById("dataChannelReceive").value = event.data;
}
function onChannelStateChange() {
var readyState = channel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onConnectionStateChange() {
var readyState = pc.readyState;
trace('Connection state is: ' + readyState);
if (readyState == "active") {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = false;
closeButton.disabled = false;
} else {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
var readyState = channel.readyState;
trace('Receive channel state is: ' + readyState);
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote and local video')
// first set to empty string to clear the video
document.getElementById('remote').src = '';
document.getElementById('local').src = '';
// then set to null for the button disabling to work
// (otherwise the src property defaults to the href)
document.getElementById('remote').src = null;
document.getElementById('local').src = null;
}
createSignals()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 6 (w. real signals & video)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 100%;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
video.attached { background: pink; }
</style>
</head>
<body>
<p>Open this link in another window (or share with someone). When that user is connected the "Add video"-button will be enabled. If your peer adds video it will show up to the right (above "Received Data"). When you do it yourself it will show up to the left.</p>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea>
<br>
<button id="startButton" onclick="getLocalVideo()">Add Video</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled></textarea><br>
</div>
<script>
var ws, pc, channel;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
var timeoutMS = 500;
function createSignals(){
trace('createSignals')
ws = new WebSocket('ws://nj.publicclass.co:8081/dc6-servers');
ws.onopen = function(){
timeoutMS *= 2;
clearTimeout(ws.timeout)
trace('WS open')
createConnection()
ws.onmessage = function(m){
timeoutMS = 500; // reset timeout when we get messages
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
pc.createAnswer(gotAnswerDescription);
} else if( json.type == 'answer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
// if( channel.readyState != 'open' )
// sendOffer()
} else if( json.type == 'close' ){
closeDataChannels()
} else if( json.candidate ){
if (!pc.remoteDescription) {
return trace('ICE candidate: too soon?')
} else {
var candidate = new RTCIceCandidate(json);
pc.addIceCandidate(candidate);
}
} else if( json.a && json.b ){
startButton.disabled = false;
} else {
trace('WS message: '+m.data)
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
closeDataChannels()
startButton.disabled = true;
setTimeout(function(){
createSignals();
},timeoutMS)
}
}
clearTimeout(ws.timeout)
ws.timeout = setTimeout(function(e){
console.error('connection to relay server timed out (will try again in 1s)')
console.log('')
console.log('start it with:')
console.log(' npm i ws')
console.log(' node relay.js')
console.log('')
// try again
clearTimeout(ws.timeout)
ws.timeout = setTimeout(createSignals,1000)
},5000)
}
function getLocalVideo(){
navigator.webkitGetUserMedia(
{video: true, audio: true},
function(stream){
document.getElementById('local').className += ' attached'
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc.addStream(stream)
sendOffer()
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function sendOffer(){
pc.createOffer(gotOfferDescription);
}
function createConnection() {
var host = 'ec2-176-34-161-202.eu-west-1.compute.amazonaws.com';
var servers = { "iceServers": [
{"url": 'stun:'+host+':3478'},
{"url": "turn:hello@"+host+':3478',"credentials":'xxx'},
// {"url": "stun:stun.l.google.com:19302"}
]}
var config = {optional: [{RtpDataChannels: true}]};
pc = new webkitRTCPeerConnection(servers,config);
trace('Created local peer connection object pc');
createSendChannel()
pc.onaddstream = attachVideo;
pc.onremovestream = detachVideo;
pc.onicecandidate = iceCallback;
pc.ondatachannel = receiveChannelCallback;
pc.onopen = onConnectionStateChange;
pc.onclose = onConnectionStateChange;
}
function createSendChannel(){
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
channel = pc.createDataChannel('hello',
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function sendData(){
var data = document.getElementById("dataChannelSend").value;
channel.send(data);
trace('Sent Data: '+data);
trace('Data Length: '+(data.length || data.byteLength));
}
function closeDataChannels() {
if( !pc || pc.readyState != 'active' ) return;
detachVideo()
trace('Closing data Channels');
channel.close();
trace('Closed data channel with label: ' + channel.label);
pc.close();
onConnectionStateChange()
pc = null;
trace('Closed peer connections');
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
// send a signal to close on the other end too
ws.send(JSON.stringify({type:'close'}))
// recreate the connection in case it wants to restart
createConnection()
}
function gotOfferDescription(desc) {
pc.setLocalDescription(desc);
trace('Offer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function gotAnswerDescription(desc) {
pc.setLocalDescription(desc);
trace('Answer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function iceCallback(event) {
trace('ice callback');
if (event.candidate) {
ws.send(JSON.stringify(event.candidate))
// trace('ICE candidate: \n' + event.candidate.candidate);
} else {
trace('ICE candidate: end of candidates')
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
channel = event.channel;
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message, length: '+event.data.length);
document.getElementById("dataChannelReceive").value = event.data;
}
function onChannelStateChange() {
var readyState = channel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onConnectionStateChange() {
var readyState = pc.readyState;
trace('Connection state is: ' + readyState);
if (readyState == "active") {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = false;
closeButton.disabled = false;
} else {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').className += ' attached'
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote and local video')
document.getElementById('remote').className = ''
document.getElementById('local').className = ''
// first set to empty string to clear the video
document.getElementById('remote').src = '';
document.getElementById('local').src = '';
// then set to null for the button disabling to work
// (otherwise the src property defaults to the href)
document.getElementById('remote').src = null;
document.getElementById('local').src = null;
}
createSignals()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 6 (w. real signals & video)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 100%;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
video.attached { background: pink; }
</style>
</head>
<body>
<p>Open this link in another window (or share with someone). When that user is connected the "Add video"-button will be enabled. If your peer adds video it will show up to the right (above "Received Data"). When you do it yourself it will show up to the left.</p>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea>
<br>
<button id="startButton" onclick="getLocalVideo()">Add Video</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled></textarea><br>
</div>
<script>
var ws, pc, channel;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
function createSignals(){
trace('createSignals')
ws = new WebSocket('ws://nj.publicclass.co:8081/dc6');
ws.onopen = function(){
clearTimeout(ws.timeout)
trace('WS open')
createConnection()
ws.onmessage = function(m){
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
pc.createAnswer(gotAnswerDescription);
} else if( json.type == 'answer' ){
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc);
if( channel.readyState != 'open' )
sendOffer()
} else if( json.type == 'close' ){
closeDataChannels()
} else if( json.candidate ){
if (!pc.remoteDescription) {
return trace('ICE candidate: too soon?')
} else {
var candidate = new RTCIceCandidate(json);
pc.addIceCandidate(candidate);
}
} else if( json.a && json.b ){
startButton.disabled = false;
} else {
trace('WS message: '+m.data)
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
closeDataChannels()
startButton.disabled = true;
setTimeout(function(){
createSignals();
},500)
}
}
clearTimeout(ws.timeout)
ws.timeout = setTimeout(function(e){
console.error('connection to relay server timed out (will try again in 1s)')
console.log('')
console.log('start it with:')
console.log(' npm i ws')
console.log(' node relay.js')
console.log('')
// try again
clearTimeout(ws.timeout)
ws.timeout = setTimeout(createSignals,1000)
},5000)
}
function getLocalVideo(){
navigator.webkitGetUserMedia(
{video: true},
function(stream){
document.getElementById('local').className += ' attached'
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc.addStream(stream)
sendOffer()
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function sendOffer(){
pc.createOffer(gotOfferDescription);
}
function createConnection() {
var servers = null;
pc = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc');
createSendChannel()
pc.onaddstream = attachVideo;
pc.onremovestream = detachVideo;
pc.onicecandidate = iceCallback;
pc.ondatachannel = receiveChannelCallback;
pc.onopen = onConnectionStateChange;
pc.onclose = onConnectionStateChange;
}
function createSendChannel(){
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
channel = pc.createDataChannel('hello',
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function sendData(){
var data = document.getElementById("dataChannelSend").value;
channel.send(data);
trace('Sent Data: '+data);
trace('Data Length: '+(data.length || data.byteLength));
}
function closeDataChannels() {
if( !pc || pc.readyState != 'active' ) return;
detachVideo()
trace('Closing data Channels');
channel.close();
trace('Closed data channel with label: ' + channel.label);
pc.close();
onConnectionStateChange()
pc = null;
trace('Closed peer connections');
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
// send a signal to close on the other end too
ws.send(JSON.stringify({type:'close'}))
// recreate the connection in case it wants to restart
createConnection()
}
function gotOfferDescription(desc) {
pc.setLocalDescription(desc);
trace('Offer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function gotAnswerDescription(desc) {
pc.setLocalDescription(desc);
trace('Answer from pc \n' + desc.sdp);
ws.send(JSON.stringify(desc))
}
function iceCallback(event) {
trace('ice callback');
if (event.candidate) {
ws.send(JSON.stringify(event.candidate))
// trace('ICE candidate: \n' + event.candidate.candidate);
} else {
trace('ICE candidate: end of candidates')
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
channel = event.channel;
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message, length: '+event.data.length);
document.getElementById("dataChannelReceive").value = event.data;
}
function onChannelStateChange() {
var readyState = channel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onConnectionStateChange() {
var readyState = pc.readyState;
trace('Connection state is: ' + readyState);
if (readyState == "active") {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = false;
closeButton.disabled = false;
} else {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').className += ' attached'
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote and local video')
document.getElementById('remote').className = ''
document.getElementById('local').className = ''
// first set to empty string to clear the video
document.getElementById('remote').src = '';
document.getElementById('local').src = '';
// then set to null for the button disabling to work
// (otherwise the src property defaults to the href)
document.getElementById('remote').src = null;
document.getElementById('local').src = null;
}
createSignals()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Data Channel Demo 6 (w. real signals & video)</title>
<style>
button {
font: 18px sans-serif;
padding: 8px;
}
textarea {
font-family: monospace;
margin: 2px;
width: 100%;
}
#left { position: absolute; left: 0; top: 0; width: 50%; }
#right { position: absolute; right: 0; top: 0; width: 50%; }
video.attached { background: pink; }
</style>
</head>
<body>
<p>Open this link in another window (or share with someone). When that user is connected the "Add video"-button will be enabled. If your peer adds video it will show up to the right (above "Received Data"). When you do it yourself it will show up to the left.</p>
<div id="left">
<video id='local' width='160' height='90' autoplay></video>
<br>
<h2>Send data</h2>
<textarea id="dataChannelSend" rows="5" cols="15" disabled></textarea>
<br>
<button id="startButton" onclick="getLocalVideo()">Add Video</button>
<button id="sendButton" onclick="sendData()">Send Data</button>
<button id="closeButton" onclick="closeDataChannels()">Stop Send Data</button>
<br>
</div>
<div id="right">
<video id='remote' width='160' height='90' autoplay></video>
<br>
<h2>Received Data</h2>
<textarea id="dataChannelReceive" rows="5" cols="15" disabled></textarea><br>
</div>
<script>
var ws, pc, channel;
startButton.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
var pendingOffer
, pendingAnswer;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] == '\n') {
text = text.substring(0, text.length - 1);
}
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}
var timeoutMS = 500;
function createSignals(){
trace('createSignals')
var room = '';
if( /[?&]r=([a-z0-9]+)/i.exec(location.href) )
room = RegExp.$1;
ws = new WebSocket('ws://nj.publicclass.co:8081/dc7-'+room);
ws.onopen = function(){
timeoutMS *= 2;
clearTimeout(ws.timeout)
trace('WS open')
createConnection()
ws.onmessage = function(m){
timeoutMS = 500; // reset timeout when we get messages
var json = JSON.parse(m.data);
if( json.type == 'offer' ){
trace('Received offer')
if( pendingOffer ){
console.log('mine',pendingOffer)
console.log('their',json)
if( pendingOffer.challenge > json.challenge ){
console.log('WINNER (trying again)')
ws.send(JSON.stringify(pendingOffer))
} else {
console.log('LOSER (restarting connection)')
closeDataChannels(true)
}
} else if( !pendingOffer ){
delete json.challenge;
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc,function(){
pc.createAnswer(gotAnswerDescription,errAnswerDescription);
},errOfferDescription);
}
pendingOffer = null;
} else if( json.type == 'answer' ){
trace('Received answer')
console.log('their',json)
delete json.challenge;
var desc = new RTCSessionDescription(json);
pc.setRemoteDescription(desc,function(){
if( channel.readyState != 'open' )
sendOffer()
},errAnswerDescription);
pendingOffer = null;
} else if( json.type == 'close' ){
closeDataChannels()
} else if( json.candidate ){
if (!pc.remoteDescription) {
return trace('ICE candidate: too soon?')
} else {
var candidate = new RTCIceCandidate(json);
pc.addIceCandidate(candidate);
}
} else if( json.a && json.b ){
startButton.disabled = false;
} else {
trace('WS message: '+m.data)
}
}
ws.onerror = function(e){
trace('WS error: '+e.message)
}
ws.onclose = function(){
trace('WS close')
closeDataChannels()
startButton.disabled = true;
setTimeout(function(){
createSignals();
},timeoutMS)
}
}
clearTimeout(ws.timeout)
ws.timeout = setTimeout(function(e){
console.error('connection to relay server timed out (will try again in 1s)')
console.log('')
console.log('start it with:')
console.log(' npm i ws')
console.log(' node relay.js')
console.log('')
// try again
clearTimeout(ws.timeout)
ws.timeout = setTimeout(createSignals,1000)
},5000)
}
function getLocalVideo(){
navigator.webkitGetUserMedia(
{video: true},
function(stream){
document.getElementById('local').className += ' attached'
document.getElementById('local').src = webkitURL.createObjectURL(stream);
pc.addStream(stream)
sendOffer()
},
function(){console.error('user media denied or non-existant',arguments)}
)
}
function sendOffer(){
pc.createOffer(gotOfferDescription,errOfferDescription);
}
function createConnection() {
var servers = null;
pc = new webkitRTCPeerConnection(servers,
{optional: [{RtpDataChannels: true}]});
trace('Created local peer connection object pc');
pendingOffer = null;
pendingAnswer = null;
createSendChannel()
pc.onaddstream = attachVideo;
pc.onremovestream = detachVideo;
pc.onicecandidate = iceCallback;
pc.ondatachannel = receiveChannelCallback;
pc.onopen = onConnectionStateChange;
pc.onclose = onConnectionStateChange;
}
function createSendChannel(){
try {
// Reliable Data Channels not yet supported in Chrome
// Data Channel api supported from Chrome M25.
// You need to start chrome with --enable-data-channels flag.
channel = pc.createDataChannel('hello',
{reliable: false});
trace('Created send data channel');
} catch (e) {
alert('Failed to create data channel. ' +
'You need Chrome M25 or later with --enable-data-channels flag');
trace('Create Data channel failed with exception: ' + e.message);
}
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function sendData(){
var data = document.getElementById("dataChannelSend").value;
channel.send(data);
trace('Sent Data: '+data);
trace('Data Length: '+(data.length || data.byteLength));
}
function closeDataChannels(force) {
if( !force && (!pc || pc.readyState != 'active') ) return;
detachVideo()
trace('Closing data Channels');
channel.close();
trace('Closed data channel with label: ' + channel.label);
pc.close();
onConnectionStateChange()
pc = null;
trace('Closed peer connections');
document.getElementById("dataChannelSend").value = "";
document.getElementById("dataChannelReceive").value = "";
document.getElementById("dataChannelSend").disabled = true;
// send a signal to close on the other end too
ws.send(JSON.stringify({type:'close'}))
// recreate the connection in case it wants to restart
createConnection()
}
function gotOfferDescription(desc) {
pc.setLocalDescription(desc);
trace('Offer from pc \n' + desc.sdp);
desc.challenge = Math.random();
pendingOffer = desc;
ws.send(JSON.stringify(desc))
}
function gotAnswerDescription(desc) {
pc.setLocalDescription(desc);
trace('Answer from pc \n' + desc.sdp);
desc.challenge = Math.random();
pendingAnswer = desc;
ws.send(JSON.stringify(desc))
}
function iceCallback(event) {
trace('ice callback');
if (event.candidate) {
ws.send(JSON.stringify(event.candidate))
// trace('ICE candidate: \n' + event.candidate.candidate);
} else {
trace('ICE candidate: end of candidates')
}
}
function receiveChannelCallback(event) {
trace('Receive Channel Callback');
channel = event.channel;
channel.onmessage = onReceiveMessageCallback;
channel.onopen = onChannelStateChange;
channel.onclose = onChannelStateChange;
}
function onReceiveMessageCallback(event) {
trace('Received Message, length: '+event.data.length);
document.getElementById("dataChannelReceive").value = event.data;
}
function onChannelStateChange() {
var readyState = channel.readyState;
trace('Send channel state is: ' + readyState);
if (readyState == "open") {
document.getElementById("dataChannelSend").disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
} else {
document.getElementById("dataChannelSend").disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onConnectionStateChange() {
var readyState = pc.readyState;
trace('Connection state is: ' + readyState);
if (readyState == "active") {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = false;
closeButton.disabled = false;
} else {
startButton.disabled = document.getElementById('local').src != '';
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function attachVideo(e){
trace('attaching remote video')
document.getElementById('remote').className += ' attached'
document.getElementById('remote').src = webkitURL.createObjectURL(e.stream);
}
function detachVideo(){
trace('detaching remote and local video')
document.getElementById('remote').className = ''
document.getElementById('local').className = ''
// first set to empty string to clear the video
document.getElementById('remote').src = '';
document.getElementById('local').src = '';
// then set to null for the button disabling to work
// (otherwise the src property defaults to the href)
document.getElementById('remote').src = null;
document.getElementById('local').src = null;
}
function errOfferDescription(err){
console.error('offer',err)
}
function errAnswerDescription(err){
console.error('answer',err)
}
createSignals()
</script>
</body>
</html>
A list of RTC Data Channel tests:
<ol>
<li><a href="dc1.html">A simple local (in one window) example</a></li>
<li><a href="dc2-video.html">A local example with video</a></li>
<li><a href="dc3-echo-signals.html">A local example signalling through echo server</a></li>
<li><a href="dc4-real-signals.html">An real (in two windows) example signalling through relay server</a></li>
<li><a href="dc5-real-signals-video.html">An real (in two windows) example signalling through relay server with video</a></li>
<li><a href="dc6.html">Same as 5 but with ice candidates messages and reconnecting websocket</a></li>
<li><a href="dc7-pending.html">Same as 6 but with a workaround for the "offer racecondition". It will send a challenge with the description and if it has a pending "offer" it will check the challenge and the loser will have to recreate the connection.</a></li>
<li><a href="dc6-servers.html">Same as 6 but with a turn/stun server configured (works best)</a></li>
</ol>
// the server code for relaying signal messages between 2 peers
// in the same room.
//
// to use:
// npm i ws
// node relay.js
var WebSocketServer = require('ws').Server
, parse = require('url').parse
, wss = new WebSocketServer({port: 8081});
var rooms = {};
wss.on('connection',function(ws){
var url = parse(ws.upgradeReq.url)
, name = url.pathname.slice(1)
, room = rooms[name] || (rooms[name] = {a:null,b:null});
if( !room.a ){
console.log('connected a@%s', name)
room.a = ws;
} else if( !room.b ) {
console.log('connected b@%s', name)
room.b = ws;
} else {
console.log('closing c@%s (room full)', name)
return ws.close();
}
// relay messages
ws.on('message',function(message){
var json = JSON.parse(message) || {}
console.log('relaying %s message %s -> %s (%d bytes)',
json.candidate ? 'candidate' : json.type,
room.a === ws ? 'a' : 'b',
room.a === ws ? 'b' : 'a',
message.length
)
if( room.a === ws && room.b ) room.b.send(message);
if( room.b === ws && room.a ) room.a.send(message);
})
ws.on('close',function(){
console.log('closing %s@%s', room.a === ws ? 'a' : 'b', name)
if( room.a === ws ){
room.a = null;
if( room.b ){
console.log('sending close to b@%s',name)
room.b.send(JSON.stringify({type:'close'}));
}
}
if( room.b === ws ){
room.b = null;
if( room.a ){
console.log('sending close to a@%s',name)
room.a.send(JSON.stringify({type:'close'}));
}
}
if( !room.a && !room.b ){
delete rooms[name];
}
})
room.a && room.a.send(JSON.stringify({a:!!room.a,b:!!room.b}));
room.b && room.b.send(JSON.stringify({a:!!room.a,b:!!room.b}));
})
console.log('listening on port 8081')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment