|
@@ -1,10 +1,40 @@
|
|
|
//CLIENT
|
|
|
|
|
|
- // Fallbacks for vendor-specific variables until the spec is finalized.
|
|
|
+// Fallbacks for vendor-specific variables until the spec is finalized.
|
|
|
+
|
|
|
+var PeerConnection = (window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection || window.mozRTCPeerConnection);
|
|
|
+var URL = (window.URL || window.webkitURL || window.msURL || window.oURL);
|
|
|
+var getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
|
|
|
+var nativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate);
|
|
|
+var nativeRTCSessionDescription = (window.mozRTCSessionDescription || window.RTCSessionDescription); // order is very important: "RTCSessionDescription" defined in Nighly but useless
|
|
|
+
|
|
|
+var sdpConstraints = {
|
|
|
+ 'mandatory': {
|
|
|
+ 'OfferToReceiveAudio': true,
|
|
|
+ 'OfferToReceiveVideo': true
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
-var PeerConnection = window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection;
|
|
|
-var URL = window.URL || window.webkitURL || window.msURL || window.oURL;
|
|
|
-var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
|
|
+if (navigator.webkitGetUserMedia) {
|
|
|
+ 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;
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
(function() {
|
|
|
|
|
@@ -44,7 +74,21 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
};
|
|
|
|
|
|
// Holds the STUN/ICE server to use for PeerConnections.
|
|
|
- rtc.SERVER = {iceServers:[{url:"stun:stun.l.google.com:19302"}]};
|
|
|
+ rtc.SERVER = function() {
|
|
|
+ if (navigator.mozGetUserMedia) {
|
|
|
+ return {
|
|
|
+ "iceServers": [{
|
|
|
+ "url": "stun:23.21.150.121"
|
|
|
+ }]
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ "iceServers": [{
|
|
|
+ "url": "stun:stun.l.google.com:19302"
|
|
|
+ }]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
// Reference to the lone PeerConnection instance.
|
|
|
rtc.peerConnections = {};
|
|
@@ -61,18 +105,32 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
rtc.dataChannels = {};
|
|
|
|
|
|
// PeerConnection datachannel configuration
|
|
|
- rtc.dataChannelConfig = {optional: [ {RtpDataChannels: true} ] };
|
|
|
+ rtc.dataChannelConfig = {
|
|
|
+ "optional": [{
|
|
|
+ "RtpDataChannels": true
|
|
|
+ }, {
|
|
|
+ "DtlsSrtpKeyAgreement": true
|
|
|
+ }]
|
|
|
+ };
|
|
|
+
|
|
|
+ rtc.pc_constraints = {
|
|
|
+ "optional": [{
|
|
|
+ "DtlsSrtpKeyAgreement": true
|
|
|
+ }]
|
|
|
+ }
|
|
|
|
|
|
|
|
|
// check whether data channel is supported.
|
|
|
rtc.checkDataChannelSupport = function() {
|
|
|
try {
|
|
|
// raises exception if createDataChannel is not supported
|
|
|
- var pc = new PeerConnection(rtc.SERVER, rtc.dataChannelConfig);
|
|
|
- var channel = pc.createDataChannel('supportCheck', {reliable: false});
|
|
|
+ var pc = new PeerConnection(rtc.SERVER(), rtc.dataChannelConfig);
|
|
|
+ var channel = pc.createDataChannel('supportCheck', {
|
|
|
+ reliable: false
|
|
|
+ });
|
|
|
channel.close();
|
|
|
return true;
|
|
|
- } catch(e) {
|
|
|
+ } catch (e) {
|
|
|
return false;
|
|
|
}
|
|
|
};
|
|
@@ -91,7 +149,7 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
|
|
|
rtc._socket.send(JSON.stringify({
|
|
|
"eventName": "join_room",
|
|
|
- "data":{
|
|
|
+ "data": {
|
|
|
"room": room
|
|
|
}
|
|
|
}));
|
|
@@ -119,7 +177,7 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
});
|
|
|
|
|
|
rtc.on('receive_ice_candidate', function(data) {
|
|
|
- var candidate = new RTCIceCandidate(data);
|
|
|
+ var candidate = new nativeRTCIceCandidate(data);
|
|
|
rtc.peerConnections[data.socketId].addIceCandidate(candidate);
|
|
|
rtc.fire('receive ice candidate', candidate);
|
|
|
});
|
|
@@ -174,24 +232,24 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
};
|
|
|
|
|
|
rtc.createPeerConnection = function(id) {
|
|
|
- var config;
|
|
|
- if (rtc.dataChannelSupport)
|
|
|
- config = rtc.dataChannelConfig;
|
|
|
|
|
|
- var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER, config);
|
|
|
+ var config = rtc.pc_constraints;
|
|
|
+ if (rtc.dataChannelSupport) config = rtc.dataChannelConfig;
|
|
|
+
|
|
|
+ var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER(), config);
|
|
|
pc.onicecandidate = function(event) {
|
|
|
if (event.candidate) {
|
|
|
- rtc._socket.send(JSON.stringify({
|
|
|
- "eventName": "send_ice_candidate",
|
|
|
- "data": {
|
|
|
- "label": event.candidate.label,
|
|
|
- "candidate": event.candidate.candidate,
|
|
|
- "socketId": id
|
|
|
- }
|
|
|
- }));
|
|
|
- }
|
|
|
- rtc.fire('ice candidate', event.candidate);
|
|
|
- };
|
|
|
+ rtc._socket.send(JSON.stringify({
|
|
|
+ "eventName": "send_ice_candidate",
|
|
|
+ "data": {
|
|
|
+ "label": event.candidate.sdpMLineIndex,
|
|
|
+ "candidate": event.candidate.candidate,
|
|
|
+ "socketId": id
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ rtc.fire('ice candidate', event.candidate);
|
|
|
+ };
|
|
|
|
|
|
pc.onopen = function() {
|
|
|
// TODO: Finalize this API
|
|
@@ -204,7 +262,7 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
};
|
|
|
|
|
|
if (rtc.dataChannelSupport) {
|
|
|
- pc.ondatachannel = function (evt) {
|
|
|
+ pc.ondatachannel = function(evt) {
|
|
|
console.log('data channel connecting ' + id);
|
|
|
rtc.addDataChannel(id, evt.channel);
|
|
|
};
|
|
@@ -215,60 +273,73 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
|
|
|
rtc.sendOffer = function(socketId) {
|
|
|
var pc = rtc.peerConnections[socketId];
|
|
|
- pc.createOffer( function(session_description) {
|
|
|
- pc.setLocalDescription(session_description);
|
|
|
- rtc._socket.send(JSON.stringify({
|
|
|
+
|
|
|
+ var constraints = {
|
|
|
+ "optional": [],
|
|
|
+ "mandatory": {
|
|
|
+ "MozDontOfferDataChannel": true
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // temporary measure to remove Moz* constraints in Chrome
|
|
|
+ if (navigator.webkitGetUserMedia) {
|
|
|
+ for (prop in constraints.mandatory) {
|
|
|
+ if (prop.indexOf("Moz") != -1) {
|
|
|
+ delete constraints.mandatory[prop];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ constraints = mergeConstraints(constraints, sdpConstraints);
|
|
|
+
|
|
|
+ pc.createOffer(function(session_description) {
|
|
|
+ session_description.sdp = preferOpus(session_description.sdp);
|
|
|
+ pc.setLocalDescription(session_description);
|
|
|
+ rtc._socket.send(JSON.stringify({
|
|
|
"eventName": "send_offer",
|
|
|
- "data":{
|
|
|
- "socketId": socketId,
|
|
|
- "sdp": session_description
|
|
|
- }
|
|
|
- }));
|
|
|
- });
|
|
|
+ "data": {
|
|
|
+ "socketId": socketId,
|
|
|
+ "sdp": session_description
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }, null, sdpConstraints);
|
|
|
};
|
|
|
|
|
|
-
|
|
|
rtc.receiveOffer = function(socketId, sdp) {
|
|
|
var pc = rtc.peerConnections[socketId];
|
|
|
- pc.setRemoteDescription(new RTCSessionDescription(sdp));
|
|
|
- rtc.sendAnswer(socketId);
|
|
|
+ rtc.sendAnswer(socketId, sdp);
|
|
|
};
|
|
|
|
|
|
-
|
|
|
- rtc.sendAnswer = function(socketId) {
|
|
|
+ rtc.sendAnswer = function(socketId, sdp) {
|
|
|
var pc = rtc.peerConnections[socketId];
|
|
|
- pc.createAnswer( function(session_description) {
|
|
|
- pc.setLocalDescription(session_description);
|
|
|
- rtc._socket.send(JSON.stringify({
|
|
|
+ pc.setRemoteDescription(new nativeRTCSessionDescription(sdp));
|
|
|
+ pc.createAnswer(function(session_description) {
|
|
|
+ pc.setLocalDescription(session_description);
|
|
|
+ rtc._socket.send(JSON.stringify({
|
|
|
"eventName": "send_answer",
|
|
|
- "data":{
|
|
|
- "socketId": socketId,
|
|
|
- "sdp": session_description
|
|
|
- }
|
|
|
+ "data": {
|
|
|
+ "socketId": socketId,
|
|
|
+ "sdp": session_description
|
|
|
}
|
|
|
- ));
|
|
|
- //TODO Unused variable!?
|
|
|
- var offer = pc.remoteDescription;
|
|
|
- });
|
|
|
+ }));
|
|
|
+ //TODO Unused variable!?
|
|
|
+ var offer = pc.remoteDescription;
|
|
|
+ }, null, sdpConstraints);
|
|
|
};
|
|
|
|
|
|
|
|
|
rtc.receiveAnswer = function(socketId, sdp) {
|
|
|
var pc = rtc.peerConnections[socketId];
|
|
|
- pc.setRemoteDescription(new RTCSessionDescription(sdp));
|
|
|
+ pc.setRemoteDescription(new nativeRTCSessionDescription(sdp));
|
|
|
};
|
|
|
|
|
|
|
|
|
rtc.createStream = function(opt, onSuccess, onFail) {
|
|
|
var options;
|
|
|
- onSuccess = onSuccess ||
|
|
|
- function() {};
|
|
|
- onFail = onFail ||
|
|
|
- function() {};
|
|
|
+ onSuccess = onSuccess || function() {};
|
|
|
+ onFail = onFail || function() {};
|
|
|
|
|
|
options = {
|
|
|
- video: !!opt.video,
|
|
|
- audio: !!opt.audio
|
|
|
+ video: !! opt.video,
|
|
|
+ audio: !! opt.audio
|
|
|
};
|
|
|
|
|
|
if (getUserMedia) {
|
|
@@ -300,7 +371,14 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
};
|
|
|
|
|
|
rtc.attachStream = function(stream, domId) {
|
|
|
- document.getElementById(domId).src = URL.createObjectURL(stream);
|
|
|
+ var element = document.getElementById(domId);
|
|
|
+ if (navigator.mozGetUserMedia) {
|
|
|
+ console.log("Attaching media stream");
|
|
|
+ element.mozSrcObject = stream;
|
|
|
+ element.play();
|
|
|
+ } else {
|
|
|
+ element.src = webkitURL.createObjectURL(stream);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
|
|
@@ -308,7 +386,7 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
if (!rtc.dataChannelSupport) {
|
|
|
//TODO this should be an exception
|
|
|
alert('webRTC data channel is not yet supported in this browser,' +
|
|
|
- ' or you must turn on experimental flags');
|
|
|
+ ' or you must turn on experimental flags');
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -320,22 +398,21 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
pc = pcOrId;
|
|
|
id = undefined;
|
|
|
for (var key in rtc.peerConnections) {
|
|
|
- if (rtc.peerConnections[key] === pc)
|
|
|
- id = key;
|
|
|
+ if (rtc.peerConnections[key] === pc) id = key;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (!id)
|
|
|
- throw new Error ('attempt to createDataChannel with unknown id');
|
|
|
+ if (!id) throw new Error('attempt to createDataChannel with unknown id');
|
|
|
|
|
|
- if (!pc || !(pc instanceof PeerConnection))
|
|
|
- throw new Error ('attempt to createDataChannel without peerConnection');
|
|
|
+ if (!pc || !(pc instanceof PeerConnection)) throw new Error('attempt to createDataChannel without peerConnection');
|
|
|
|
|
|
// need a label
|
|
|
label = label || 'fileTransfer' || String(id);
|
|
|
|
|
|
// chrome only supports reliable false atm.
|
|
|
- var options = {reliable: false};
|
|
|
+ var options = {
|
|
|
+ reliable: false
|
|
|
+ };
|
|
|
|
|
|
var channel;
|
|
|
try {
|
|
@@ -379,11 +456,10 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
};
|
|
|
|
|
|
rtc.addDataChannels = function() {
|
|
|
- if (!rtc.dataChannelSupport)
|
|
|
- return;
|
|
|
+ if (!rtc.dataChannelSupport) return;
|
|
|
|
|
|
for (var connection in rtc.peerConnections)
|
|
|
- rtc.createDataChannel(connection);
|
|
|
+ rtc.createDataChannel(connection);
|
|
|
};
|
|
|
|
|
|
|
|
@@ -395,3 +471,77 @@ var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || nav
|
|
|
});
|
|
|
|
|
|
}).call(this);
|
|
|
+
|
|
|
+function preferOpus(sdp) {
|
|
|
+ var sdpLines = sdp.split('\r\n');
|
|
|
+
|
|
|
+ // Search for m line.
|
|
|
+ for (var i = 0; i < sdpLines.length; i++) {
|
|
|
+ if (sdpLines[i].search('m=audio') !== -1) {
|
|
|
+ var mLineIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (mLineIndex === null) return sdp;
|
|
|
+
|
|
|
+ // If Opus is available, set it as the default in m line.
|
|
|
+ for (var i = 0; i < sdpLines.length; i++) {
|
|
|
+ if (sdpLines[i].search('opus/48000') !== -1) {
|
|
|
+ var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
|
|
|
+ if (opusPayload) sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove CN in m line and sdp.
|
|
|
+ sdpLines = removeCN(sdpLines, mLineIndex);
|
|
|
+
|
|
|
+ sdp = sdpLines.join('\r\n');
|
|
|
+ return sdp;
|
|
|
+}
|
|
|
+
|
|
|
+function extractSdp(sdpLine, pattern) {
|
|
|
+ var result = sdpLine.match(pattern);
|
|
|
+ return (result && result.length == 2) ? result[1] : null;
|
|
|
+}
|
|
|
+
|
|
|
+function setDefaultCodec(mLine, payload) {
|
|
|
+ var elements = mLine.split(' ');
|
|
|
+ var newLine = new Array();
|
|
|
+ var index = 0;
|
|
|
+ for (var i = 0; i < elements.length; i++) {
|
|
|
+ if (index === 3) // Format of media starts from the fourth.
|
|
|
+ newLine[index++] = payload; // Put target payload to the first.
|
|
|
+ if (elements[i] !== payload) newLine[index++] = elements[i];
|
|
|
+ }
|
|
|
+ return newLine.join(' ');
|
|
|
+}
|
|
|
+
|
|
|
+function removeCN(sdpLines, mLineIndex) {
|
|
|
+ var mLineElements = sdpLines[mLineIndex].split(' ');
|
|
|
+ // Scan from end for the convenience of removing an item.
|
|
|
+ for (var i = sdpLines.length - 1; i >= 0; i--) {
|
|
|
+ var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
|
|
|
+ if (payload) {
|
|
|
+ var cnPos = mLineElements.indexOf(payload);
|
|
|
+ if (cnPos !== -1) {
|
|
|
+ // Remove CN payload from m line.
|
|
|
+ mLineElements.splice(cnPos, 1);
|
|
|
+ }
|
|
|
+ // Remove CN line in sdp
|
|
|
+ sdpLines.splice(i, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sdpLines[mLineIndex] = mLineElements.join(' ');
|
|
|
+ return sdpLines;
|
|
|
+}
|
|
|
+
|
|
|
+function mergeConstraints(cons1, cons2) {
|
|
|
+ var merged = cons1;
|
|
|
+ for (var name in cons2.mandatory) {
|
|
|
+ merged.mandatory[name] = cons2.mandatory[name];
|
|
|
+ }
|
|
|
+ merged.optional.concat(cons2.optional);
|
|
|
+ return merged;
|
|
|
+}
|