Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@mganeko
Created December 5, 2014 08:04
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 mganeko/0662e3c5f01247c81b88 to your computer and use it in GitHub Desktop.
Save mganeko/0662e3c5f01247c81b88 to your computer and use it in GitHub Desktop.
WebRTC signaling over milkcocoa for multiple room / people
<!DOCTYPE html>
<html>
<head>
<title>WebRTC over milkcocoa</title>
</head>
<body>
<button type="button" onclick="startVideo();">Start video</button>
<button type="button" onclick="stopVideo();">Stop video</button>
<button type="button" onclick="call();">Connect</button>
<button type="button" onclick="hangUp();">Hang Up</button>
<br />
<div style="position: relative;">
<video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video>
<!-- <video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video> -->
<video id="webrtc-remote-video-0" autoplay style="position: absolute; top: 250px; left: 0px; width: 320px; height: 240px; border: 1px solid black; "></video>
<video id="webrtc-remote-video-1" autoplay style="position: absolute; top: 250px; left: 330px; width: 320px; height: 240px; border: 1px solid black; "></video>
<video id="webrtc-remote-video-2" autoplay style="position: absolute; top: 0px; left: 330px; width: 320px; height: 240px; border: 1px solid black; " ></video>
</div>
<script src="http://cdn.mlkcca.com/v0.2.2/milkcocoa.js"></script>
<script>
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL;
var localVideo = document.getElementById('local-video');
var localStream = null;
var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};
// ---- multi people video & audio ----
var videoElementsInUse = {};
var videoElementsStandBy = {};
pushVideoStandBy(getVideoForRemote(0));
pushVideoStandBy(getVideoForRemote(1));
pushVideoStandBy(getVideoForRemote(2));
function getVideoForRemote(index) {
var elementID = 'webrtc-remote-video-' + index;
var element = document.getElementById(elementID);
return element;
}
// ---- video element management ---
function pushVideoStandBy(element) {
videoElementsStandBy[element.id] = element;
}
function popVideoStandBy() {
var element = null;
for (var id in videoElementsStandBy) {
element = videoElementsStandBy[id];
delete videoElementsStandBy[id];
return element;
}
return null;
}
function pushVideoInUse(id, element) {
videoElementsInUse[id] = element;
}
function popVideoInUse(id) {
element = videoElementsInUse[id];
delete videoElementsInUse[id];
return element;
}
function attachVideo(id, stream) {
console.log('try to attach video. id=' + id);
var videoElement = popVideoStandBy();
if (videoElement) {
videoElement.src = window.URL.createObjectURL(stream);
console.log("videoElement.src=" + videoElement.src);
pushVideoInUse(id, videoElement);
videoElement.style.display = 'block';
}
else {
console.error('--- no video element stand by.');
}
}
function detachVideo(id) {
console.log('try to detach video. id=' + id);
var videoElement = popVideoInUse(id);
if (videoElement) {
videoElement.pause();
videoElement.src = "";
console.log("videoElement.src=" + videoElement.src);
pushVideoStandBy(videoElement);
}
else {
console.warn('warning --- no video element using with id=' + id);
}
}
function detachAllVideo() {
var element = null;
for (var id in videoElementsInUse) {
detachVideo(id);
}
}
function getFirstVideoInUse() {
var element = null;
for (var id in videoElementsInUse) {
element = videoElementsInUse[id];
return element;
}
return null;
}
function getVideoCountInUse() {
var count = 0;
for (var id in videoElementsInUse) {
count++;
}
return count;
}
function isLocalStreamStarted() {
if (localStream) {
return true;
}
else {
return false;
}
}
// -------------- multi connections --------------------
var MAX_CONNECTION_COUNT = 3;
var connections = {}; // Connection hash
function Connection() { // Connection Class
var self = this;
var id = ""; // milkcocoa.id of partner
var peerconnection = null; // RTCPeerConnection instance
}
function getConnection(id) {
var con = null;
con = connections[id];
return con;
}
function addConnection(id, connection) {
connections[id] = connection;
}
function getConnectionCount() {
var count = 0;
for (var id in connections) {
count++;
}
console.log('getConnectionCount=' + count);
return count;
}
function isConnectPossible() {
if (getConnectionCount() < MAX_CONNECTION_COUNT)
return true;
else
return false;
}
function getConnectionIndex(id_to_lookup) {
var index = 0;
for (var id in connections) {
if (id == id_to_lookup) {
return index;
}
index++;
}
// not found
return -1;
}
function deleteConnection(id) {
delete connections[id];
}
function stopAllConnections() {
for (var id in connections) {
var conn = connections[id];
conn.peerconnection.close();
conn.peerconnection = null;
delete connections[id];
}
}
function stopConnection(id) {
var conn = connections[id];
if(conn) {
console.log('stop and delete connection with id=' + id);
conn.peerconnection.close();
conn.peerconnection = null;
delete connections[id];
}
else {
console.log('try to stop connection, but not found id=' + id);
}
}
function isPeerStarted() {
if (getConnectionCount() > 0) {
return true;
}
else {
return false;
}
}
// ----- milkcocoa ---
var selfID = '';
var room = getRoomName();
var milkcocoa = new MilkCocoa("https://your-app-id.mlkcca.com/"); /* your-app-id must be io-xxxxxxx */
var ds = milkcocoa.dataStore("signaling/" + room);
var socketReady = false;
milkcocoa.anonymous(function(err, user) {
if (user) {
selfID = user.id;
console.log('anonymous() id=' + selfID);
socketReady = true;
}
else {
console.error('milk anonymous() error');
}
});
// watch realtime message
ds.on("push", function(data) {
onMessage(data.value);
});
// socket: accept connection request
function onMessage(evt) {
var id = evt.from;
var target = evt.sendto;
var conn = getConnection(id);
if (id === selfID) {
// skip message from myself
console.log('.... skip self message ...' + id);
return;
}
if( (target) && (target != '') && (target != selfID) ) {
// skip message to someone else
console.log('.... skip others message ...' + target);
return;
}
if (evt.type === 'call') {
console.log('--- got call ---');
console.log(evt);
if (! isLocalStreamStarted()) {
return;
}
if (conn) {
return; // already connected
}
if (isConnectPossible()) {
ds.push({type: "response", sendto: id, from: selfID});
}
else {
console.warn('max connections. so ignore call');
}
return;
}
else if (evt.type === 'response') {
console.log('--- got response ---');
console.log(evt);
sendOffer(id);
return;
} else if (evt.type === 'offer') {
console.log("Received offer, set offer, sending answer....")
onOffer(evt);
} else if (evt.type === 'answer' && isPeerStarted()) { // **
console.log('Received answer, settinng answer SDP');
onAnswer(evt);
} else if (evt.type === 'candidate' && isPeerStarted()) { // **
console.log('Received ICE candidate...');
onCandidate(evt);
} else if (evt.type === 'user dissconnected' && isPeerStarted()) { // **
console.log("disconnected");
detachVideo(id); // force detach video
stopConnection(id);
}
else if ( (evt.type === 'bye') && isPeerStarted() ) {
console.log('Received bye...');
detachVideo(id); // force detach video
stopConnection(id);
}
}
function getRoomName() { // signaling_milkcocoa_multi.html?roomname
var url = document.location.href;
var args = url.split('?');
if (args.length > 1) {
var room = args[1];
if (room != "") {
return room;
}
}
return "_defaultroom";
}
function onOffer(evt) {
console.log("Received offer...")
console.log(evt);
setOffer(evt);
sendAnswer(evt);
}
function onAnswer(evt) {
console.log("Received Answer...")
console.log(evt);
setAnswer(evt);
}
function onCandidate(evt) {
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
console.error('peerConnection not exist!');
return;
}
var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
console.log('Received Candidate... from=' + evt.from + ' to=' + evt.sendto)
console.log(candidate);
conn.peerconnection.addIceCandidate(candidate);
}
function sendSDP(sdp) {
//var text = JSON.stringify(sdp);
//console.log("---sending sdp text ---");
//console.log(text);
ds.push(sdp);
}
function sendCandidate(candidate) {
//var text = JSON.stringify(candidate);
//console.log("---sending candidate text ---");
//console.log(text);
ds.push(candidate);
}
// ---------------------- video handling -----------------------
// start local video
function startVideo() {
navigator.getUserMedia({video: true, audio: true},
function (stream) { // success
localStream = stream;
localVideo.src = window.URL.createObjectURL(stream);
localVideo.play();
localVideo.volume = 0;
},
function (error) { // error
console.error('An error occurred:');
console.error(error);
return;
}
);
}
// stop local video
function stopVideo() {
localVideo.src = "";
localStream.stop();
}
// ---------------------- connection handling -----------------------
function prepareNewConnection(id) {
var pc_config = {"iceServers":[]};
var peer = null;
try {
peer = new webkitRTCPeerConnection(pc_config);
} catch (e) {
console.log("Failed to create PeerConnection, exception: " + e.message);
}
var conn = new Connection();
conn.id = id;
conn.peerconnection = peer;
peer.id = id;
addConnection(id, conn);
// send any ice candidates to the other peer
peer.onicecandidate = function (evt) {
if (evt.candidate) {
console.log(evt.candidate);
sendCandidate({type: "candidate",
sendto: conn.id, from: selfID,
sdpMLineIndex: evt.candidate.sdpMLineIndex,
sdpMid: evt.candidate.sdpMid,
candidate: evt.candidate.candidate});
} else {
console.log("empty event --- phase=" + evt.eventPhase);
}
};
peer.oniceconnectionstatechange = function() {
console.log('ice connection status=' + peer.iceConnectionState + ' gahter=' + peer.iceGatheringState);
if ('completed' === peer.iceConnectionState) {
console.log("candidate complete");
}
};
peer.onsignalingstatechange = function() {
console.log('signaling status=' + peer.signalingState);
};
console.log('Adding local stream...');
peer.addStream(localStream);
peer.addEventListener("addstream", onRemoteStreamAdded, false);
peer.addEventListener("removestream", onRemoteStreamRemoved, false)
// when remote adds a stream, hand it on to the local video element
function onRemoteStreamAdded(event) {
console.log("Added remote stream");
attachVideo(this.id, event.stream);
}
// when remote removes a stream, remove it from the local video element
function onRemoteStreamRemoved(event) {
console.log("Remove remote stream");
detachVideo(this.id);
}
return conn;
}
function sendOffer(id) {
console.log('--- sending offer to:' + id);
var conn = getConnection(id);
if (!conn) {
conn = prepareNewConnection(id);
}
conn.peerconnection.createOffer(function (sessionDescription) { // in case of success
conn.peerconnection.setLocalDescription(sessionDescription);
sessionDescription.sendto = id;
sessionDescription.from = selfID;
sendSDP(sessionDescription);
}, function () { // in case of error
console.log("Create Offer failed");
}, mediaConstraints);
}
function setOffer(evt) {
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
conn = prepareNewConnection(id);
conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
}
else {
console.error('peerConnection alreay exist!');
}
}
function sendAnswer(evt) {
console.log('-- sending Answer. Creating remote session description... to:' + evt.from);
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
console.error('peerConnection not exist!');
return
}
conn.peerconnection.createAnswer(function (sessionDescription) {
// in case of success
conn.peerconnection.setLocalDescription(sessionDescription);
sessionDescription.sendto = id;
sessionDescription.from = selfID;
sendSDP(sessionDescription);
}, function () { // in case of error
console.log("Create Answer failed");
}, mediaConstraints);
}
function setAnswer(evt) {
var id = evt.from;
var conn = getConnection(id);
if (! conn) {
console.error('peerConnection not exist!');
return
}
conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
}
// call others before connecting peer
function call() {
if (! isLocalStreamStarted()) {
alert("Local stream not running yet. Please [Start Video] or [Start Screen].");
return;
}
if (! socketReady) {
alert("Socket is not connected to server. Please reload and try again.");
return;
}
// call others, in same room
console.log("---call others in same room, befeore offer--");
ds.push({type: "call", from: selfID});
console.log({type: "call", from: selfID});
}
// stop the connection upon user request
function hangUp() {
console.log("Hang up.");
ds.push({type: "bye", from: selfID});
detachAllVideo();
stopAllConnections();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment