Skip to content

Instantly share code, notes, and snippets.

@mbildner
Created November 14, 2013 17:05
Show Gist options
  • Save mbildner/7470424 to your computer and use it in GitHub Desktop.
Save mbildner/7470424 to your computer and use it in GitHub Desktop.
<html>
<head>
<title></title>
</head>
<body>
<button id="startButton">Start</button>
<video id="remoteView" height="300" width="300" autoplay></video>
<video id="selfView" height="300" width="300" autoplay></video>
<script>
var RTCPeerConnection = null;
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;
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);
}
if (navigator.mozGetUserMedia) {
console.log("This appears to be Firefox");
webrtcDetectedBrowser = "firefox";
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]);
// The RTCPeerConnection object.
RTCPeerConnection = mozRTCPeerConnection;
// The RTCSessionDescription object.
RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
RTCIceCandidate = mozRTCIceCandidate;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
// Creates iceServer from the url for FF.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0 &&
(url.indexOf('transport=udp') !== -1 ||
url.indexOf('?transport') === -1)) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url.
var turn_url_parts = url.split("?");
iceServer = { 'url': turn_url_parts[0],
'credential': password,
'username': username };
}
return iceServer;
};
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
console.log("Attaching media stream");
element.mozSrcObject = stream;
element.play();
};
reattachMediaStream = function(to, from) {
console.log("Reattaching media stream");
to.mozSrcObject = from.mozSrcObject;
to.play();
};
// Fake get{Video,Audio}Tracks
MediaStream.prototype.getVideoTracks = function() {
return [];
};
MediaStream.prototype.getAudioTracks = function() {
return [];
};
} else if (navigator.webkitGetUserMedia) {
console.log("This appears to be Chrome");
webrtcDetectedBrowser = "chrome";
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]);
// Creates iceServer from the url for Chrome.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 28) {
// For pre-M28 chrome versions use old TURN format.
var url_turn_parts = url.split("turn:");
iceServer = { 'url': 'turn:' + username + '@' + url_turn_parts[1],
'credential': password };
} else {
// For Chrome M28 & above use new TURN format.
iceServer = { 'url': url,
'credential': password,
'username': username };
}
}
return iceServer;
};
// The RTCPeerConnection object.
RTCPeerConnection = webkitRTCPeerConnection;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
}
};
reattachMediaStream = function(to, from) {
to.src = from.src;
};
// The representation of tracks in a stream is changed in M26.
// Unify them for earlier Chrome versions in the coexisting period.
if (!webkitMediaStream.prototype.getVideoTracks) {
webkitMediaStream.prototype.getVideoTracks = function() {
return this.videoTracks;
};
webkitMediaStream.prototype.getAudioTracks = function() {
return this.audioTracks;
};
}
// New syntax of getXXXStreams method in M26.
if (!webkitRTCPeerConnection.prototype.getLocalStreams) {
webkitRTCPeerConnection.prototype.getLocalStreams = function() {
return this.localStreams;
};
webkitRTCPeerConnection.prototype.getRemoteStreams = function() {
return this.remoteStreams;
};
}
} else {
console.log("Browser does not appear to be WebRTC-capable");
}
</script>
<script>
var button = document.getElementById('startButton');
button.disabled = true;
button.addEventListener('click', function (click) {
start();
this.disabled = true;
});
var remoteView = document.getElementById('remoteView');
var selfView = document.getElementById('selfView');
var createWebSocket = function (endpoint) {
// var wsEndpoint = "/signalsocket";
var wsEndpoint = endpoint;
var origin = document.location.origin;
var wsUrl = "ws" + origin.substring(4) + wsEndpoint;
var ws = new WebSocket(wsUrl);
ws.onopen = function () {
button.disabled = false;
}
return ws;
}
function SignalingChannel () {
return createWebSocket('/signalsocket');
}
var googleStunServer = {
'url': "stun:stun.l.google.com:19302"
}
// we think this is the mozilla stun server
var mozStunServer = {
'url': "stun:23.21.150.121"
}
var stunservers = [
googleStunServer,
mozStunServer
];
var signalingChannel = SignalingChannel();
var configuration = { "iceServers": stunservers, optional: [{RtpDataChannels: true}] };
var icecandidates = [];
var pc;
// call start() to initiate
function start() {
pc = new RTCPeerConnection(configuration);
// sendChannel is quite broken, let's figure out how to fix it and be on our way
// sendChannel = pc.createDataChannel("sendDataChannel", {reliable: false});
// sendChannel.onopen = function () {alert("sendchannel open");}
// send any ice candidates to the other peer
pc.onicecandidate = function (evt) {
icecandidates.push(evt);
console.log("onicecandidate fired");
// console.log(pc.iceGatheringState);
try {
console.log(evt.candidate.candidate.toString());
} catch (e) {
console.log("candidate log errored: ", evt);
}
if (evt.candidate)
signalingChannel.send(JSON.stringify({ "candidate": evt.candidate }));
};
// let the "negotiationneeded" event trigger offer generation
pc.onnegotiationneeded = function () {
console.log("onnegotiationneeded fired");
pc.createOffer(localDescCreated, logError);
}
// once remote stream arrives, show it in the remote video element
pc.onaddstream = function (evt) {
console.log("onaddstream fired");
remoteView.src = URL.createObjectURL(evt.stream);
};
// get a local stream, show it in a self-view and add it to be sent
navigator.webkitGetUserMedia({ "audio": true, "video": true }, function (stream) {
console.log("get user media fired");
selfView.src = URL.createObjectURL(stream);
pc.addStream(stream);
}, logError);
}
function localDescCreated(desc) {
pc.setLocalDescription(desc, function () {
signalingChannel.send(JSON.stringify({ "sdp": pc.localDescription }));
}, logError);
}
signalingChannel.onmessage = function (evt) {
if (!pc)
start();
var message = JSON.parse(evt.data);
console.log(message);
if (message.sdp){
pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
// if we received an offer, we need to answer
if (pc.remoteDescription.type == "offer")
console.log("offer received");
pc.createAnswer(localDescCreated, logError);
}, logError);
} else {
console.log("add ice candidate firing");
pc.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
function logError(error) {
console.log(error.name + ": " + error.message);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment