2014-02-06 16:44:24 +00:00
|
|
|
// Muaz Khan - https://github.com/muaz-khan
|
|
|
|
// MIT License - https://www.webrtc-experiment.com/licence/
|
|
|
|
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/websocket
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
|
|
window.PeerConnection = function (socketURL, userid) {
|
|
|
|
this.userid = userid || getToken();
|
|
|
|
this.peers = {};
|
|
|
|
|
|
|
|
if (!socketURL) throw 'Socket-URL is mandatory.';
|
|
|
|
|
|
|
|
new Signaler(this, socketURL);
|
2014-02-09 02:06:26 +00:00
|
|
|
|
|
|
|
this.addStream = function(stream) {
|
|
|
|
this.MediaStream = stream;
|
|
|
|
};
|
2014-02-06 16:44:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function Signaler(root, socketURL) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
root.startBroadcasting = function () {
|
2014-02-09 02:06:26 +00:00
|
|
|
if(!root.MediaStream) throw 'Offerer must have media stream.';
|
|
|
|
|
2014-02-06 16:44:24 +00:00
|
|
|
(function transmit() {
|
|
|
|
socket.send({
|
|
|
|
userid: root.userid,
|
|
|
|
broadcasting: true
|
|
|
|
});
|
|
|
|
!self.participantFound &&
|
|
|
|
!self.stopBroadcasting &&
|
|
|
|
setTimeout(transmit, 3000);
|
|
|
|
})();
|
|
|
|
};
|
|
|
|
|
|
|
|
root.sendParticipationRequest = function (userid) {
|
|
|
|
socket.send({
|
|
|
|
participationRequest: true,
|
|
|
|
userid: root.userid,
|
|
|
|
to: userid
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// if someone shared SDP
|
|
|
|
this.onsdp = function (message) {
|
|
|
|
var sdp = message.sdp;
|
|
|
|
|
|
|
|
if (sdp.type == 'offer') {
|
|
|
|
root.peers[message.userid] = Answer.createAnswer(merge(options, {
|
|
|
|
MediaStream: root.MediaStream,
|
|
|
|
sdp: sdp
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sdp.type == 'answer') {
|
|
|
|
root.peers[message.userid].setRemoteDescription(sdp);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
root.acceptRequest = function (userid) {
|
|
|
|
root.peers[userid] = Offer.createOffer(merge(options, {
|
|
|
|
MediaStream: root.MediaStream
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
var candidates = [];
|
|
|
|
// if someone shared ICE
|
|
|
|
this.onice = function (message) {
|
|
|
|
var peer = root.peers[message.userid];
|
|
|
|
if (peer) {
|
|
|
|
peer.addIceCandidate(message.candidate);
|
|
|
|
for (var i = 0; i < candidates.length; i++) {
|
|
|
|
peer.addIceCandidate(candidates[i]);
|
|
|
|
}
|
|
|
|
candidates = [];
|
|
|
|
} else candidates.push(candidates);
|
|
|
|
};
|
|
|
|
|
|
|
|
// it is passed over Offer/Answer objects for reusability
|
|
|
|
var options = {
|
|
|
|
onsdp: function (sdp) {
|
|
|
|
socket.send({
|
|
|
|
userid: root.userid,
|
|
|
|
sdp: sdp,
|
|
|
|
to: root.participant
|
|
|
|
});
|
|
|
|
},
|
|
|
|
onicecandidate: function (candidate) {
|
|
|
|
socket.send({
|
|
|
|
userid: root.userid,
|
|
|
|
candidate: candidate,
|
|
|
|
to: root.participant
|
|
|
|
});
|
|
|
|
},
|
|
|
|
onStreamAdded: function (stream) {
|
|
|
|
console.debug('onStreamAdded', '>>>>>>', stream);
|
|
|
|
|
|
|
|
stream.onended = function () {
|
|
|
|
if (root.onStreamEnded) root.onStreamEnded(streamObject);
|
|
|
|
};
|
|
|
|
|
|
|
|
var mediaElement = document.createElement('video');
|
|
|
|
mediaElement.id = root.participant;
|
|
|
|
mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.webkitURL.createObjectURL(stream);
|
|
|
|
mediaElement.autoplay = true;
|
|
|
|
mediaElement.controls = true;
|
|
|
|
mediaElement.play();
|
|
|
|
|
|
|
|
var streamObject = {
|
|
|
|
mediaElement: mediaElement,
|
|
|
|
stream: stream,
|
|
|
|
userid: root.participant,
|
|
|
|
type: 'remote'
|
|
|
|
};
|
|
|
|
|
|
|
|
function afterRemoteStreamStartedFlowing() {
|
|
|
|
if (!root.onStreamAdded) return;
|
|
|
|
root.onStreamAdded(streamObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
afterRemoteStreamStartedFlowing();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function closePeerConnections() {
|
|
|
|
self.stopBroadcasting = true;
|
|
|
|
if (root.MediaStream) root.MediaStream.stop();
|
|
|
|
|
|
|
|
for (var userid in root.peers) {
|
|
|
|
root.peers[userid].peer.close();
|
|
|
|
}
|
|
|
|
root.peers = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
root.close = function () {
|
|
|
|
socket.send({
|
|
|
|
userLeft: true,
|
|
|
|
userid: root.userid,
|
|
|
|
to: root.participant
|
|
|
|
});
|
|
|
|
closePeerConnections();
|
|
|
|
};
|
|
|
|
|
|
|
|
window.onbeforeunload = function () {
|
|
|
|
root.close();
|
|
|
|
};
|
|
|
|
|
|
|
|
window.onkeyup = function (e) {
|
|
|
|
if (e.keyCode == 116)
|
|
|
|
root.close();
|
|
|
|
};
|
2014-02-09 02:06:26 +00:00
|
|
|
|
|
|
|
function onmessage(e) {
|
|
|
|
var message = JSON.parse(e.data);
|
2014-02-06 16:44:24 +00:00
|
|
|
|
|
|
|
if (message.userid == root.userid) return;
|
|
|
|
root.participant = message.userid;
|
|
|
|
|
|
|
|
// for pretty logging
|
|
|
|
console.debug(JSON.stringify(message, function (key, value) {
|
|
|
|
if (value && value.sdp) {
|
|
|
|
console.log(value.sdp.type, '---', value.sdp.sdp);
|
|
|
|
return '';
|
|
|
|
} else return value;
|
|
|
|
}, '---'));
|
|
|
|
|
|
|
|
// if someone shared SDP
|
|
|
|
if (message.sdp && message.to == root.userid) {
|
|
|
|
self.onsdp(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if someone shared ICE
|
|
|
|
if (message.candidate && message.to == root.userid) {
|
|
|
|
self.onice(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if someone sent participation request
|
|
|
|
if (message.participationRequest && message.to == root.userid) {
|
|
|
|
self.participantFound = true;
|
|
|
|
|
|
|
|
if (root.onParticipationRequest) {
|
|
|
|
root.onParticipationRequest(message.userid);
|
|
|
|
} else root.acceptRequest(message.userid);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if someone is broadcasting himself!
|
|
|
|
if (message.broadcasting && root.onUserFound) {
|
|
|
|
root.onUserFound(message.userid);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.userLeft && message.to == root.userid) {
|
|
|
|
closePeerConnections();
|
|
|
|
}
|
2014-02-09 02:06:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var socket = socketURL;
|
|
|
|
if(typeof socketURL == 'string') {
|
|
|
|
socket = new WebSocket(socketURL);
|
|
|
|
socket.push = socket.send;
|
|
|
|
socket.send = function (data) {
|
|
|
|
socket.push(JSON.stringify(data));
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.onopen = function () {
|
|
|
|
console.log('websocket connection opened.');
|
|
|
|
};
|
|
|
|
}
|
|
|
|
socket.onmessage = onmessage;
|
2014-02-06 16:44:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
|
|
|
var RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
|
|
|
|
var RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
|
|
|
|
|
|
|
|
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
|
|
|
|
window.URL = window.webkitURL || window.URL;
|
|
|
|
|
|
|
|
var isFirefox = !!navigator.mozGetUserMedia;
|
|
|
|
var isChrome = !!navigator.webkitGetUserMedia;
|
|
|
|
|
|
|
|
var STUN = {
|
|
|
|
url: isChrome ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121'
|
|
|
|
};
|
|
|
|
|
|
|
|
var TURN = {
|
|
|
|
url: 'turn:homeo@turn.bistri.com:80',
|
|
|
|
credential: 'homeo'
|
|
|
|
};
|
|
|
|
|
|
|
|
var iceServers = {
|
|
|
|
iceServers: [STUN]
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isChrome) {
|
|
|
|
if (parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 28)
|
|
|
|
TURN = {
|
|
|
|
url: 'turn:turn.bistri.com:80',
|
|
|
|
credential: 'homeo',
|
|
|
|
username: 'homeo'
|
|
|
|
};
|
|
|
|
|
|
|
|
iceServers.iceServers = [STUN, TURN];
|
|
|
|
}
|
|
|
|
|
|
|
|
var optionalArgument = {
|
|
|
|
optional: [{
|
|
|
|
DtlsSrtpKeyAgreement: true
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
var offerAnswerConstraints = {
|
|
|
|
optional: [],
|
|
|
|
mandatory: {
|
|
|
|
OfferToReceiveAudio: true,
|
|
|
|
OfferToReceiveVideo: true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function getToken() {
|
|
|
|
return Math.round(Math.random() * 9999999999) + 9999999999;
|
|
|
|
}
|
2014-02-09 02:06:26 +00:00
|
|
|
|
|
|
|
function onSdpError() {}
|
2014-02-06 16:44:24 +00:00
|
|
|
|
|
|
|
// var offer = Offer.createOffer(config);
|
|
|
|
// offer.setRemoteDescription(sdp);
|
|
|
|
// offer.addIceCandidate(candidate);
|
|
|
|
var Offer = {
|
|
|
|
createOffer: function (config) {
|
|
|
|
var peer = new RTCPeerConnection(iceServers, optionalArgument);
|
|
|
|
|
|
|
|
if (config.MediaStream) peer.addStream(config.MediaStream);
|
|
|
|
peer.onaddstream = function (event) {
|
|
|
|
config.onStreamAdded(event.stream);
|
|
|
|
};
|
|
|
|
|
|
|
|
peer.onicecandidate = function (event) {
|
|
|
|
if (event.candidate)
|
|
|
|
config.onicecandidate(event.candidate);
|
|
|
|
};
|
|
|
|
|
|
|
|
peer.createOffer(function (sdp) {
|
|
|
|
peer.setLocalDescription(sdp);
|
|
|
|
config.onsdp(sdp);
|
|
|
|
}, onSdpError, offerAnswerConstraints);
|
|
|
|
|
|
|
|
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
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// var answer = Answer.createAnswer(config);
|
|
|
|
// answer.setRemoteDescription(sdp);
|
|
|
|
// answer.addIceCandidate(candidate);
|
|
|
|
var Answer = {
|
|
|
|
createAnswer: function (config) {
|
|
|
|
var peer = new RTCPeerConnection(iceServers, optionalArgument);
|
|
|
|
|
|
|
|
if (config.MediaStream) peer.addStream(config.MediaStream);
|
|
|
|
peer.onaddstream = function (event) {
|
|
|
|
config.onStreamAdded(event.stream);
|
|
|
|
};
|
|
|
|
|
|
|
|
peer.onicecandidate = function (event) {
|
|
|
|
if (event.candidate)
|
|
|
|
config.onicecandidate(event.candidate);
|
|
|
|
};
|
|
|
|
|
|
|
|
peer.setRemoteDescription(new RTCSessionDescription(config.sdp));
|
|
|
|
peer.createAnswer(function (sdp) {
|
|
|
|
peer.setLocalDescription(sdp);
|
|
|
|
config.onsdp(sdp);
|
|
|
|
}, onSdpError, offerAnswerConstraints);
|
|
|
|
|
|
|
|
this.peer = peer;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
addIceCandidate: function (candidate) {
|
|
|
|
this.peer.addIceCandidate(new RTCIceCandidate({
|
|
|
|
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
|
|
candidate: candidate.candidate
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function merge(mergein, mergeto) {
|
|
|
|
for (var t in mergeto) {
|
|
|
|
mergein[t] = mergeto[t];
|
|
|
|
}
|
|
|
|
return mergein;
|
|
|
|
}
|
2014-02-09 02:06:26 +00:00
|
|
|
|
|
|
|
window.URL = window.webkitURL || window.URL;
|
|
|
|
navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
|
|
|
navigator.getUserMedia = function(hints, onsuccess, onfailure) {
|
|
|
|
if(!hints) hints = {audio:true,video:true};
|
|
|
|
if(!onsuccess) throw 'Second argument is mandatory. navigator.getUserMedia(hints,onsuccess,onfailure)';
|
|
|
|
|
|
|
|
navigator.getMedia(hints, _onsuccess, _onfailure);
|
|
|
|
|
|
|
|
function _onsuccess(stream) {
|
|
|
|
onsuccess(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _onfailure(e) {
|
|
|
|
if(onfailure) onfailure(e);
|
|
|
|
else throw Error('getUserMedia failed: ' + JSON.stringify(e, null, '\t'));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-02-06 16:44:24 +00:00
|
|
|
})();
|